Skip to main content

helm_schema_core/
provider_schema_fragment.rs

1use serde_json::Value;
2
3use crate::ProviderOrigin;
4
5/// Provider document location that produced a schema fragment.
6#[derive(Clone, Debug, Eq, PartialEq)]
7pub struct ProviderSchemaSource {
8    origin: ProviderOrigin,
9    source_id: String,
10    version: Option<String>,
11    filename: String,
12    pointer: String,
13}
14
15impl ProviderSchemaSource {
16    /// Describes one provider document and JSON pointer that supplied a fragment.
17    #[must_use]
18    pub fn new(
19        origin: ProviderOrigin,
20        source_id: impl Into<String>,
21        version: Option<String>,
22        filename: impl Into<String>,
23        pointer: impl Into<String>,
24    ) -> Self {
25        Self {
26            origin,
27            source_id: source_id.into(),
28            version,
29            filename: filename.into(),
30            pointer: pointer.into(),
31        }
32    }
33
34    /// Describes a fragment sourced from a versioned Kubernetes `OpenAPI` document.
35    #[must_use]
36    pub fn kubernetes_openapi(
37        source_id: impl Into<String>,
38        version: impl Into<String>,
39        filename: impl Into<String>,
40        pointer: impl Into<String>,
41    ) -> Self {
42        Self::new(
43            ProviderOrigin::KubernetesOpenApi,
44            source_id,
45            Some(version.into()),
46            filename,
47            pointer,
48        )
49    }
50
51    /// Returns the provider family that owns the source.
52    #[must_use]
53    pub fn origin(&self) -> ProviderOrigin {
54        self.origin
55    }
56
57    /// Returns the stable provider-source namespace.
58    #[must_use]
59    pub fn source_id(&self) -> &str {
60        &self.source_id
61    }
62
63    /// Returns the Kubernetes release associated with the document, if any.
64    #[must_use]
65    pub fn version(&self) -> Option<&str> {
66        self.version.as_deref()
67    }
68
69    /// Returns the source document's filename.
70    #[must_use]
71    pub fn filename(&self) -> &str {
72        &self.filename
73    }
74
75    /// Returns the JSON pointer of the fragment within the source document.
76    #[must_use]
77    pub fn pointer(&self) -> &str {
78        &self.pointer
79    }
80}
81
82/// Schema fragment returned by a provider for one resource/path lookup.
83#[derive(Clone, Debug, PartialEq)]
84pub struct ProviderSchemaFragment {
85    schema: Value,
86    source_fragment: Option<ProviderSourceFragment>,
87    /// Whether the resolved path's final segment is listed in its parent
88    /// object's `required` array: the provider rejects a resource that
89    /// omits the field (or, for typed fields, supplies null).
90    required_in_parent: bool,
91}
92
93/// Provider-owned source leaf.
94#[derive(Clone, Debug, PartialEq)]
95pub struct ProviderSourceFragment {
96    source: ProviderSchemaSource,
97    source_schema: Value,
98    definition_schema: Value,
99}
100
101impl ProviderSourceFragment {
102    fn new(source: ProviderSchemaSource, source_schema: Value, definition_schema: Value) -> Self {
103        Self {
104            source,
105            source_schema,
106            definition_schema,
107        }
108    }
109
110    /// Returns the provider document location that owns this fragment.
111    #[must_use]
112    pub fn source(&self) -> &ProviderSchemaSource {
113        &self.source
114    }
115
116    /// Returns the normalized schema document loaded from the provider.
117    #[must_use]
118    pub fn source_schema(&self) -> &Value {
119        &self.source_schema
120    }
121
122    /// Returns the definition root used to resolve references in the fragment.
123    #[must_use]
124    pub fn definition_schema(&self) -> &Value {
125        &self.definition_schema
126    }
127}
128
129impl ProviderSchemaFragment {
130    /// Wraps and normalizes a materialized provider schema.
131    #[must_use]
132    pub fn new(mut schema: Value) -> Self {
133        // Ingestion is the one boundary where foreign regex dialects enter:
134        // provider documents spell Go/RE2 patterns (a leading `(?i)`), and
135        // everything downstream — resolution, arms, emitted fixtures — must
136        // only ever see the portable ECMA-262 form.
137        crate::normalize_schema_pattern_dialects(&mut schema);
138        Self {
139            schema,
140            source_fragment: None,
141            required_in_parent: false,
142        }
143    }
144
145    /// Attaches source ownership using the materialized schema as its definition root.
146    #[must_use]
147    pub fn with_source(mut self, source: ProviderSchemaSource) -> Self {
148        self.source_fragment = Some(ProviderSourceFragment::new(
149            source,
150            self.schema.clone(),
151            self.schema.clone(),
152        ));
153        self
154    }
155
156    /// Attaches explicit source-document and definition-root schemas.
157    #[must_use]
158    pub fn with_source_definition_schema(
159        mut self,
160        source: ProviderSchemaSource,
161        mut source_schema: Value,
162        mut definition_schema: Value,
163    ) -> Self {
164        crate::normalize_schema_pattern_dialects(&mut source_schema);
165        crate::normalize_schema_pattern_dialects(&mut definition_schema);
166        self.source_fragment = Some(ProviderSourceFragment::new(
167            source,
168            source_schema,
169            definition_schema,
170        ));
171        self
172    }
173
174    /// Records whether the fragment's field is required by its parent schema.
175    #[must_use]
176    pub fn with_required_in_parent(mut self, required_in_parent: bool) -> Self {
177        self.required_in_parent = required_in_parent;
178        self
179    }
180
181    /// Reports whether the fragment's field is required by its parent schema.
182    #[must_use]
183    pub fn required_in_parent(&self) -> bool {
184        self.required_in_parent
185    }
186
187    /// Returns the materialized schema at the requested resource path.
188    #[must_use]
189    pub fn schema(&self) -> &Value {
190        &self.schema
191    }
192
193    /// Returns source ownership when the fragment still maps to a provider leaf.
194    #[must_use]
195    pub fn source(&self) -> Option<&ProviderSchemaSource> {
196        self.source_fragment
197            .as_ref()
198            .map(ProviderSourceFragment::source)
199    }
200
201    /// Consumes the fragment and returns its materialized schema.
202    #[must_use]
203    pub fn into_schema(self) -> Value {
204        self.schema
205    }
206
207    /// Consumes the fragment into its schema and optional provider-owned source leaf.
208    #[must_use]
209    pub fn into_source_parts(self) -> (Value, Option<ProviderSourceFragment>) {
210        (self.schema, self.source_fragment)
211    }
212
213    /// Transform the materialized schema while preserving provider ownership.
214    #[must_use]
215    pub fn try_map_schema(self, map_schema: impl FnOnce(&Value) -> Option<Value>) -> Option<Self> {
216        let Self {
217            schema,
218            source_fragment,
219            required_in_parent,
220        } = self;
221        let mapped = map_schema(&schema)?;
222        let source_fragment = (mapped == schema).then_some(source_fragment).flatten();
223        Some(Self {
224            schema: mapped,
225            source_fragment,
226            required_in_parent,
227        })
228    }
229}
230
231impl From<Value> for ProviderSchemaFragment {
232    fn from(schema: Value) -> Self {
233        Self::new(schema)
234    }
235}