From b50fa51e9cc7f3cfa38472fbdbdc0610877f46d8 Mon Sep 17 00:00:00 2001 From: Nilstrieb <48135649+Nilstrieb@users.noreply.github.com> Date: Wed, 29 May 2024 20:20:36 +0200 Subject: [PATCH] Support nested object --- .../src/resources/class_resource.rs | 59 +++++++---------- .../src/resources/hugo.rs | 9 +-- .../src/resources/kitty.rs | 65 ++++++++++--------- terraform-provider-corsschool/test/main.tf | 12 +++- terustform-macros/src/lib.rs | 2 +- terustform/src/lib.rs | 33 ++++++++-- terustform/src/schema.rs | 43 +++++++----- terustform/src/server/convert.rs | 16 ++++- 8 files changed, 143 insertions(+), 96 deletions(-) diff --git a/terraform-provider-corsschool/src/resources/class_resource.rs b/terraform-provider-corsschool/src/resources/class_resource.rs index 6bc199f..76f37a8 100644 --- a/terraform-provider-corsschool/src/resources/class_resource.rs +++ b/terraform-provider-corsschool/src/resources/class_resource.rs @@ -1,5 +1,3 @@ -use std::collections::HashMap; - use terustform::{ resource::Resource, AttrPath, Attribute, DResult, EyreExt, Mode, Schema, Value, ValueModel, }; @@ -78,41 +76,28 @@ impl Resource for ClassResource { fn schema() -> terustform::Schema { Schema { description: "A class".into(), - attributes: HashMap::from([ - ( - "id".to_owned(), - // TODO: UUID validation :3 - Attribute::String { - description: "The UUID".to_owned(), - mode: Mode::Computed, - sensitive: false, - }, - ), - ( - "name".to_owned(), - Attribute::String { - description: "The description".to_owned(), - mode: Mode::Required, - sensitive: false, - }, - ), - ( - "description".to_owned(), - Attribute::String { - description: "The description".to_owned(), - mode: Mode::Required, - sensitive: false, - }, - ), - ( - "discord_id".to_owned(), - Attribute::String { - description: "The discord ID of the class".to_owned(), - mode: Mode::Optional, - sensitive: false, - }, - ), - ]), + attributes: terustform::attrs! { + "id" => Attribute::String { + description: "The UUID".to_owned(), + mode: Mode::Computed, + sensitive: false, + }, + "name" => Attribute::String { + description: "The description".to_owned(), + mode: Mode::Required, + sensitive: false, + }, + "description" => Attribute::String { + description: "The description".to_owned(), + mode: Mode::Required, + sensitive: false, + }, + "discord_id" => Attribute::String { + description: "The discord ID of the class".to_owned(), + mode: Mode::Optional, + sensitive: false, + }, + }, } } diff --git a/terraform-provider-corsschool/src/resources/hugo.rs b/terraform-provider-corsschool/src/resources/hugo.rs index 5a0f02d..39af70a 100644 --- a/terraform-provider-corsschool/src/resources/hugo.rs +++ b/terraform-provider-corsschool/src/resources/hugo.rs @@ -1,5 +1,3 @@ -use std::collections::HashMap; - use eyre::Context; use terustform::{ datasource::DataSource, Attribute, DResult, EyreExt, Mode, Schema, StringValue, Value, @@ -41,14 +39,13 @@ impl DataSource for HugoDataSource { fn schema() -> Schema { Schema { description: "Get Hugo Boss".to_owned(), - attributes: HashMap::from([( - "hugo".to_owned(), - Attribute::String { + attributes: terustform::attrs! { + "hugo" => Attribute::String { description: "Hugo Boss".to_owned(), mode: Mode::Computed, sensitive: false, }, - )]), + }, } } diff --git a/terraform-provider-corsschool/src/resources/kitty.rs b/terraform-provider-corsschool/src/resources/kitty.rs index 423efa6..a9123f4 100644 --- a/terraform-provider-corsschool/src/resources/kitty.rs +++ b/terraform-provider-corsschool/src/resources/kitty.rs @@ -1,5 +1,3 @@ -use std::collections::HashMap; - use terustform::{ datasource::DataSource, AttrPath, Attribute, DResult, Mode, Schema, StringValue, Value, ValueModel, @@ -13,7 +11,13 @@ pub struct ExampleDataSource {} struct ExampleDataSourceModel { name: StringValue, meow: StringValue, - id: StringValue, + paws: ExampleDataSourceModelPaws, +} + +#[derive(terustform::Model)] +struct ExampleDataSourceModelPaws { + left: StringValue, + right: StringValue, } impl DataSource for ExampleDataSource { @@ -26,32 +30,35 @@ impl DataSource for ExampleDataSource { fn schema() -> Schema { Schema { description: "an example".to_owned(), - attributes: HashMap::from([ - ( - "name".to_owned(), - Attribute::String { - description: "a cool name".to_owned(), - mode: Mode::Required, - sensitive: false, + attributes: terustform::attrs! { + "name" => Attribute::String { + description: "a cool name".to_owned(), + mode: Mode::Required, + sensitive: false, + }, + "meow" => Attribute::String { + description: "the meow of the cat".to_owned(), + mode: Mode::Computed, + sensitive: false, + }, + "paws" => Attribute::Object { + description: "the ID of the meowy cat".to_owned(), + mode: Mode::Required, + sensitive: false, + attrs: terustform::attrs! { + "left" => Attribute::String { + description: "meow".to_owned(), + mode: Mode::Required, + sensitive: false, + }, + "right" => Attribute::String { + description: "meow".to_owned(), + mode: Mode::Required, + sensitive: false, + }, }, - ), - ( - "meow".to_owned(), - Attribute::String { - description: "the meow of the cat".to_owned(), - mode: Mode::Computed, - sensitive: false, - }, - ), - ( - "id".to_owned(), - Attribute::String { - description: "the ID of the meowy cat".to_owned(), - mode: Mode::Computed, - sensitive: false, - }, - ), - ]), + }, + }, } } @@ -67,7 +74,7 @@ impl DataSource for ExampleDataSource { let meow = format!("mrrrrr i am {name_str}"); model.meow = StringValue::Known(meow); - model.id = StringValue::Known("0".to_owned()); + model.paws.right = StringValue::Known("O".to_owned()); Ok(model.to_value()) } diff --git a/terraform-provider-corsschool/test/main.tf b/terraform-provider-corsschool/test/main.tf index 1622047..4cd4a5e 100644 --- a/terraform-provider-corsschool/test/main.tf +++ b/terraform-provider-corsschool/test/main.tf @@ -19,8 +19,18 @@ data "corsschool_class" "test" { output "class" { value = data.corsschool_class.test } - +/* resource "corsschool_class" "myclass" { name = "meow" description = "???" +}*/ +data "corsschool_kitty" "name" { + name = "a" + paws = { + left = "x" + right = "y" + } +} +output "kitty_paw" { + value = data.corsschool_kitty.name.paws.right } \ No newline at end of file diff --git a/terustform-macros/src/lib.rs b/terustform-macros/src/lib.rs index 631fc7d..7fdb085 100644 --- a/terustform-macros/src/lib.rs +++ b/terustform-macros/src/lib.rs @@ -50,7 +50,7 @@ fn data_source_model_inner( let #tf::Some(#name) = obj.remove(#name_str) else { return #tf::Err( #tf::Diagnostics::from(#tf::Diagnostic::error_string( - format!("Expected property '{}', which was not present", #name_str), + format!("Expected property '{}' when deserializing value, which was not present in the value", #name_str), ).with_path(path.clone())) ); }; diff --git a/terustform/src/lib.rs b/terustform/src/lib.rs index da72a92..40228d1 100644 --- a/terustform/src/lib.rs +++ b/terustform/src/lib.rs @@ -29,9 +29,12 @@ use provider::Provider; pub async fn start(provider: P) -> eyre::Result<()> { tracing_subscriber::fmt() - .with_env_filter(EnvFilter::builder().parse_lossy( - std::env::var("RUST_LOG").unwrap_or_else(|_| "h2=info,rustls=info,hyper_util=info,debug".into()), - )) + .with_env_filter( + EnvFilter::builder().parse_lossy( + std::env::var("RUST_LOG") + .unwrap_or_else(|_| "h2=info,rustls=info,hyper_util=info,debug".into()), + ), + ) .with_writer(std::io::stderr) .without_time() .init(); @@ -39,6 +42,28 @@ pub async fn start(provider: P) -> eyre::Result<()> { server::serve(provider).await } +/// ```rust +/// # use std::collections::HashMap; +/// let x: HashMap = terustform::attrs! { +/// "hello" => 0, +/// }; +/// ``` +#[macro_export] +macro_rules! attrs { + ( + $( $name:literal => $rhs:expr ,)* + ) => { + <$crate::__derive_private::HashMap<_, _> as $crate::__derive_private::FromIterator<(_, _)>>::from_iter([ + $( + ( + $name.into(), + $rhs, + ), + )* + ]) + }; +} + /// Private, only for use for with the derive macro. #[doc(hidden)] pub mod __derive_private { @@ -46,7 +71,7 @@ pub mod __derive_private { AttrPath, AttrPathSegment, BaseValue, DResult, Diagnostic, Diagnostics, Value, ValueKind, ValueModel, }; - pub use {Clone, Option::Some, Result::Err, ToOwned}; + pub use {std::collections::HashMap, Clone, FromIterator, Option::Some, Result::Err, ToOwned}; pub fn new_object(elems: [(&str, Value); N]) -> Value { Value::Known(ValueKind::Object(std::collections::BTreeMap::from_iter( diff --git a/terustform/src/schema.rs b/terustform/src/schema.rs index 8d2dc0a..79518a6 100644 --- a/terustform/src/schema.rs +++ b/terustform/src/schema.rs @@ -20,6 +20,12 @@ pub enum Attribute { mode: Mode, sensitive: bool, }, + Object { + description: String, + mode: Mode, + sensitive: bool, + attrs: HashMap, + }, } #[derive(Clone, Copy)] @@ -35,6 +41,7 @@ impl Attribute { match *self { Self::Int64 { mode, .. } => mode, Self::String { mode, .. } => mode, + Self::Object { mode, .. } => mode, } } } @@ -55,22 +62,28 @@ impl Mode { impl Schema { pub fn typ(&self) -> Type { - let attrs = self - .attributes - .iter() - .map(|(name, attr)| { - let attr_type = match attr { - Attribute::Int64 { .. } => Type::Number, - Attribute::String { .. } => Type::String, - }; + attrs_typ(&self.attributes) + } +} - (name.clone(), attr_type) - }) - .collect(); - - Type::Object { - attrs, - optionals: vec![], +impl Attribute { + pub fn typ(&self) -> Type { + match self { + Attribute::Int64 { .. } => Type::Number, + Attribute::String { .. } => Type::String, + Attribute::Object { attrs, .. } => attrs_typ(attrs), } } } + +fn attrs_typ(attrs: &HashMap) -> Type { + let attrs = attrs + .iter() + .map(|(name, attr)| (name.clone(), attr.typ())) + .collect(); + + Type::Object { + attrs, + optionals: vec![], + } +} diff --git a/terustform/src/server/convert.rs b/terustform/src/server/convert.rs index 9f7055f..83f8f45 100644 --- a/terustform/src/server/convert.rs +++ b/terustform/src/server/convert.rs @@ -1,4 +1,4 @@ -use crate::{values::Type, AttrPathSegment, Attribute, Diagnostics, Mode, Schema, Value}; +use crate::{AttrPathSegment, Attribute, Diagnostics, Mode, Schema, Value}; use super::grpc::tfplugin6; @@ -43,13 +43,14 @@ impl Attribute { attr.computed = mode.computed(); }; + attr.r#type = self.typ().to_json().into_bytes(); + match self { Attribute::String { description, mode, sensitive, } => { - attr.r#type = Type::String.to_json().into_bytes(); attr.description = description; set_modes(&mut attr, mode); attr.sensitive = sensitive; @@ -59,7 +60,16 @@ impl Attribute { mode, sensitive, } => { - attr.r#type = Type::Number.to_json().into_bytes(); + attr.description = description; + set_modes(&mut attr, mode); + attr.sensitive = sensitive; + } + Attribute::Object { + description, + mode, + sensitive, + attrs: _, + } => { attr.description = description; set_modes(&mut attr, mode); attr.sensitive = sensitive;