greentic_types/
distributor.rs

1//! Distributor API data transfer objects used by runner/deployer clients.
2//!
3//! These mirror the `greentic:distributor-api@1.0.0` WIT shapes.
4
5use alloc::string::String;
6
7#[cfg(feature = "schemars")]
8use schemars::JsonSchema;
9#[cfg(feature = "serde")]
10use serde::{Deserialize, Serialize};
11use serde_json::Value;
12
13use crate::TenantCtx;
14
15/// Identifier for a distributor environment.
16#[derive(Clone, Debug, PartialEq, Eq, Hash)]
17#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
18#[cfg_attr(feature = "schemars", derive(JsonSchema))]
19pub struct DistributorEnvironmentId(pub String);
20
21impl DistributorEnvironmentId {
22    /// Returns the underlying identifier as a string slice.
23    pub fn as_str(&self) -> &str {
24        &self.0
25    }
26}
27
28impl From<String> for DistributorEnvironmentId {
29    fn from(value: String) -> Self {
30        Self(value)
31    }
32}
33
34impl From<&str> for DistributorEnvironmentId {
35    fn from(value: &str) -> Self {
36        Self(value.to_owned())
37    }
38}
39
40/// Digest for a component artifact.
41#[derive(Clone, Debug, PartialEq, Eq, Hash)]
42#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
43#[cfg_attr(feature = "schemars", derive(JsonSchema))]
44pub struct ComponentDigest(pub String);
45
46impl ComponentDigest {
47    /// Returns the digest as a string slice.
48    pub fn as_str(&self) -> &str {
49        &self.0
50    }
51
52    /// Heuristic check for sha256-like digests: `sha256:` + 64 lowercase hex chars.
53    pub fn is_sha256_like(&self) -> bool {
54        const PREFIX: &str = "sha256:";
55        let s = self.0.as_str();
56        if !s.starts_with(PREFIX) {
57            return false;
58        }
59        let rest = &s[PREFIX.len()..];
60        if rest.len() != 64 {
61            return false;
62        }
63        rest.chars()
64            .all(|c| c.is_ascii_hexdigit() && c.is_ascii_lowercase() || c.is_ascii_digit())
65    }
66}
67
68impl From<String> for ComponentDigest {
69    fn from(value: String) -> Self {
70        Self(value)
71    }
72}
73
74impl From<&str> for ComponentDigest {
75    fn from(value: &str) -> Self {
76        Self(value.to_owned())
77    }
78}
79
80/// Resolution status for a component.
81#[derive(Clone, Debug, PartialEq, Eq)]
82#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
83#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
84#[cfg_attr(feature = "schemars", derive(JsonSchema))]
85pub enum ComponentStatus {
86    /// Resolution in progress or awaiting cache.
87    Pending,
88    /// Component is ready for use.
89    Ready,
90    /// Resolution failed with a reason.
91    Failed {
92        /// Human-readable failure explanation.
93        reason: String,
94    },
95}
96
97/// Location of the resolved artifact.
98#[derive(Clone, Debug, PartialEq, Eq)]
99#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
100#[cfg_attr(feature = "schemars", derive(JsonSchema))]
101#[cfg_attr(feature = "serde", serde(tag = "kind", rename_all = "snake_case"))]
102pub enum ArtifactLocation {
103    /// Local file path on disk.
104    FilePath {
105        /// Absolute or relative path to the artifact.
106        path: String,
107    },
108    /// OCI reference to the artifact.
109    OciReference {
110        /// Reference string to the OCI artifact.
111        reference: String,
112    },
113    /// Internal distributor handle.
114    DistributorInternal {
115        /// Opaque handle understood by the distributor.
116        handle: String,
117    },
118}
119
120/// Summary of artifact signature verification.
121#[derive(Clone, Debug, PartialEq, Eq)]
122#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
123#[cfg_attr(feature = "schemars", derive(JsonSchema))]
124pub struct SignatureSummary {
125    /// Whether the signature verified.
126    pub verified: bool,
127    /// Signer identifier or key hint.
128    pub signer: String,
129    /// Opaque extra details.
130    pub extra: Value,
131}
132
133/// Cache metadata for resolved artifacts.
134#[derive(Clone, Debug, PartialEq, Eq)]
135#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
136#[cfg_attr(feature = "schemars", derive(JsonSchema))]
137pub struct CacheInfo {
138    /// Size of the cached artifact in bytes.
139    pub size_bytes: u64,
140    /// Last use timestamp in ISO 8601 (UTC).
141    pub last_used_utc: String,
142    /// Last refresh timestamp in ISO 8601 (UTC).
143    pub last_refreshed_utc: String,
144}
145
146/// Request to resolve a component for a tenant/environment.
147#[derive(Clone, Debug, PartialEq, Eq)]
148#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
149#[cfg_attr(feature = "schemars", derive(JsonSchema))]
150pub struct ResolveComponentRequest {
151    /// Tenant context for the request.
152    pub tenant: TenantCtx,
153    /// Distributor environment identifier.
154    pub environment_id: DistributorEnvironmentId,
155    /// Pack identifier.
156    pub pack_id: String,
157    /// Component identifier.
158    pub component_id: String,
159    /// Requested version or label.
160    pub version: String,
161    /// Opaque extension field.
162    pub extra: Value,
163}
164
165/// Response returned by the distributor.
166#[derive(Clone, Debug, PartialEq, Eq)]
167#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
168#[cfg_attr(feature = "schemars", derive(JsonSchema))]
169pub struct ResolveComponentResponse {
170    /// Resolution status.
171    pub status: ComponentStatus,
172    /// Content digest of the component.
173    pub digest: ComponentDigest,
174    /// Location of the resolved artifact.
175    pub artifact: ArtifactLocation,
176    /// Signature summary.
177    pub signature: SignatureSummary,
178    /// Cache metadata.
179    pub cache: CacheInfo,
180}