Derive macro getting works

This commit is contained in:
nora 2024-04-15 19:48:17 +02:00
parent 63bd32c3cd
commit 7d28815065
9 changed files with 245 additions and 41 deletions

2
Cargo.lock generated
View file

@ -1083,7 +1083,6 @@ version = "0.1.0"
dependencies = [ dependencies = [
"eyre", "eyre",
"terustform", "terustform",
"terustform-macros",
"tokio", "tokio",
] ]
@ -1100,6 +1099,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"tempfile", "tempfile",
"terustform-macros",
"time", "time",
"tokio", "tokio",
"tokio-stream", "tokio-stream",

View file

@ -6,6 +6,5 @@ edition = "2021"
[dependencies] [dependencies]
eyre = "0.6.12" eyre = "0.6.12"
terustform = { path = "../terustform" } terustform = { path = "../terustform" }
terustform-macros = { path = "../terustform-macros" }
tokio = { version = "1.37.0", features = ["full"] } tokio = { version = "1.37.0", features = ["full"] }

View file

@ -4,8 +4,7 @@ use terustform::{
framework::{ framework::{
datasource::{self, DataSource}, datasource::{self, DataSource},
provider::Provider, provider::Provider,
value::StringValue, AttrPath, DResult, Diagnostics, StringValue, ValueModel,
DResult,
}, },
values::{Value, ValueKind}, values::{Value, ValueKind},
}; };
@ -29,11 +28,6 @@ impl Provider for ExampleProvider {
struct ExampleDataSource {} struct ExampleDataSource {}
#[derive(terustform_macros::DataSourceModel)]
struct _ExampleDataSourceModel {
name: StringValue,
}
impl DataSource for ExampleDataSource { impl DataSource for ExampleDataSource {
fn name(&self, provider_name: &str) -> String { fn name(&self, provider_name: &str) -> String {
format!("{provider_name}_kitty") format!("{provider_name}_kitty")
@ -72,20 +66,20 @@ impl DataSource for ExampleDataSource {
} }
fn read(&self, config: Value) -> DResult<Value> { fn read(&self, config: Value) -> DResult<Value> {
let name = match config { let model = ExampleDataSourceModel::from_value(config, &AttrPath::root())?;
Value::Known(ValueKind::Object(mut obj)) => obj.remove("name").unwrap(),
_ => unreachable!(), let StringValue::Known(name_str) = &model.name else {
}; return Err(Diagnostics::error_string(
let name_str = match &name { "model name must be known".to_owned(),
Value::Known(ValueKind::String(s)) => s.clone(), ));
_ => unreachable!(),
}; };
let meow = format!("mrrrrr i am {name_str}");
Ok(Value::Known(ValueKind::Object(BTreeMap::from([ Ok(Value::Known(ValueKind::Object(BTreeMap::from([
("name".to_owned(), name), ("name".to_owned(), model.name.to_value()),
( (
"meow".to_owned(), "meow".to_owned(),
Value::Known(ValueKind::String(format!("mrrrrr i am {name_str}"))), Value::Known(ValueKind::String(meow)),
), ),
( (
"id".to_owned(), "id".to_owned(),
@ -94,3 +88,10 @@ impl DataSource for ExampleDataSource {
])))) ]))))
} }
} }
#[derive(terustform::DataSourceModel)]
struct ExampleDataSourceModel {
name: StringValue,
meow: StringValue,
id: StringValue,
}

View file

@ -1,6 +1,104 @@
use quote::quote;
use syn::spanned::Spanned;
// This macro should only reference items in `terustform::__derive_private`.
#[proc_macro_derive(DataSourceModel)] #[proc_macro_derive(DataSourceModel)]
pub fn data_source_model( pub fn data_source_model(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
_input: proc_macro::TokenStream, let input = syn::parse_macro_input!(input as syn::DeriveInput);
) -> proc_macro::TokenStream { match data_source_model_inner(input) {
Default::default() Ok(ts) => ts.into(),
Err(err) => err.into_compile_error().into(),
}
}
fn data_source_model_inner(
input: syn::DeriveInput,
) -> Result<proc_macro2::TokenStream, syn::Error> {
let struct_name = input.ident;
let syn::Data::Struct(data) = input.data else {
return Err(syn::Error::new(
struct_name.span(),
"models must be structs",
));
};
let syn::Fields::Named(fields) = data.fields else {
return Err(syn::Error::new(
struct_name.span(),
"models must have named fields",
));
};
let terustform = quote!(::terustform::__derive_private);
let fields = fields
.named
.into_iter()
.map(|field| {
let Some(name) = field.ident else {
return Err(syn::Error::new(field.span(), "field must be named"));
};
Ok((name, field.ty))
})
.collect::<Result<Vec<_>, _>>()?;
let field_extractions = fields.iter().map(|(name, ty)| {
let name_str = proc_macro2::Literal::string(&name.to_string());
quote! {
let #terustform::Some(#name) = obj.remove(#name_str) else {
return #terustform::Err(
#terustform::Diagnostics::error_string(
format!("Expected property '{}', which was not present", #name_str),
).with_path(path.clone())
);
};
let #name = <#ty as #terustform::ValueModel>::from_value(
#name,
&path.append_attribute_name(#terustform::ToOwned::to_owned(#name_str))
)?;
}
});
let constructor_fields = fields.iter().map(|(name, _)| quote! { #name, });
let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();
Ok(quote! {
#[automatically_derived]
impl #impl_generics #terustform::ValueModel
for #struct_name #type_generics #where_clause
{
fn from_value(v: #terustform::Value, path: &#terustform::AttrPath) -> #terustform::DResult<Self> {
match v {
#terustform::BaseValue::Unknown => {
return #terustform::Err(#terustform::Diagnostics::with_path(
#terustform::Diagnostics::error_string(#terustform::ToOwned::to_owned("Expected object, found unknown value")),
#terustform::Clone::clone(&path),
));
},
#terustform::BaseValue::Null => {
return #terustform::Err(#terustform::Diagnostics::with_path(
#terustform::Diagnostics::error_string(#terustform::ToOwned::to_owned("Expected object, found null value")),
#terustform::Clone::clone(&path),
));
},
#terustform::BaseValue::Known(#terustform::ValueKind::Object(mut obj)) => {
#(#field_extractions)*
Ok(#struct_name {
#(#constructor_fields)*
})
},
#terustform::BaseValue::Known(v) => {
return #terustform::Err(#terustform::Diagnostics::with_path(
#terustform::Diagnostics::error_string(format!("Expected object, found {} value", v.diagnostic_type_str())),
#terustform::Clone::clone(&path),
));
},
}
}
}
})
} }

