diff --git a/Cargo.lock b/Cargo.lock index 92bfd2b..8fdbfac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -950,6 +950,12 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" +[[package]] +name = "ryu" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + [[package]] name = "scopeguard" version = "1.2.0" @@ -976,6 +982,17 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.115" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -1070,6 +1087,8 @@ dependencies = [ "rcgen", "rmp", "rustls 0.23.4", + "serde", + "serde_json", "tempfile", "time", "tokio", diff --git a/Cargo.toml b/Cargo.toml index 3c736ef..872c15f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,8 @@ prost = "0.12.4" rcgen = "0.13.1" rmp = "0.8.12" rustls = { version = "0.23.4", default-features = false, features = ["ring", "logging", "std", "tls12"] } +serde = "1.0.197" +serde_json = "1.0.115" tempfile = "3.10.1" time = "0.3.35" tokio = { version = "1.37.0", features = ["full"] } diff --git a/src/example.rs b/src/example.rs index a44922b..6a2b36a 100644 --- a/src/example.rs +++ b/src/example.rs @@ -6,7 +6,7 @@ use crate::{ provider::Provider, DResult, }, - values::Value, + values::{Value, ValueKind}, }; pub struct ExampleProvider {} @@ -61,16 +61,22 @@ impl DataSource for ExampleDataSource { } fn read(&self, config: Value) -> DResult { - Ok(Value::Object(BTreeMap::from([ + Ok(Value::Known(ValueKind::Object(BTreeMap::from([ ( "name".to_owned(), match config { - Value::Object(mut obj) => obj.remove("name").unwrap(), + Value::Known(ValueKind::Object(mut obj)) => obj.remove("name").unwrap(), _ => unreachable!(), }, ), - ("meow".to_owned(), Value::String("mrrrrr".to_owned())), - ("id".to_owned(), Value::String("0".to_owned())), - ]))) + ( + "meow".to_owned(), + Value::Known(ValueKind::String("mrrrrr".to_owned())), + ), + ( + "id".to_owned(), + Value::Known(ValueKind::String("0".to_owned())), + ), + ])))) } } diff --git a/src/framework/datasource.rs b/src/framework/datasource.rs index 798497a..6261339 100644 --- a/src/framework/datasource.rs +++ b/src/framework/datasource.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use crate::values::Value; +use crate::values::{Type, Value}; use super::DResult; @@ -56,4 +56,26 @@ impl Mode { pub fn computed(&self) -> bool { matches!(self, Self::OptionalComputed | Self::Computed) } -} \ No newline at end of file +} + +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, + }; + + (name.clone(), attr_type) + }) + .collect(); + + Type::Object { + attrs, + optionals: vec![], + } + } +} diff --git a/src/framework/mod.rs b/src/framework/mod.rs index 425f5e9..2c7c9d7 100644 --- a/src/framework/mod.rs +++ b/src/framework/mod.rs @@ -10,3 +10,17 @@ pub struct Diagnostics { } pub type DResult = Result; + +impl Diagnostics { + pub fn error_string(msg: String) -> Self { + Self { + errors: vec![msg], + } + } +} + +impl From for Diagnostics { + fn from(value: E) -> Self { + Self::error_string(format!("{:?}", value)) + } +} diff --git a/src/server/grpc.rs b/src/server/grpc.rs index 1fdec24..5564936 100644 --- a/src/server/grpc.rs +++ b/src/server/grpc.rs @@ -237,10 +237,32 @@ impl Provider for super::ProviderHandler { .get(&request.get_ref().type_name) .unwrap(); - let state = ds.read(crate::values::Value::Object(BTreeMap::from([( - "name".to_owned(), - crate::values::Value::String("mykitten".to_owned()), - )]))); + let typ = ds.schema().typ(); + let config = match &request.get_ref().config { + None => crate::values::Value::Null, + Some(v) => { + let value = crate::values::Value::msg_unpack(&v.msgpack, &typ); + match value { + Ok(value) => value, + Err(errs) => { + return Ok(Response::new(tfplugin6::read_data_source::Response { + deferred: None, + state: None, + diagnostics: errs.to_tfplugin_diags(), + })); + } + } + } + }; + + let state = ds.read(crate::values::Value::Known( + crate::values::ValueKind::Object(BTreeMap::from([( + "name".to_owned(), + crate::values::Value::Known(crate::values::ValueKind::String( + "mykitten".to_owned(), + )), + )])), + )); let (state, diagnostics) = match state { Ok(s) => ( Some(tfplugin6::DynamicValue { diff --git a/src/server/mod.rs b/src/server/mod.rs index 6112221..a0cb313 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -5,9 +5,8 @@ use std::collections::HashMap; use tokio_util::sync::CancellationToken; -use crate::framework::datasource::{self, DataSource}; +use crate::framework::datasource::DataSource; use crate::framework::provider::Provider; -use crate::framework::DResult; pub use grpc::plugin::grpc_controller_server::GrpcControllerServer; pub use grpc::tfplugin6::provider_server::ProviderServer; diff --git a/src/values.rs b/src/values.rs index 2e3f11f..474287f 100644 --- a/src/values.rs +++ b/src/values.rs @@ -1,32 +1,102 @@ // check terraform-plugin-go tfprotov6/internal/toproto for convesions // tftypes for types and values -use std::collections::{BTreeMap, HashMap}; +use std::{ + collections::{BTreeMap, HashMap, HashSet}, + io::{self, Read}, +}; + +use rmp::encode::write_bool; + +use crate::framework::{DResult, Diagnostics}; pub enum Type { + Bool, Number, String, + Dynamic, + /// A list of elements of the same type. + List { + elem: Box, + }, + /// A bunch of unordered string-value pair of the same type. + Map { + elem: Box, + }, + /// A set of unique values of the same type. + Set { + elem: Box, + }, + /// A bunch of unordered string-value pairs of different types. + /// The attributes are statically known. + Object { + attrs: HashMap, + /// The attributes in `attrs` that are optional. + /// Always empty for now because of JSON reasons. + optionals: Vec, + }, + /// An ordered list of values of different types. + Tuple { + elems: Vec, + }, } impl Type { pub fn to_json(&self) -> String { - match *self { - Self::String => "\"string\"".to_owned(), - Self::Number => "\"number\"".to_owned(), + let value = self.to_json_inner(); + serde_json::to_string(&value).unwrap() + } + pub fn to_json_inner(&self) -> serde_json::Value { + use serde_json::Value; + + let compound = + |tag: &str, inner: Value| Value::Array(vec![Value::String(tag.to_owned()), inner]); + + match self { + Self::Bool => Value::String("bool".to_owned()), + Self::String => Value::String("string".to_owned()), + Self::Number => Value::String("number".to_owned()), + Self::Dynamic => Value::String("dynamic".to_owned()), + Self::List { elem } => compound("list", elem.to_json_inner()), + Self::Map { elem } => compound("map", elem.to_json_inner()), + Self::Set { elem } => compound("set", elem.to_json_inner()), + Self::Object { + attrs, + optionals: _, + } => compound( + "object", + Value::Object( + attrs + .iter() + .map(|(k, v)| (k.clone(), v.to_json_inner())) + .collect(), + ), + ), + Self::Tuple { elems } => compound( + "tuple", + elems.iter().map(|elem| elem.to_json_inner()).collect(), + ), } } } -// this is very dumb and wrong +#[derive(Debug)] pub enum Value { - String(String), - Object(BTreeMap), + Known(ValueKind), + Unknown, + Null, } -impl Value { - pub fn ty(&self) -> Type { - todo!() - } +#[derive(Debug)] +pub enum ValueKind { + String(String), + Number(f64), + Bool(bool), + List(Vec), + Set(Vec), + Map(BTreeMap), + Tuple(Vec), + Object(BTreeMap), } // marshal msg pack @@ -35,22 +105,170 @@ impl Value { impl Value { pub fn msg_pack(&self) -> Vec { let mut buf = Vec::new(); - self.msg_pack_inner(&mut buf); + self.msg_pack_inner(&mut buf) + .expect("writing to Vec cannot fail"); buf } - pub fn msg_pack_inner(&self, v: &mut Vec) { - match self { - Value::String(s) => { - rmp::encode::write_str(v, s).unwrap(); + pub fn msg_pack_inner(&self, wr: &mut Vec) -> std::io::Result<()> { + use rmp::encode as mp; + + let known = match self { + Value::Unknown => { + wr.extend_from_slice(&[0xd4, 0, 0]); + return Ok(()); } - Value::Object(o) => { - rmp::encode::write_map_len(v, o.len().try_into().unwrap()).unwrap(); + Value::Null => { + mp::write_nil(wr)?; + return Ok(()); + } + Value::Known(known) => known, + }; + + match known { + ValueKind::String(s) => { + mp::write_str(wr, s)?; + } + &ValueKind::Number(n) => { + if n.is_infinite() { + if n.signum() == -1.0 { + mp::write_f64(wr, f64::NEG_INFINITY)?; + } else { + mp::write_f64(wr, f64::INFINITY)?; + } + } else if (n as i64 as f64) == n { + // is int + mp::write_i64(wr, n as i64)?; + } else { + mp::write_f64(wr, n)?; + } + // Terraform handles bigfloats but we do emphatically not care + } + ValueKind::Bool(b) => { + mp::write_bool(wr, *b)?; + } + ValueKind::List(elems) | ValueKind::Set(elems) | ValueKind::Tuple(elems) => { + mp::write_array_len(wr, elems.len().try_into().unwrap())?; + for elem in elems { + elem.msg_pack_inner(wr)?; + } + } + ValueKind::Map(o) | ValueKind::Object(o) => { + mp::write_map_len(wr, o.len().try_into().unwrap())?; for (key, val) in o { - rmp::encode::write_str(v, key).unwrap(); - val.msg_pack_inner(v); + mp::write_str(wr, key)?; + val.msg_pack_inner(wr)?; } } } + + Ok(()) } -} \ No newline at end of file + + pub fn msg_unpack(data: &[u8], typ: &Type) -> DResult { + let mut read = io::Cursor::new(data); + Self::msg_unpack_inner(&mut read, typ) + } + + fn msg_unpack_inner(rd: &mut io::Cursor<&[u8]>, typ: &Type) -> DResult { + use rmp::decode as mp; + + if let Ok(()) = mp::read_nil(rd) { + return Ok(Value::Null); + } + rd.set_position(rd.position() - 1); // revert past the nil + + let read_string = |rd: &mut io::Cursor<&[u8]>| -> DResult { + let len = std::cmp::min(mp::read_str_len(rd)?, 1024 * 1024); // you're not gonna get more than a 1MB string... + let mut buf = Vec::with_capacity(len as usize); + rd.read_exact(&mut buf)?; + Ok(String::from_utf8(buf)?) + }; + + let value = match typ { + Type::Bool => { + let b = mp::read_bool(rd)?; + ValueKind::Bool(b) + } + Type::Number => { + let prev = rd.position(); + if let Ok(int) = mp::read_int::(rd) { + ValueKind::Number(int as f64) + } else { + rd.set_position(prev); + if let Ok(f32) = mp::read_f32(rd) { + ValueKind::Number(f32 as f64) + } else { + rd.set_position(prev); + let f64 = mp::read_f64(rd)?; + ValueKind::Number(f64) + } + } + } + Type::String => ValueKind::String(read_string(rd)?), + Type::Dynamic => todo!("dynamic"), + Type::List { elem } => { + let len = mp::read_array_len(rd)?; + let elems = (0..len) + .map(|_| Value::msg_unpack_inner(rd, &elem)) + .collect::, _>>()?; + ValueKind::List(elems) + } + Type::Map { elem } => { + let len = mp::read_array_len(rd)?; + let elems = (0..len) + .map(|_| -> DResult<_> { + let key = read_string(rd)?; + let value = Value::msg_unpack_inner(rd, &elem)?; + Ok((key, value)) + }) + .collect::>>()?; + ValueKind::Map(elems) + } + Type::Set { elem } => { + let len = mp::read_array_len(rd)?; + let elems = (0..len) + .map(|_| Value::msg_unpack_inner(rd, &elem)) + .collect::, _>>()?; + ValueKind::Set(elems) + } + Type::Object { attrs, optionals } => { + assert!(optionals.is_empty()); + let len = mp::read_array_len(rd)?; + if attrs.len() != (len as usize) { + return Err(Diagnostics::error_string(format!( + "expected {} attrs, found {len} attrs in object", + attrs.len() + ))); + } + let elems = (0..len) + .map(|_| -> DResult<_> { + let key = read_string(rd)?; + let typ = attrs.get(&key).ok_or_else(|| { + Diagnostics::error_string(format!("unexpected attribute: {key}")) + })?; + let value = Value::msg_unpack_inner(rd, &typ)?; + Ok((key, value)) + }) + .collect::>>()?; + ValueKind::Object(elems) + } + Type::Tuple { elems } => { + let len = mp::read_array_len(rd)?; + if elems.len() != (len as usize) { + return Err(Diagnostics::error_string(format!( + "expected {} elems, found {len} elems in tuple", + elems.len() + ))); + } + let elems = elems + .iter() + .map(|typ| Value::msg_unpack_inner(rd, &typ)) + .collect::, _>>()?; + ValueKind::List(elems) + } + }; + + Ok(Value::Known(value)) + } +}