tf_provider/
schema.rs

1// This file is part of the tf-provider project
2//
3// Copyright (C) ANEO, 2024-2024. All rights reserved.
4//
5// Licensed under the Apache License, Version 2.0 (the "License")
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9//     http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17use std::{collections::HashMap, fmt::Display, hash::Hash};
18
19use serde::{ser::SerializeMap, Serialize};
20
21use crate::tfplugin6;
22
23/// Specify if a description must interpreted as markdown or plain
24#[derive(Clone, PartialEq, Eq, Hash, Debug, Default)]
25pub enum StringKind {
26    /// No formatting
27    #[default]
28    Plain = 0,
29
30    /// Markdown formatting
31    Markdown = 1,
32}
33
34/// Description
35#[derive(Clone, PartialEq, Eq, Hash, Debug)]
36pub struct Description {
37    /// Kind of the description (either [`StringKind::Plain`] or [`StringKind::Markdown`])
38    pub kind: StringKind,
39    /// Content of the description
40    pub content: String,
41}
42
43impl Description {
44    /// Create a plain description
45    ///
46    /// # Arguments
47    ///
48    /// * `content` - Content of the description
49    pub fn plain<T>(content: T) -> Self
50    where
51        T: ToString,
52    {
53        Self {
54            kind: StringKind::Plain,
55            content: content.to_string(),
56        }
57    }
58    /// Create a markdown description
59    ///
60    /// # Arguments
61    ///
62    /// * `content` - Content of the description
63    pub fn markdown<T>(content: T) -> Self
64    where
65        T: ToString,
66    {
67        Self {
68            kind: StringKind::Markdown,
69            content: content.to_string(),
70        }
71    }
72}
73
74impl<T> From<T> for Description
75where
76    T: ToString,
77{
78    fn from(value: T) -> Self {
79        Description::plain(value.to_string())
80    }
81}
82
83/// NestedBlock
84#[derive(Clone, PartialEq, Eq, Debug)]
85pub enum NestedBlock {
86    /// The nested block must appear exactly once
87    Single(Block),
88    /// The nested block can appear multiple times
89    List(Block),
90    /// The nested block can appear multiple times (unordered)
91    Set(Block),
92    /// The nested block can appear multiple times and have names
93    Map(Block),
94    /// The nested block can appear at most once (if not given, it will be populate with Nulls)
95    Group(Block),
96    /// The nested block can appear at most once (if not given, it will be null)
97    /// This is implemented with a list block, and must be serialized with `value::serde_as_vec`
98    /// ```
99    /// # use serde::{Serialize, Deserialize};
100    /// use tf_provider::value::{self, Value};
101    ///
102    /// #[derive(Serialize, Deserialize)]
103    /// struct MyBlock {
104    ///   /* ... */
105    /// }
106    ///
107    /// #[derive(Serialize, Deserialize)]
108    /// struct MyState {
109    ///   #[serde(with = "value::serde_as_vec")]
110    ///   my_block: Value<MyBlock>,
111    /// }
112    ///
113    /// ```
114    Optional(Block),
115}
116
117/// Block
118#[derive(Clone, PartialEq, Eq, Debug)]
119pub struct Block {
120    /// Version of the block
121    pub version: i64,
122    /// Attributes of the block
123    pub attributes: HashMap<String, Attribute>,
124    /// Nested blocks of the block
125    pub blocks: HashMap<String, NestedBlock>,
126    /// Description for the block
127    pub description: Description,
128    /// Is the block deprecated
129    pub deprecated: bool,
130}
131
132impl Default for Block {
133    /// Create an empty block (with a version of 1, and a description "empty")
134    fn default() -> Block {
135        Block {
136            version: 1,
137            attributes: Default::default(),
138            blocks: Default::default(),
139            description: "empty".into(),
140            deprecated: false,
141        }
142    }
143}
144
145fn cvt_nested_blocks_tf6(
146    blocks: &HashMap<String, NestedBlock>,
147) -> ::prost::alloc::vec::Vec<tfplugin6::schema::NestedBlock> {
148    use tfplugin6::schema::nested_block::NestingMode;
149    blocks
150        .iter()
151        .map(|(name, nested_block)| {
152            let (nesting_mode, block) = match nested_block {
153                NestedBlock::Single(block) => (NestingMode::Single, block),
154                NestedBlock::List(block) => (NestingMode::List, block),
155                NestedBlock::Set(block) => (NestingMode::Set, block),
156                NestedBlock::Map(block) => (NestingMode::Map, block),
157                NestedBlock::Group(block) => (NestingMode::Group, block),
158                NestedBlock::Optional(block) => (NestingMode::List, block),
159            };
160            let nitems = match nested_block {
161                NestedBlock::Single(_) => (1, 1),
162                NestedBlock::List(_) => (0, i64::MAX),
163                NestedBlock::Set(_) => (0, i64::MAX),
164                NestedBlock::Map(_) => (0, 0),
165                NestedBlock::Group(_) => (0, 0),
166                NestedBlock::Optional(_) => (0, 1),
167            };
168            tfplugin6::schema::NestedBlock {
169                type_name: name.clone(),
170                block: Some(block.into()),
171                nesting: nesting_mode as i32,
172                min_items: nitems.0,
173                max_items: nitems.1,
174            }
175        })
176        .collect()
177}
178
179#[allow(deprecated)]
180fn cvt_attributes_tf6(
181    attrs: &HashMap<String, Attribute>,
182) -> ::prost::alloc::vec::Vec<tfplugin6::schema::Attribute> {
183    use tfplugin6::schema::object::NestingMode;
184    use tfplugin6::schema::Object;
185    attrs
186        .iter()
187        .map(|(name, attr)| {
188            let attr_type = attr.attr_type.to_string().into();
189            let nested = match &attr.attr_type {
190                AttributeType::AttributeSingle(attrs) => Some((NestingMode::Single, attrs)),
191                AttributeType::AttributeList(attrs) => Some((NestingMode::List, attrs)),
192                AttributeType::AttributeSet(attrs) => Some((NestingMode::Set, attrs)),
193                AttributeType::AttributeMap(attrs) => Some((NestingMode::Map, attrs)),
194                _ => None,
195            }
196            .map(|(nesting_mode, attrs)| Object {
197                attributes: cvt_attributes_tf6(attrs),
198                nesting: nesting_mode as i32,
199                min_items: 0,
200                max_items: if nesting_mode == NestingMode::Single {
201                    1
202                } else {
203                    i64::MAX
204                },
205            });
206            tfplugin6::schema::Attribute {
207                name: name.clone(),
208                r#type: attr_type,
209                nested_type: nested,
210                description: attr.description.content.clone(),
211                required: attr.constraint == AttributeConstraint::Required,
212                optional: attr.constraint == AttributeConstraint::OptionalComputed
213                    || attr.constraint == AttributeConstraint::Optional,
214                computed: attr.constraint == AttributeConstraint::OptionalComputed
215                    || attr.constraint == AttributeConstraint::Computed,
216                sensitive: attr.sensitive,
217                description_kind: match attr.description.kind {
218                    StringKind::Plain => tfplugin6::StringKind::Plain,
219                    StringKind::Markdown => tfplugin6::StringKind::Markdown,
220                } as i32,
221                deprecated: attr.deprecated,
222            }
223        })
224        .collect()
225}
226
227impl From<&Block> for tfplugin6::schema::Block {
228    fn from(value: &Block) -> Self {
229        Self {
230            attributes: cvt_attributes_tf6(&value.attributes),
231            block_types: cvt_nested_blocks_tf6(&value.blocks),
232            version: value.version,
233            description: value.description.content.clone(),
234            description_kind: match value.description.kind {
235                StringKind::Plain => tfplugin6::StringKind::Plain,
236                StringKind::Markdown => tfplugin6::StringKind::Markdown,
237            } as i32,
238            deprecated: value.deprecated,
239        }
240    }
241}
242
243/// Specify the Attribute type
244#[derive(Clone, PartialEq, Eq, Debug)]
245pub enum AttributeType {
246    /// String
247    String,
248    /// Number (int or float)
249    Number,
250    /// Boolean
251    Bool,
252    /// List
253    List(Box<AttributeType>),
254    /// Set
255    Set(Box<AttributeType>),
256    /// Map
257    Map(Box<AttributeType>),
258    /// Object
259    Object(HashMap<String, AttributeType>),
260    /// Tuple
261    Tuple(Vec<AttributeType>),
262    /// Nested attributes
263    AttributeSingle(HashMap<String, Attribute>),
264    /// List of nested attributes
265    AttributeList(HashMap<String, Attribute>),
266    /// Set of nested attributes
267    AttributeSet(HashMap<String, Attribute>),
268    /// Map of nested attributes
269    AttributeMap(HashMap<String, Attribute>),
270    /// Dynamic (serialized into a json pair `[type, value]`)
271    Any,
272}
273
274impl Serialize for AttributeType {
275    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
276    where
277        S: serde::Serializer,
278    {
279        struct AttributesAsType<'a>(&'a HashMap<String, Attribute>);
280        impl<'a> Serialize for AttributesAsType<'a> {
281            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
282            where
283                S: serde::Serializer,
284            {
285                let mut map = serializer.serialize_map(Some(self.0.len()))?;
286                for (name, attr) in self.0 {
287                    map.serialize_entry(name, &attr.attr_type)?;
288                }
289                map.end()
290            }
291        }
292        match self {
293            AttributeType::String => serializer.serialize_str("string"),
294            AttributeType::Number => serializer.serialize_str("number"),
295            AttributeType::Bool => serializer.serialize_str("bool"),
296            AttributeType::List(attr) => ("list", attr).serialize(serializer),
297            AttributeType::Set(attr) => ("set", attr).serialize(serializer),
298            AttributeType::Map(attr) => ("map", attr).serialize(serializer),
299            AttributeType::Object(attrs) => ("object", attrs).serialize(serializer),
300            AttributeType::Tuple(attrs) => ("tuple", attrs).serialize(serializer),
301            AttributeType::AttributeSingle(attrs) => {
302                ("object", &AttributesAsType(attrs)).serialize(serializer)
303            }
304            AttributeType::AttributeList(attrs) => {
305                ("list", ("object", &AttributesAsType(attrs))).serialize(serializer)
306            }
307            AttributeType::AttributeSet(attrs) => {
308                ("set", ("object", &AttributesAsType(attrs))).serialize(serializer)
309            }
310            AttributeType::AttributeMap(attrs) => {
311                ("map", ("object", &AttributesAsType(attrs))).serialize(serializer)
312            }
313            AttributeType::Any => serializer.serialize_str("dynamic"),
314        }
315    }
316}
317
318impl Display for AttributeType {
319    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
320        return f.write_str(
321            serde_json::to_string(self)
322                .or(Err(std::fmt::Error))?
323                .as_str(),
324        );
325    }
326}
327
328/// Specify the Attribute type
329#[derive(Clone, PartialEq, Eq, Hash, Debug)]
330pub enum AttributeConstraint {
331    /// The attribute is computed, but cannot be specified by the practitioner (output only)
332    Computed,
333    /// The attribute is optional, and the provider cannot generate a value for it
334    Optional,
335    /// The attribute is both optional and computed:
336    /// the practitioner can omit the value, and the provider can generate a value for it
337    OptionalComputed,
338    /// The attribute is required
339    Required,
340}
341
342/// Attribute
343#[derive(Clone, PartialEq, Eq, Debug)]
344pub struct Attribute {
345    /// Type of the Attribute
346    pub attr_type: AttributeType,
347    /// Description of the Attribute
348    pub description: Description,
349    /// Is the attribute required
350    pub constraint: AttributeConstraint,
351    /// Is the attribute sensitive
352    pub sensitive: bool,
353    /// Is the attribute deprecated
354    pub deprecated: bool,
355}
356
357impl Default for Attribute {
358    fn default() -> Self {
359        Self {
360            attr_type: AttributeType::Any,
361            description: "empty".into(),
362            constraint: AttributeConstraint::OptionalComputed,
363            sensitive: false,
364            deprecated: false,
365        }
366    }
367}
368
369/// Schema
370#[derive(Clone, PartialEq, Eq, Debug)]
371pub struct Schema {
372    /// Version of the schema
373    pub version: i64,
374    /// Root block of the schema
375    pub block: Block,
376}
377
378impl From<&Schema> for tfplugin6::Schema {
379    fn from(value: &Schema) -> Self {
380        Self {
381            version: value.version,
382            block: Some((&value.block).into()),
383        }
384    }
385}
386
387/// Type for a function parameter or return value
388#[derive(Clone, PartialEq, Eq, Debug)]
389pub enum Type {
390    /// String
391    String,
392    /// Number (int or float)
393    Number,
394    /// Boolean
395    Bool,
396    /// List
397    List(Box<Type>),
398    /// Set
399    Set(Box<Type>),
400    /// Map
401    Map(Box<Type>),
402    /// Object
403    Object(HashMap<String, Type>),
404    /// Tuple
405    Tuple(Vec<Type>),
406    /// Dynamic (serialized into a json pair `[type, value]`)
407    Any,
408}
409
410impl Serialize for Type {
411    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
412    where
413        S: serde::Serializer,
414    {
415        match self {
416            Type::String => serializer.serialize_str("string"),
417            Type::Number => serializer.serialize_str("number"),
418            Type::Bool => serializer.serialize_str("bool"),
419            Type::List(attr) => ("list", attr).serialize(serializer),
420            Type::Set(attr) => ("set", attr).serialize(serializer),
421            Type::Map(attr) => ("map", attr).serialize(serializer),
422            Type::Object(attrs) => ("object", attrs).serialize(serializer),
423            Type::Tuple(attrs) => ("tuple", attrs).serialize(serializer),
424            Type::Any => serializer.serialize_str("dynamic"),
425        }
426    }
427}
428
429impl Display for Type {
430    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
431        return f.write_str(
432            serde_json::to_string(self)
433                .or(Err(std::fmt::Error))?
434                .as_str(),
435        );
436    }
437}
438
439/// Function parameter schema
440#[derive(Clone, PartialEq, Eq, Debug)]
441pub struct Parameter {
442    /// Name of the parameter
443    pub name: String,
444    /// Type of the parameter
445    pub param_type: Type,
446    /// Whether the parameter can be null or not
447    ///
448    /// When enabled denotes that a null argument value
449    /// can be passed to the provider. When disabled,
450    /// Terraform returns an error if the argument value is null.
451    pub allow_null: bool,
452    /// Whether the parameter can be unknown or not
453    ///
454    /// When enabled denotes that only wholly known
455    /// argument values will be passed to the provider. When disabled,
456    /// Terraform skips the function call entirely and assumes an unknown
457    /// value result from the function.
458    pub allow_unknown: bool,
459    /// Description of the argument
460    pub description: Description,
461}
462
463impl Default for Parameter {
464    fn default() -> Self {
465        Self {
466            name: Default::default(),
467            param_type: Type::Any,
468            allow_null: Default::default(),
469            allow_unknown: Default::default(),
470            description: Description::plain(""),
471        }
472    }
473}
474
475impl From<&Parameter> for tfplugin6::function::Parameter {
476    fn from(value: &Parameter) -> Self {
477        Self {
478            name: value.name.clone(),
479            r#type: value.param_type.to_string().into(),
480            allow_null_value: value.allow_null,
481            allow_unknown_values: value.allow_unknown,
482            description: value.description.content.clone(),
483            description_kind: match value.description.kind {
484                StringKind::Markdown => tfplugin6::StringKind::Markdown,
485                StringKind::Plain => tfplugin6::StringKind::Plain,
486            } as i32,
487        }
488    }
489}
490
491/// Function schema
492#[derive(Debug, Clone, PartialEq, Eq)]
493pub struct FunctionSchema {
494    /// List of positional function parameters
495    pub parameters: Vec<Parameter>,
496    /// Optional final parameter which accepts
497    /// zero or more argument values, in which Terraform will send an
498    /// ordered list of the parameter type
499    pub variadic: Option<Parameter>,
500    /// Type constraint for the function result
501    pub return_type: Type,
502    /// Human-readable shortened documentation for the function
503    pub summary: String,
504    /// Description of the function
505    pub description: Description,
506    /// Whether the function deprecated
507    ///
508    /// If the function is deprecated, this field contains the deprecation message
509    pub deprecated: Option<String>,
510}
511
512impl Default for FunctionSchema {
513    fn default() -> Self {
514        Self {
515            parameters: Default::default(),
516            variadic: Default::default(),
517            return_type: Type::Any,
518            summary: Default::default(),
519            description: Description::plain(""),
520            deprecated: Default::default(),
521        }
522    }
523}
524
525impl From<&FunctionSchema> for tfplugin6::Function {
526    fn from(value: &FunctionSchema) -> Self {
527        Self {
528            parameters: value.parameters.iter().map(Into::into).collect(),
529            variadic_parameter: value.variadic.as_ref().map(Into::into),
530            r#return: Some(tfplugin6::function::Return {
531                r#type: value.return_type.to_string().into_bytes(),
532            }),
533            summary: value.summary.clone(),
534            description: value.description.content.clone(),
535            description_kind: match value.description.kind {
536                StringKind::Markdown => tfplugin6::StringKind::Markdown,
537                StringKind::Plain => tfplugin6::StringKind::Plain,
538            } as i32,
539            deprecation_message: match &value.deprecated {
540                Some(msg) if msg.is_empty() => "deprecated".to_owned(),
541                Some(msg) => msg.clone(),
542                None => String::new(),
543            },
544        }
545    }
546}