Expand cfgsync rustdoc coverage

This commit is contained in:
andrussal 2026-03-12 07:30:01 +01:00
parent 5e9b59140d
commit b90734483b
4 changed files with 82 additions and 0 deletions

View File

@ -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<ArtifactFile>) -> 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<ArtifactFile> {
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<ArtifactFile> {
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<NodeArtifacts>) -> 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<NodeArtifacts> {
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<ResolvedNodeArtifacts> {
self.nodes.resolve(identifier).map(|node| {

View File

@ -10,6 +10,10 @@ pub type DynCfgsyncError = Box<dyn Error + Send + Sync + 'static>;
/// 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<M> CachedSnapshotMaterializer<M> {
/// 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<M, S> {
}
impl<M, S> PersistingSnapshotMaterializer<M, S> {
/// Wraps a snapshot materializer with one-time persistence for each
/// distinct registration snapshot.
#[must_use]
pub fn new(inner: M, sink: S) -> Self {
Self {

View File

@ -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<T>(value: &T) -> Result<Self, serde_json::Error>
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<Self, serde_json::Error> {
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<T>(&self) -> Result<Option<T>, 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<String>, ip: Ipv4Addr) -> Self {
Self {
@ -120,6 +127,7 @@ impl NodeRegistration {
}
}
/// Attaches one typed adapter-owned payload to this registration.
pub fn with_metadata<T>(mut self, metadata: &T) -> Result<Self, serde_json::Error>
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<NodeArtifactFile>) -> 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<String>) -> Self {
Self {

View File

@ -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<M>(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<M, S>(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<M>(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<M, S>(
port: u16,
materializer: M,