View file

@ -4,6 +4,8 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
terustform-macros = { path = "../terustform-macros" }
base64 = "0.22.0" base64 = "0.22.0"
eyre = "0.6.12" eyre = "0.6.12"
prost = "0.12.4" prost = "0.12.4"

View file

@ -2,12 +2,16 @@
pub mod datasource; pub mod datasource;
pub mod provider; pub mod provider;
pub mod value;
use crate::values::{Value, ValueKind};
use self::datasource::DataSource; use self::datasource::DataSource;
#[derive(Debug, Default)]
pub struct Diagnostics { pub struct Diagnostics {
pub(crate) errors: Vec<String>, pub(crate) errors: Vec<String>,
pub(crate) attr: Option<AttrPath>,
// note: lol this cannot contain warnings that would be fucked oops
} }
pub type DResult<T> = Result<T, Diagnostics>; pub type DResult<T> = Result<T, Diagnostics>;
@ -16,8 +20,18 @@ impl Diagnostics {
pub fn error_string(msg: String) -> Self { pub fn error_string(msg: String) -> Self {
Self { Self {
errors: vec![msg], errors: vec![msg],
attr: None,
} }
} }
pub fn with_path(mut self, path: AttrPath) -> Self {
self.attr = Some(path);
self
}
pub fn has_errors(&self) -> bool {
!self.errors.is_empty()
}
} }
impl<E: std::error::Error + std::fmt::Debug> From<E> for Diagnostics { impl<E: std::error::Error + std::fmt::Debug> From<E> for Diagnostics {
@ -25,3 +39,75 @@ impl<E: std::error::Error + std::fmt::Debug> From<E> for Diagnostics {
Self::error_string(format!("{:?}", value)) Self::error_string(format!("{:?}", value))
} }
} }
// TODO: this could probably be a clever 0-alloc &-based linked list!
#[derive(Debug, Clone, Default)]
pub struct AttrPath(Vec<AttrPathSegment>);
#[derive(Debug, Clone)]
pub enum AttrPathSegment {
AttributeName(String),
ElementKeyString(String),
ElementKeyInt(i64),
}
impl AttrPath {
pub fn root() -> Self {
Self::default()
}
pub fn append_attribute_name(&self, name: String) -> Self {
let mut p = self.clone();
p.0.push(AttrPathSegment::AttributeName(name));
p
}
}
pub type StringValue = BaseValue<String>;
pub type I64Value = BaseValue<i64>;
#[derive(Debug)]
pub enum BaseValue<T> {
Unknown,
Null,
Known(T),
}
impl<T> BaseValue<T> {
fn map<U>(self, f: impl FnOnce(T) -> U) -> BaseValue<U> {
self.try_map(|v| Ok(f(v))).unwrap()
}
fn try_map<U>(self, f: impl FnOnce(T) -> DResult<U>) -> DResult<BaseValue<U>> {
Ok(match self {
Self::Unknown => BaseValue::Unknown,
Self::Null => BaseValue::Null,
Self::Known(v) => BaseValue::Known(f(v)?),
})
}
}
pub trait ValueModel: Sized {
fn from_value(v: Value, path: &AttrPath) -> DResult<Self>;
fn to_value(self) -> Value {
todo!()
}
}
impl ValueModel for StringValue {
fn from_value(v: Value, path: &AttrPath) -> DResult<Self> {
v.try_map(|v| match v {
ValueKind::String(s) => Ok(s),
_ => Err(Diagnostics::error_string(format!(
"expected string, found {}",
v.diagnostic_type_str()
))
.with_path(path.clone())),
})
}
fn to_value(self) -> Value {
self.map(ValueKind::String)
}
}

