mirror of
https://github.com/Noratrieb/terustform.git
synced 2026-01-14 16:35:11 +01:00
implement optional attrs
This commit is contained in:
parent
b50fa51e9c
commit
bf7835162d
5 changed files with 135 additions and 56 deletions
|
|
@ -53,7 +53,7 @@ impl DataSource for ExampleDataSource {
|
||||||
},
|
},
|
||||||
"right" => Attribute::String {
|
"right" => Attribute::String {
|
||||||
description: "meow".to_owned(),
|
description: "meow".to_owned(),
|
||||||
mode: Mode::Required,
|
mode: Mode::Optional,
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -42,10 +42,11 @@ pub async fn start<P: Provider>(provider: P) -> eyre::Result<()> {
|
||||||
server::serve(provider).await
|
server::serve(provider).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ```rust
|
/// ```rust,no_run
|
||||||
/// # use std::collections::HashMap;
|
/// # use std::collections::HashMap;
|
||||||
/// let x: HashMap<String, u8> = terustform::attrs! {
|
/// # let attr: terustform::Attribute = todo!();
|
||||||
/// "hello" => 0,
|
/// let x: HashMap<String, terustform::Attribute> = terustform::attrs! {
|
||||||
|
/// "hello" => attr,
|
||||||
/// };
|
/// };
|
||||||
/// ```
|
/// ```
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
|
|
@ -53,7 +54,7 @@ macro_rules! attrs {
|
||||||
(
|
(
|
||||||
$( $name:literal => $rhs:expr ,)*
|
$( $name:literal => $rhs:expr ,)*
|
||||||
) => {
|
) => {
|
||||||
<$crate::__derive_private::HashMap<_, _> as $crate::__derive_private::FromIterator<(_, _)>>::from_iter([
|
$crate::__derive_private::FromIterator::from_iter([
|
||||||
$(
|
$(
|
||||||
(
|
(
|
||||||
$name.into(),
|
$name.into(),
|
||||||
|
|
@ -69,7 +70,7 @@ macro_rules! attrs {
|
||||||
pub mod __derive_private {
|
pub mod __derive_private {
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
AttrPath, AttrPathSegment, BaseValue, DResult, Diagnostic, Diagnostics, Value, ValueKind,
|
AttrPath, AttrPathSegment, BaseValue, DResult, Diagnostic, Diagnostics, Value, ValueKind,
|
||||||
ValueModel,
|
ValueModel, Attribute,
|
||||||
};
|
};
|
||||||
pub use {std::collections::HashMap, Clone, FromIterator, Option::Some, Result::Err, ToOwned};
|
pub use {std::collections::HashMap, Clone, FromIterator, Option::Some, Result::Err, ToOwned};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -77,13 +77,24 @@ impl Attribute {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn attrs_typ(attrs: &HashMap<String, Attribute>) -> Type {
|
fn attrs_typ(attrs: &HashMap<String, Attribute>) -> Type {
|
||||||
let attrs = attrs
|
let attr_tys = attrs
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(name, attr)| (name.clone(), attr.typ()))
|
.map(|(name, attr)| (name.clone(), attr.typ()))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
let optionals = attrs
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(name, attr)| {
|
||||||
|
if attr.mode().optional() {
|
||||||
|
Some(name.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
Type::Object {
|
Type::Object {
|
||||||
attrs,
|
attrs: attr_tys,
|
||||||
optionals: vec![],
|
optionals,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{AttrPathSegment, Attribute, Diagnostics, Mode, Schema, Value};
|
use crate::{AttrPath, AttrPathSegment, Attribute, Diagnostics, Mode, Schema, Value};
|
||||||
|
|
||||||
use super::grpc::tfplugin6;
|
use super::grpc::tfplugin6;
|
||||||
|
|
||||||
|
|
@ -88,9 +88,16 @@ impl Diagnostics {
|
||||||
severity: tfplugin6::diagnostic::Severity::Error as _,
|
severity: tfplugin6::diagnostic::Severity::Error as _,
|
||||||
summary: err.msg,
|
summary: err.msg,
|
||||||
detail: "".to_owned(),
|
detail: "".to_owned(),
|
||||||
attribute: err.attr.map(|attr| -> tfplugin6::AttributePath {
|
attribute: err.attr.map(|path| path.into_tfplugin()),
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AttrPath {
|
||||||
|
pub(crate) fn into_tfplugin(self) -> tfplugin6::AttributePath {
|
||||||
tfplugin6::AttributePath {
|
tfplugin6::AttributePath {
|
||||||
steps: attr
|
steps: self
|
||||||
.0
|
.0
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|segment| {
|
.map(|segment| {
|
||||||
|
|
@ -98,23 +105,16 @@ impl Diagnostics {
|
||||||
|
|
||||||
tfplugin6::attribute_path::Step {
|
tfplugin6::attribute_path::Step {
|
||||||
selector: Some(match segment {
|
selector: Some(match segment {
|
||||||
AttrPathSegment::AttributeName(name) => {
|
AttrPathSegment::AttributeName(name) => Selector::AttributeName(name),
|
||||||
Selector::AttributeName(name)
|
|
||||||
}
|
|
||||||
AttrPathSegment::ElementKeyString(key) => {
|
AttrPathSegment::ElementKeyString(key) => {
|
||||||
Selector::ElementKeyString(key)
|
Selector::ElementKeyString(key)
|
||||||
}
|
}
|
||||||
AttrPathSegment::ElementKeyInt(key) => {
|
AttrPathSegment::ElementKeyInt(key) => Selector::ElementKeyInt(key),
|
||||||
Selector::ElementKeyInt(key)
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
}
|
}
|
||||||
}),
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
// tftypes for types and values
|
// tftypes for types and values
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::{BTreeMap, HashMap},
|
collections::BTreeMap,
|
||||||
io::{self, Read},
|
io::{self, Read},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -29,7 +29,7 @@ pub enum Type {
|
||||||
/// A bunch of unordered string-value pairs of different types.
|
/// A bunch of unordered string-value pairs of different types.
|
||||||
/// The attributes are statically known.
|
/// The attributes are statically known.
|
||||||
Object {
|
Object {
|
||||||
attrs: HashMap<String, Type>,
|
attrs: BTreeMap<String, Type>,
|
||||||
/// The attributes in `attrs` that are optional.
|
/// The attributes in `attrs` that are optional.
|
||||||
/// Always empty for now because of JSON reasons.
|
/// Always empty for now because of JSON reasons.
|
||||||
optionals: Vec<String>,
|
optionals: Vec<String>,
|
||||||
|
|
@ -41,6 +41,8 @@ pub enum Type {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Type {
|
impl Type {
|
||||||
|
// tftypes/type.go
|
||||||
|
// https://github.com/hashicorp/terraform-plugin-go/blob/05dc75aefa5b71406022d0ac08eca99f44fbf378/tftypes/type.go#L95
|
||||||
pub fn to_json(&self) -> String {
|
pub fn to_json(&self) -> String {
|
||||||
let value = self.to_json_inner();
|
let value = self.to_json_inner();
|
||||||
serde_json::to_string(&value).unwrap()
|
serde_json::to_string(&value).unwrap()
|
||||||
|
|
@ -59,18 +61,25 @@ impl Type {
|
||||||
Self::List { elem } => compound("list", elem.to_json_inner()),
|
Self::List { elem } => compound("list", elem.to_json_inner()),
|
||||||
Self::Map { elem } => compound("map", elem.to_json_inner()),
|
Self::Map { elem } => compound("map", elem.to_json_inner()),
|
||||||
Self::Set { elem } => compound("set", elem.to_json_inner()),
|
Self::Set { elem } => compound("set", elem.to_json_inner()),
|
||||||
Self::Object {
|
Self::Object { attrs, optionals } => {
|
||||||
attrs,
|
let mut parts = vec![
|
||||||
optionals: _,
|
Value::String("object".to_owned()),
|
||||||
} => compound(
|
|
||||||
"object",
|
|
||||||
Value::Object(
|
Value::Object(
|
||||||
attrs
|
attrs
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(k, v)| (k.clone(), v.to_json_inner()))
|
.map(|(k, v)| (k.clone(), v.to_json_inner()))
|
||||||
.collect(),
|
.collect(),
|
||||||
),
|
),
|
||||||
),
|
];
|
||||||
|
|
||||||
|
if !optionals.is_empty() {
|
||||||
|
parts.push(Value::Array(
|
||||||
|
optionals.iter().map(|v| Value::String(v.clone())).collect(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
Value::Array(parts)
|
||||||
|
}
|
||||||
Self::Tuple { elems } => compound(
|
Self::Tuple { elems } => compound(
|
||||||
"tuple",
|
"tuple",
|
||||||
elems.iter().map(|elem| elem.to_json_inner()).collect(),
|
elems.iter().map(|elem| elem.to_json_inner()).collect(),
|
||||||
|
|
@ -356,9 +365,7 @@ impl Value {
|
||||||
ValueKind::Set(elems)
|
ValueKind::Set(elems)
|
||||||
}
|
}
|
||||||
Type::Object { attrs, optionals } => {
|
Type::Object { attrs, optionals } => {
|
||||||
assert!(optionals.is_empty());
|
|
||||||
let len = mp::read_map_len(rd)?;
|
let len = mp::read_map_len(rd)?;
|
||||||
dbg!(len);
|
|
||||||
|
|
||||||
if attrs.len() != (len as usize) {
|
if attrs.len() != (len as usize) {
|
||||||
return Err(Diagnostic::error_string(format!(
|
return Err(Diagnostic::error_string(format!(
|
||||||
|
|
@ -370,16 +377,21 @@ impl Value {
|
||||||
let elems = (0..len)
|
let elems = (0..len)
|
||||||
.map(|_| -> DResult<_> {
|
.map(|_| -> DResult<_> {
|
||||||
let key = read_string(rd)?;
|
let key = read_string(rd)?;
|
||||||
dbg!(&key);
|
|
||||||
let typ = attrs.get(&key).ok_or_else(|| {
|
let typ = attrs.get(&key).ok_or_else(|| {
|
||||||
Diagnostic::error_string(format!("unexpected attribute: '{key}'"))
|
Diagnostic::error_string(format!("unexpected attribute: '{key}'"))
|
||||||
})?;
|
})?;
|
||||||
dbg!(typ);
|
|
||||||
let value = Value::msg_unpack_inner(rd, typ)?;
|
let value = Value::msg_unpack_inner(rd, typ)?;
|
||||||
dbg!(&value);
|
|
||||||
Ok((key, value))
|
Ok((key, value))
|
||||||
})
|
})
|
||||||
.collect::<DResult<BTreeMap<_, _>>>()?;
|
.collect::<DResult<BTreeMap<_, _>>>()?;
|
||||||
|
|
||||||
|
for expected_attr in attrs.keys() {
|
||||||
|
let is_ok = elems.contains_key(expected_attr);
|
||||||
|
if !is_ok && !optionals.contains(expected_attr) {
|
||||||
|
return Err(Diagnostic::error_string(format!("expected attribute '{expected_attr}', but it was not present")).into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ValueKind::Object(elems)
|
ValueKind::Object(elems)
|
||||||
}
|
}
|
||||||
Type::Tuple { elems } => {
|
Type::Tuple { elems } => {
|
||||||
|
|
@ -406,14 +418,69 @@ impl Value {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use std::collections::{BTreeMap, HashMap};
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use crate::{Type, Value, ValueKind};
|
use crate::{Type, Value, ValueKind};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn type_json() {
|
||||||
|
let typs = [
|
||||||
|
(Type::Bool, "\"bool\""),
|
||||||
|
(Type::Number, "\"number\""),
|
||||||
|
(Type::String, "\"string\""),
|
||||||
|
(Type::Dynamic, "\"dynamic\""),
|
||||||
|
(
|
||||||
|
Type::List {
|
||||||
|
elem: Box::new(Type::String),
|
||||||
|
},
|
||||||
|
r#"["list","string"]"#,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Type::Map {
|
||||||
|
elem: Box::new(Type::String),
|
||||||
|
},
|
||||||
|
r#"["map","string"]"#,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Type::Set {
|
||||||
|
elem: Box::new(Type::String),
|
||||||
|
},
|
||||||
|
r#"["set","string"]"#,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Type::Object {
|
||||||
|
attrs: crate::attrs! {
|
||||||
|
"meow" => Type::String,
|
||||||
|
"mrooow" => Type::String,
|
||||||
|
"uwu" => Type::String,
|
||||||
|
},
|
||||||
|
optionals: vec![],
|
||||||
|
},
|
||||||
|
r#"["object",{"meow":"string","mrooow":"string","uwu":"string"}]"#,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Type::Object {
|
||||||
|
attrs: crate::attrs! {
|
||||||
|
"meow" => Type::String,
|
||||||
|
"mrooow" => Type::String,
|
||||||
|
"uwu" => Type::String,
|
||||||
|
},
|
||||||
|
optionals: vec!["uwu".to_owned()],
|
||||||
|
},
|
||||||
|
r#"["object",{"meow":"string","mrooow":"string","uwu":"string"},["uwu"]]"#,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (typ, expected) in typs {
|
||||||
|
let actual_str = typ.to_json();
|
||||||
|
assert_eq!(actual_str, expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn decode_object() {
|
fn decode_object() {
|
||||||
let typ = Type::Object {
|
let typ = Type::Object {
|
||||||
attrs: HashMap::from([
|
attrs: BTreeMap::from([
|
||||||
("id".into(), Type::String),
|
("id".into(), Type::String),
|
||||||
("discord_id".into(), Type::String),
|
("discord_id".into(), Type::String),
|
||||||
("name".into(), Type::String),
|
("name".into(), Type::String),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue