use super::*;
#[derive(Clone)]
pub struct DistributedClientSurfaceExport {
service_id: String,
identity: ClientSurfaceIdentity,
surface: Arc<Surface>,
execution: ClientExecutionLimits,
}
impl std::fmt::Debug for DistributedClientSurfaceExport {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("DistributedClientSurfaceExport")
.field("service_id", &self.service_id)
.field("identity", &self.identity)
.finish_non_exhaustive()
}
}
impl DistributedClientSurfaceExport {
fn new(
service_id: impl Into<String>,
identity: ClientSurfaceIdentity,
surface: impl Into<Arc<Surface>>,
execution: ClientExecutionLimits,
) -> Self {
Self {
service_id: service_id.into(),
identity,
surface: surface.into(),
execution,
}
}
pub fn from_selected(
service_id: impl Into<String>,
surface: impl Into<Arc<Surface>>,
) -> Result<Self, ClientManifestError> {
Self::from_selected_with_execution(service_id, surface, ClientExecutionLimits::default())
}
pub(crate) fn from_selected_with_execution(
service_id: impl Into<String>,
surface: impl Into<Arc<Surface>>,
execution: ClientExecutionLimits,
) -> Result<Self, ClientManifestError> {
let surface = surface.into();
let service_id = service_id.into();
let identity = match &surface.selection {
SurfaceSelection::Catalog => {
return Err(ClientManifestError(
"client exports require an explicitly role- or application-selected Surface"
.into(),
));
}
SurfaceSelection::Role { name } => ClientSurfaceIdentity::role(name),
SurfaceSelection::Application {
name,
eligible_roles,
schema_roles,
} => {
ClientSurfaceIdentity::application_with_schema_roles(
name,
eligible_roles.clone(),
schema_roles.clone(),
)
}
};
validate_service_provenance(&service_id, &surface)?;
Ok(Self::new(service_id, identity, surface, execution))
}
pub fn from_contract(
service_id: impl Into<String>,
surface: impl Into<Arc<Surface>>,
) -> Result<Self, ClientManifestError> {
let surface = surface.into();
let service_id = service_id.into();
let identity = match &surface.selection {
SurfaceSelection::Catalog => {
return Err(ClientManifestError(
"client exports require an explicitly role- or application-selected Surface"
.into(),
));
}
SurfaceSelection::Role { name } => ClientSurfaceIdentity::role(name),
SurfaceSelection::Application {
name,
eligible_roles,
schema_roles,
} => {
ClientSurfaceIdentity::application_with_schema_roles(
name,
eligible_roles.clone(),
schema_roles.clone(),
)
}
};
if surface.service_binding.is_some() {
return Err(ClientManifestError(
"contract-only client export cannot carry executable Service provenance".into(),
));
}
Ok(Self::new(
service_id,
identity,
surface,
ClientExecutionLimits::default(),
))
}
pub fn manifest(&self) -> Result<DistributedClientManifest, ClientManifestError> {
client_manifest_from_surface_with_execution(
&self.service_id,
self.identity.clone(),
&self.surface,
self.execution.clone(),
)
}
pub fn service_id(&self) -> &str {
&self.service_id
}
pub fn identity(&self) -> &ClientSurfaceIdentity {
&self.identity
}
pub fn surface(&self) -> &Arc<Surface> {
&self.surface
}
pub fn manifest_json_pretty(&self) -> Result<String, ClientManifestError> {
Ok(serde_json::to_string_pretty(&self.manifest()?)?)
}
}
fn validate_service_provenance(
service_id: &str,
surface: &Surface,
) -> Result<(), ClientManifestError> {
let has_typed_commands = !surface.commands.is_empty();
match (&surface.service_binding, has_typed_commands) {
(Some(binding), _) if binding.service_id != service_id => Err(ClientManifestError(
format!(
"client export service ID `{service_id}` does not match typed Surface provenance `{}`",
binding.service_id
),
)),
(None, true) => Err(ClientManifestError(
"typed client export requires Surface provenance from Surface::with_service"
.into(),
)),
_ => Ok(()),
}
}