greentic_types/
provider.rs

1//! Provider manifest and index data structures.
2//!
3//! These types model the JSON returned by provider-core `describe()` and the provider index
4//! entries used by store, deployer, and runner components.
5
6use alloc::collections::{BTreeMap, BTreeSet};
7use alloc::{format, string::String, vec::Vec};
8
9#[cfg(feature = "schemars")]
10use schemars::JsonSchema;
11#[cfg(feature = "serde")]
12use serde::{Deserialize, Serialize};
13use serde_json::Value;
14
15use crate::{ErrorCode, GResult, GreenticError};
16
17/// Canonical provider extension identifier stored in pack manifests.
18pub const PROVIDER_EXTENSION_ID: &str = "greentic.provider-extension.v1";
19
20/// Manifest describing a provider returned by `describe()`.
21#[derive(Clone, Debug, PartialEq, Eq)]
22#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
23#[cfg_attr(feature = "schemars", derive(JsonSchema))]
24pub struct ProviderManifest {
25    /// Provider type identifier (string-based to avoid enum coupling).
26    pub provider_type: String,
27    /// Capabilities advertised by the provider.
28    #[cfg_attr(
29        feature = "serde",
30        serde(default, skip_serializing_if = "Vec::is_empty")
31    )]
32    pub capabilities: Vec<String>,
33    /// Operations exposed by the provider.
34    #[cfg_attr(
35        feature = "serde",
36        serde(default, skip_serializing_if = "Vec::is_empty")
37    )]
38    pub ops: Vec<String>,
39    /// Optional JSON Schema reference for configuration.
40    #[cfg_attr(
41        feature = "serde",
42        serde(default, skip_serializing_if = "Option::is_none")
43    )]
44    pub config_schema_ref: Option<String>,
45    /// Optional JSON Schema reference for provider state.
46    #[cfg_attr(
47        feature = "serde",
48        serde(default, skip_serializing_if = "Option::is_none")
49    )]
50    pub state_schema_ref: Option<String>,
51}
52
53/// Runtime binding for a provider implementation.
54#[derive(Clone, Debug, PartialEq, Eq)]
55#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
56#[cfg_attr(feature = "schemars", derive(JsonSchema))]
57pub struct ProviderRuntimeRef {
58    /// Component identifier for the provider runtime.
59    pub component_ref: String,
60    /// Exported function implementing the provider runtime.
61    pub export: String,
62    /// WIT world for the provider runtime.
63    pub world: String,
64}
65
66/// Provider declaration stored in indexes and extension payloads.
67#[derive(Clone, Debug, PartialEq, Eq)]
68#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
69#[cfg_attr(feature = "schemars", derive(JsonSchema))]
70pub struct ProviderDecl {
71    /// Provider type identifier (string-based to avoid enum coupling).
72    pub provider_type: String,
73    /// Capabilities advertised by the provider.
74    #[cfg_attr(
75        feature = "serde",
76        serde(default, skip_serializing_if = "Vec::is_empty")
77    )]
78    pub capabilities: Vec<String>,
79    /// Operations exposed by the provider.
80    #[cfg_attr(
81        feature = "serde",
82        serde(default, skip_serializing_if = "Vec::is_empty")
83    )]
84    pub ops: Vec<String>,
85    /// JSON Schema reference for configuration.
86    pub config_schema_ref: String,
87    /// Optional JSON Schema reference for provider state.
88    #[cfg_attr(
89        feature = "serde",
90        serde(default, skip_serializing_if = "Option::is_none")
91    )]
92    pub state_schema_ref: Option<String>,
93    /// Runtime binding information for the provider.
94    pub runtime: ProviderRuntimeRef,
95    /// Optional documentation reference.
96    #[cfg_attr(
97        feature = "serde",
98        serde(default, skip_serializing_if = "Option::is_none")
99    )]
100    pub docs_ref: Option<String>,
101}
102
103/// Inline extension payload embedding provider declarations.
104#[derive(Clone, Debug, PartialEq, Eq, Default)]
105#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
106#[cfg_attr(feature = "schemars", derive(JsonSchema))]
107pub struct ProviderExtensionInline {
108    /// Providers included in the extension payload.
109    pub providers: Vec<ProviderDecl>,
110    /// Additional fields preserved for forward-compatibility.
111    #[cfg_attr(
112        feature = "serde",
113        serde(default, skip_serializing_if = "BTreeMap::is_empty", flatten)
114    )]
115    pub additional_fields: BTreeMap<String, Value>,
116}
117
118impl ProviderExtensionInline {
119    /// Performs basic structural validation without provider-specific semantics.
120    pub fn validate_basic(&self) -> GResult<()> {
121        let mut seen = BTreeSet::new();
122        for provider in &self.providers {
123            if provider.provider_type.is_empty() {
124                return Err(GreenticError::new(
125                    ErrorCode::InvalidInput,
126                    "ProviderDecl.provider_type must not be empty",
127                ));
128            }
129            if !seen.insert(&provider.provider_type) {
130                return Err(GreenticError::new(
131                    ErrorCode::InvalidInput,
132                    format!(
133                        "duplicate provider_type '{}' in ProviderExtensionInline",
134                        provider.provider_type
135                    ),
136                ));
137            }
138            if provider.runtime.component_ref.trim().is_empty()
139                || provider.runtime.export.trim().is_empty()
140                || provider.runtime.world.trim().is_empty()
141            {
142                return Err(GreenticError::new(
143                    ErrorCode::InvalidInput,
144                    format!(
145                        "runtime fields must be set for provider '{}'",
146                        provider.provider_type
147                    ),
148                ));
149            }
150        }
151        Ok(())
152    }
153}