diff --git a/cfgsync/adapter/src/artifacts.rs b/cfgsync/adapter/src/artifacts.rs index 2418ade..1afe2e6 100644 --- a/cfgsync/adapter/src/artifacts.rs +++ b/cfgsync/adapter/src/artifacts.rs @@ -19,21 +19,29 @@ pub struct ArtifactSet { } impl ArtifactSet { + /// Creates one logical artifact group. + /// + /// The same type is used for: + /// - node-local files that belong to one node only + /// - shared files that should be delivered alongside every node #[must_use] pub fn new(files: Vec) -> Self { Self { files } } + /// Returns the files carried by this artifact group. #[must_use] pub fn files(&self) -> &[ArtifactFile] { &self.files } + /// Returns `true` when the group contains no files. #[must_use] pub fn is_empty(&self) -> bool { self.files.is_empty() } + /// Consumes the group and returns its files. #[must_use] pub fn into_files(self) -> Vec { self.files @@ -49,21 +57,25 @@ pub struct ResolvedNodeArtifacts { } impl ResolvedNodeArtifacts { + /// Creates the resolved file set for one node. #[must_use] pub fn new(node: ArtifactSet, shared: ArtifactSet) -> Self { Self { node, shared } } + /// Returns the node-local files. #[must_use] pub fn node(&self) -> &ArtifactSet { &self.node } + /// Returns the shared files delivered alongside every node. #[must_use] pub fn shared(&self) -> &ArtifactSet { &self.shared } + /// Returns the full file list that should be written for this node. #[must_use] pub fn files(&self) -> Vec { let mut files = self.node.files().to_vec(); @@ -79,6 +91,7 @@ pub struct NodeArtifactsCatalog { } impl NodeArtifactsCatalog { + /// Creates a catalog indexed by stable node identifier. #[must_use] pub fn new(nodes: Vec) -> Self { let nodes = nodes @@ -89,21 +102,25 @@ impl NodeArtifactsCatalog { Self { nodes } } + /// Resolves one node's local artifacts by identifier. #[must_use] pub fn resolve(&self, identifier: &str) -> Option<&NodeArtifacts> { self.nodes.get(identifier) } + /// Returns the number of nodes in the catalog. #[must_use] pub fn len(&self) -> usize { self.nodes.len() } + /// Returns `true` when the catalog is empty. #[must_use] pub fn is_empty(&self) -> bool { self.nodes.is_empty() } + /// Consumes the catalog and returns its node entries. #[must_use] pub fn into_nodes(self) -> Vec { self.nodes.into_values().collect() @@ -118,26 +135,34 @@ pub struct MaterializedArtifacts { } impl MaterializedArtifacts { + /// Creates a fully materialized cfgsync result. + /// + /// `nodes` contains node-specific files. + /// `shared` contains files that should accompany every node. #[must_use] pub fn new(nodes: NodeArtifactsCatalog, shared: ArtifactSet) -> Self { Self { nodes, shared } } + /// Creates a materialized result without any shared files. #[must_use] pub fn from_catalog(nodes: NodeArtifactsCatalog) -> Self { Self::new(nodes, ArtifactSet::default()) } + /// Returns the node-specific artifact catalog. #[must_use] pub fn nodes(&self) -> &NodeArtifactsCatalog { &self.nodes } + /// Returns the shared artifact set. #[must_use] pub fn shared(&self) -> &ArtifactSet { &self.shared } + /// Resolves the full file set for one node. #[must_use] pub fn resolve(&self, identifier: &str) -> Option { self.nodes.resolve(identifier).map(|node| { diff --git a/cfgsync/adapter/src/materializer.rs b/cfgsync/adapter/src/materializer.rs index b2d18de..8e00b96 100644 --- a/cfgsync/adapter/src/materializer.rs +++ b/cfgsync/adapter/src/materializer.rs @@ -10,6 +10,10 @@ pub type DynCfgsyncError = Box; /// Adapter-side materialization contract for a single registered node. pub trait NodeArtifactsMaterializer: Send + Sync { + /// Resolves one node from the current registration set. + /// + /// Returning `Ok(None)` means the node is known but its artifacts are not + /// ready yet. fn materialize( &self, registration: &NodeRegistration, @@ -20,6 +24,13 @@ pub trait NodeArtifactsMaterializer: Send + Sync { /// Adapter contract for materializing a whole registration snapshot into /// per-node cfgsync artifacts. pub trait RegistrationSnapshotMaterializer: Send + Sync { + /// Materializes the current registration set. + /// + /// This is the main registration-driven integration point for cfgsync. + /// Implementations decide: + /// - when the current snapshot is ready to serve + /// - which per-node artifacts should be produced + /// - which shared artifacts should accompany every node fn materialize_snapshot( &self, registrations: &RegistrationSnapshot, @@ -28,6 +39,7 @@ pub trait RegistrationSnapshotMaterializer: Send + Sync { /// Optional hook for persisting or publishing materialized cfgsync artifacts. pub trait MaterializedArtifactsSink: Send + Sync { + /// Persists or publishes a ready materialization result. fn persist(&self, artifacts: &MaterializedArtifacts) -> Result<(), DynCfgsyncError>; } @@ -40,11 +52,13 @@ pub enum MaterializationResult { } impl MaterializationResult { + /// Creates a ready materialization result. #[must_use] pub fn ready(nodes: MaterializedArtifacts) -> Self { Self::Ready(nodes) } + /// Returns the ready artifacts when materialization succeeded. #[must_use] pub fn artifacts(&self) -> Option<&MaterializedArtifacts> { match self { @@ -66,6 +80,8 @@ struct CachedSnapshot { } impl CachedSnapshotMaterializer { + /// Wraps a snapshot materializer with deterministic snapshot-result + /// caching. #[must_use] pub fn new(inner: M) -> Self { Self { @@ -126,6 +142,8 @@ pub struct PersistingSnapshotMaterializer { } impl PersistingSnapshotMaterializer { + /// Wraps a snapshot materializer with one-time persistence for each + /// distinct registration snapshot. #[must_use] pub fn new(inner: M, sink: S) -> Self { Self { diff --git a/cfgsync/core/src/protocol.rs b/cfgsync/core/src/protocol.rs index ca8f205..f794fd1 100644 --- a/cfgsync/core/src/protocol.rs +++ b/cfgsync/core/src/protocol.rs @@ -28,16 +28,19 @@ pub struct RegistrationPayload { } impl RegistrationPayload { + /// Creates an empty adapter-owned payload. #[must_use] pub fn new() -> Self { Self::default() } + /// Returns `true` when no adapter-owned payload was attached. #[must_use] pub fn is_empty(&self) -> bool { self.raw_json.is_none() } + /// Stores one typed adapter payload as opaque JSON. pub fn from_serializable(value: &T) -> Result where T: Serialize, @@ -47,6 +50,7 @@ impl RegistrationPayload { }) } + /// Stores a raw JSON payload after validating that it parses. pub fn from_json_str(raw_json: &str) -> Result { let value: Value = serde_json::from_str(raw_json)?; @@ -55,6 +59,7 @@ impl RegistrationPayload { }) } + /// Deserializes the adapter-owned payload into the requested type. pub fn deserialize(&self) -> Result, serde_json::Error> where T: DeserializeOwned, @@ -65,6 +70,7 @@ impl RegistrationPayload { .transpose() } + /// Returns the validated JSON representation stored in this payload. #[must_use] pub fn raw_json(&self) -> Option<&str> { self.raw_json.as_deref() @@ -111,6 +117,7 @@ pub struct NodeRegistration { } impl NodeRegistration { + /// Creates a registration with the generic node identity fields only. #[must_use] pub fn new(identifier: impl Into, ip: Ipv4Addr) -> Self { Self { @@ -120,6 +127,7 @@ impl NodeRegistration { } } + /// Attaches one typed adapter-owned payload to this registration. pub fn with_metadata(mut self, metadata: &T) -> Result where T: Serialize, @@ -128,6 +136,7 @@ impl NodeRegistration { Ok(self) } + /// Attaches a prebuilt registration payload to this registration. #[must_use] pub fn with_payload(mut self, payload: RegistrationPayload) -> Self { self.metadata = payload; @@ -136,6 +145,7 @@ impl NodeRegistration { } impl NodeArtifactsPayload { + /// Creates a payload from the files that should be written for one node. #[must_use] pub fn from_files(files: Vec) -> Self { Self { @@ -144,11 +154,13 @@ impl NodeArtifactsPayload { } } + /// Returns the files carried by this payload. #[must_use] pub fn files(&self) -> &[NodeArtifactFile] { &self.files } + /// Returns `true` when the payload carries no files. #[must_use] pub fn is_empty(&self) -> bool { self.files.is_empty() @@ -172,6 +184,7 @@ pub struct CfgsyncErrorResponse { } impl CfgsyncErrorResponse { + /// Builds a missing-config error for one identifier. #[must_use] pub fn missing_config(identifier: &str) -> Self { Self { @@ -180,6 +193,7 @@ impl CfgsyncErrorResponse { } } + /// Builds a not-ready error for one identifier. #[must_use] pub fn not_ready(identifier: &str) -> Self { Self { @@ -188,6 +202,7 @@ impl CfgsyncErrorResponse { } } + /// Builds an internal cfgsync error. #[must_use] pub fn internal(message: impl Into) -> Self { Self { diff --git a/cfgsync/runtime/src/server.rs b/cfgsync/runtime/src/server.rs index cfefd78..0e00628 100644 --- a/cfgsync/runtime/src/server.rs +++ b/cfgsync/runtime/src/server.rs @@ -17,9 +17,17 @@ use thiserror::Error; #[derive(Debug, Clone, PartialEq, Eq)] pub struct CfgsyncServerConfig { pub port: u16, + /// Source used by the runtime-managed cfgsync server. pub source: CfgsyncServerSource, } +/// Runtime cfgsync source loaded from config. +/// +/// This type is intentionally runtime-oriented: +/// - `Bundle` serves a static precomputed bundle directly +/// - `RegistrationBundle` serves a precomputed bundle through the registration +/// protocol, which is useful when the consumer wants clients to register +/// before receiving already-materialized artifacts #[derive(Debug, Clone, Deserialize, PartialEq, Eq)] #[serde(tag = "kind", rename_all = "snake_case")] pub enum CfgsyncServerSource { @@ -189,6 +197,13 @@ pub async fn serve_cfgsync_from_config(config_path: &Path) -> anyhow::Result<()> /// Builds a registration-backed cfgsync router directly from a snapshot /// materializer. +/// +/// This is the main code-driven entrypoint for apps that want cfgsync to own: +/// - node registration +/// - readiness polling +/// - artifact serving +/// +/// while the app owns only snapshot materialization logic. pub fn build_snapshot_cfgsync_router(materializer: M) -> Router where M: RegistrationSnapshotMaterializer + 'static, @@ -199,6 +214,9 @@ where /// Builds a registration-backed cfgsync router with a persistence hook for /// ready materialization results. +/// +/// Use this when the application wants cfgsync to persist or publish shared +/// artifacts after a snapshot becomes ready. pub fn build_persisted_snapshot_cfgsync_router(materializer: M, sink: S) -> Router where M: RegistrationSnapshotMaterializer + 'static, @@ -213,6 +231,9 @@ where /// Runs a registration-backed cfgsync server directly from a snapshot /// materializer. +/// +/// This is the simplest runtime entrypoint when the application already has a +/// materializer value and does not need to compose extra routes. pub async fn serve_snapshot_cfgsync(port: u16, materializer: M) -> Result<(), RunCfgsyncError> where M: RegistrationSnapshotMaterializer + 'static, @@ -223,6 +244,9 @@ where /// Runs a registration-backed cfgsync server with a persistence hook for ready /// materialization results. +/// +/// This is the direct serving counterpart to +/// [`build_persisted_snapshot_cfgsync_router`]. pub async fn serve_persisted_snapshot_cfgsync( port: u16, materializer: M,