View file

@ -1 +0,0 @@
pub struct StringValue;

View file

@ -3,6 +3,8 @@ pub mod framework;
mod server; mod server;
pub mod values; pub mod values;
pub use terustform_macros::DataSourceModel;
use std::{env, path::PathBuf}; use std::{env, path::PathBuf};
use base64::Engine; use base64::Engine;
@ -48,16 +50,13 @@ pub async fn serve(provider: &dyn Provider) -> eyre::Result<()> {
let server = tonic::transport::Server::builder() let server = tonic::transport::Server::builder()
.tls_config(tls) .tls_config(tls)
.wrap_err("invalid TLS config")? .wrap_err("invalid TLS config")?
.add_service(server::ProviderServer::new( .add_service(server::ProviderServer::new(server::ProviderHandler::new(
server::ProviderHandler::new(shutdown.clone(), provider), shutdown.clone(),
)) provider,
.add_service( )))
server::GrpcControllerServer::new( .add_service(server::GrpcControllerServer::new(server::Controller {
server::Controller { shutdown: shutdown.clone(),
shutdown: shutdown.clone(), }))
},
),
)
.serve_with_incoming(uds_stream); .serve_with_incoming(uds_stream);
tokio::select! { tokio::select! {
@ -101,3 +100,13 @@ async fn init_handshake(server_cert: &rcgen::Certificate) -> Result<(tempfile::T
Ok((tmpdir, socket)) Ok((tmpdir, socket))
} }
/// Private, only for use for with the derive macro.
#[doc(hidden)]
pub mod __derive_private {
pub use crate::framework::{
AttrPath, AttrPathSegment, BaseValue, DResult, Diagnostics, ValueModel,
};
pub use crate::values::{Value, ValueKind};
pub use {Clone, Result::Err, Option::Some, ToOwned};
}

View file

@ -6,7 +6,7 @@ use std::{
io::{self, Read}, io::{self, Read},
}; };
use crate::framework::{DResult, Diagnostics}; use crate::framework::{BaseValue, DResult, Diagnostics};
#[derive(Debug)] #[derive(Debug)]
pub enum Type { pub enum Type {
@ -79,12 +79,7 @@ impl Type {
} }
} }
#[derive(Debug)] pub type Value = BaseValue<ValueKind>;
pub enum Value {
Known(ValueKind),
Unknown,
Null,
}
#[derive(Debug)] #[derive(Debug)]
pub enum ValueKind { pub enum ValueKind {
@ -98,6 +93,21 @@ pub enum ValueKind {
Object(BTreeMap<String, Value>), Object(BTreeMap<String, Value>),
} }
impl ValueKind {
pub fn diagnostic_type_str(&self) -> &'static str {
match self {
ValueKind::String(_) => "string",
ValueKind::Number(_) => "number",
ValueKind::Bool(_) => "bool",
ValueKind::List(_) => "list",
ValueKind::Set(_) => "set",
ValueKind::Map(_) => "map",
ValueKind::Tuple(_) => "tuple",
ValueKind::Object(_) => "object",
}
}
}
// marshal msg pack // marshal msg pack
// tftypes/value.go:MarshalMsgPack // tftypes/value.go:MarshalMsgPack