mirror of
https://github.com/Noratrieb/terustform.git
synced 2026-01-16 09:25:10 +01:00
Derive macro getting works
This commit is contained in:
parent
63bd32c3cd
commit
7d28815065
9 changed files with 245 additions and 41 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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"] }
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
));
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
pub struct StringValue;
|
|
||||||
|
|
@ -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};
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue