fix diagnostic

This commit is contained in:
nora 2024-04-15 20:16:54 +02:00
parent e0f753c0c2
commit b86292fadb
5 changed files with 90 additions and 43 deletions

View file

@ -49,9 +49,9 @@ fn data_source_model_inner(
quote! { quote! {
let #tf::Some(#name) = obj.remove(#name_str) else { let #tf::Some(#name) = obj.remove(#name_str) else {
return #tf::Err( return #tf::Err(
#tf::Diagnostics::error_string( #tf::Diagnostics::from(#tf::Diagnostic::error_string(
format!("Expected property '{}', which was not present", #name_str), format!("Expected property '{}', which was not present", #name_str),
).with_path(path.clone()) ).with_path(path.clone()))
); );
}; };
let #name = <#ty as #tf::ValueModel>::from_value( let #name = <#ty as #tf::ValueModel>::from_value(
@ -78,16 +78,16 @@ fn data_source_model_inner(
fn from_value(v: #tf::Value, path: &#tf::AttrPath) -> #tf::DResult<Self> { fn from_value(v: #tf::Value, path: &#tf::AttrPath) -> #tf::DResult<Self> {
match v { match v {
#tf::BaseValue::Unknown => { #tf::BaseValue::Unknown => {
return #tf::Err(#tf::Diagnostics::with_path( return #tf::Err(#tf::Diagnostics::from(#tf::Diagnostic::with_path(
#tf::Diagnostics::error_string(#tf::ToOwned::to_owned("Expected object, found unknown value")), #tf::Diagnostic::error_string(#tf::ToOwned::to_owned("Expected object, found unknown value")),
#tf::Clone::clone(&path), #tf::Clone::clone(&path),
)); )));
}, },
#tf::BaseValue::Null => { #tf::BaseValue::Null => {
return #tf::Err(#tf::Diagnostics::with_path( return #tf::Err(#tf::Diagnostics::from(#tf::Diagnostic::with_path(
#tf::Diagnostics::error_string(#tf::ToOwned::to_owned("Expected object, found null value")), #tf::Diagnostic::error_string(#tf::ToOwned::to_owned("Expected object, found null value")),
#tf::Clone::clone(&path), #tf::Clone::clone(&path),
)); )));
}, },
#tf::BaseValue::Known(#tf::ValueKind::Object(mut obj)) => { #tf::BaseValue::Known(#tf::ValueKind::Object(mut obj)) => {
#(#field_extractions)* #(#field_extractions)*
@ -97,10 +97,10 @@ fn data_source_model_inner(
}) })
}, },
#tf::BaseValue::Known(v) => { #tf::BaseValue::Known(v) => {
return #tf::Err(#tf::Diagnostics::with_path( return #tf::Err(#tf::Diagnostics::from(#tf::Diagnostic::with_path(
#tf::Diagnostics::error_string(format!("Expected object, found {} value", v.diagnostic_type_str())), #tf::Diagnostic::error_string(format!("Expected object, found {} value", v.diagnostic_type_str())),
#tf::Clone::clone(&path), #tf::Clone::clone(&path),
)); )));
}, },
} }
} }

View file

@ -9,41 +9,58 @@ use self::datasource::DataSource;
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct Diagnostics { pub struct Diagnostics {
pub(crate) errors: Vec<String>, pub(crate) diags: Vec<Diagnostic>,
pub(crate) attr: Option<AttrPath>,
// note: lol this cannot contain warnings that would be fucked oops // note: lol this cannot contain warnings that would be fucked oops
} }
#[derive(Debug)]
pub struct Diagnostic {
pub(crate) msg: String,
pub(crate) attr: Option<AttrPath>,
}
pub type DResult<T> = Result<T, Diagnostics>; pub type DResult<T> = Result<T, Diagnostics>;
impl Diagnostics { impl Diagnostic {
pub fn error_string(msg: impl Into<String>) -> Self { pub fn error_string(msg: impl Into<String>) -> Self {
Self { Diagnostic {
errors: vec![msg.into()], msg: msg.into(),
attr: None, attr: None,
} }
} }
pub fn with_path(mut self, path: AttrPath) -> Self { pub fn with_path(mut self, path: AttrPath) -> Self {
self.attr = Some(path); self.attr = Some(path);
self self
} }
}
impl Diagnostics {
pub fn has_errors(&self) -> bool { pub fn has_errors(&self) -> bool {
!self.errors.is_empty() !self.diags.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 Diagnostic {
fn from(value: E) -> Self { fn from(value: E) -> Self {
Self::error_string(format!("{:?}", value)) Self::error_string(format!("{:?}", value))
} }
} }
impl<E: std::error::Error + std::fmt::Debug> From<E> for Diagnostics {
fn from(value: E) -> Self {
Diagnostic::from(value).into()
}
}
impl From<Diagnostic> for Diagnostics {
fn from(value: Diagnostic) -> Self {
Self { diags: vec![value] }
}
}
// TODO: this could probably be a clever 0-alloc &-based linked list! // TODO: this could probably be a clever 0-alloc &-based linked list!
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct AttrPath(Vec<AttrPathSegment>); pub struct AttrPath(pub(crate) Vec<AttrPathSegment>);
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum AttrPathSegment { pub enum AttrPathSegment {
@ -91,13 +108,14 @@ impl<T> BaseValue<T> {
pub fn expect_known(&self, path: AttrPath) -> DResult<&T> { pub fn expect_known(&self, path: AttrPath) -> DResult<&T> {
match self { match self {
BaseValue::Null => { BaseValue::Null => Err(Diagnostic::error_string("expected value, found null value")
Err(Diagnostics::error_string("expected value, found null value").with_path(path)) .with_path(path)
} .into()),
BaseValue::Unknown => Err(Diagnostics::error_string( BaseValue::Unknown => Err(Diagnostic::error_string(
"expected known value, found unknown value", "expected known value, found unknown value",
) )
.with_path(path)), .with_path(path)
.into()),
BaseValue::Known(v) => Ok(v), BaseValue::Known(v) => Ok(v),
} }
} }
@ -111,13 +129,16 @@ pub trait ValueModel: Sized {
impl ValueModel for StringValue { impl ValueModel for StringValue {
fn from_value(v: Value, path: &AttrPath) -> DResult<Self> { fn from_value(v: Value, path: &AttrPath) -> DResult<Self> {
v.try_map(|v| match v { v.try_map(|v| -> DResult<String> {
ValueKind::String(s) => Ok(s), match v {
_ => Err(Diagnostics::error_string(format!( ValueKind::String(s) => Ok(s),
"expected string, found {}", _ => Err(Diagnostic::error_string(format!(
v.diagnostic_type_str() "expected string, found {}",
)) v.diagnostic_type_str()
.with_path(path.clone())), ))
.with_path(path.clone())
.into()),
}
}) })
} }

View file

@ -105,7 +105,7 @@ async fn init_handshake(server_cert: &rcgen::Certificate) -> Result<(tempfile::T
#[doc(hidden)] #[doc(hidden)]
pub mod __derive_private { pub mod __derive_private {
pub use crate::framework::{ pub use crate::framework::{
AttrPath, AttrPathSegment, BaseValue, DResult, Diagnostics, ValueModel, AttrPath, AttrPathSegment, BaseValue, DResult, Diagnostic, Diagnostics, ValueModel,
}; };
pub use crate::values::{Value, ValueKind}; pub use crate::values::{Value, ValueKind};
pub use {Clone, Option::Some, Result::Err, ToOwned}; pub use {Clone, Option::Some, Result::Err, ToOwned};

View file

@ -1,7 +1,7 @@
use crate::{ use crate::{
framework::{ framework::{
datasource::{self, Mode}, datasource::{self, Mode},
Diagnostics, AttrPathSegment, Diagnostics,
}, },
values::Type, values::Type,
}; };
@ -78,13 +78,37 @@ impl datasource::Attribute {
impl Diagnostics { impl Diagnostics {
pub(crate) fn to_tfplugin_diags(self) -> Vec<tfplugin6::Diagnostic> { pub(crate) fn to_tfplugin_diags(self) -> Vec<tfplugin6::Diagnostic> {
self.errors self.diags
.into_iter() .into_iter()
.map(|err| tfplugin6::Diagnostic { .map(|err| tfplugin6::Diagnostic {
severity: tfplugin6::diagnostic::Severity::Error as _, severity: tfplugin6::diagnostic::Severity::Error as _,
summary: err, summary: err.msg,
detail: "".to_owned(), detail: "".to_owned(),
attribute: None, attribute: err.attr.map(|attr| -> tfplugin6::AttributePath {
tfplugin6::AttributePath {
steps: attr
.0
.into_iter()
.map(|segment| {
use tfplugin6::attribute_path::step::Selector;
tfplugin6::attribute_path::Step {
selector: Some(match segment {
AttrPathSegment::AttributeName(name) => {
Selector::AttributeName(name)
}
AttrPathSegment::ElementKeyString(key) => {
Selector::ElementKeyString(key)
}
AttrPathSegment::ElementKeyInt(key) => {
Selector::ElementKeyInt(key)
}
}),
}
})
.collect(),
}
}),
}) })
.collect() .collect()
} }

View file

@ -6,7 +6,7 @@ use std::{
io::{self, Read}, io::{self, Read},
}; };
use crate::framework::{BaseValue, DResult, Diagnostics}; use crate::framework::{BaseValue, DResult, Diagnostic};
#[derive(Debug)] #[derive(Debug)]
pub enum Type { pub enum Type {
@ -250,16 +250,17 @@ impl Value {
let len = mp::read_map_len(rd)?; let len = mp::read_map_len(rd)?;
if attrs.len() != (len as usize) { if attrs.len() != (len as usize) {
return Err(Diagnostics::error_string(format!( return Err(Diagnostic::error_string(format!(
"expected {} attrs, found {len} attrs in object", "expected {} attrs, found {len} attrs in object",
attrs.len() attrs.len()
))); ))
.into());
} }
let elems = (0..len) let elems = (0..len)
.map(|_| -> DResult<_> { .map(|_| -> DResult<_> {
let key = read_string(rd)?; let key = read_string(rd)?;
let typ = attrs.get(&key).ok_or_else(|| { let typ = attrs.get(&key).ok_or_else(|| {
Diagnostics::error_string(format!("unexpected attribute: '{key}'")) Diagnostic::error_string(format!("unexpected attribute: '{key}'"))
})?; })?;
let value = Value::msg_unpack_inner(rd, &typ)?; let value = Value::msg_unpack_inner(rd, &typ)?;
Ok((key, value)) Ok((key, value))
@ -270,10 +271,11 @@ impl Value {
Type::Tuple { elems } => { Type::Tuple { elems } => {
let len = mp::read_array_len(rd)?; let len = mp::read_array_len(rd)?;
if elems.len() != (len as usize) { if elems.len() != (len as usize) {
return Err(Diagnostics::error_string(format!( return Err(Diagnostic::error_string(format!(
"expected {} elems, found {len} elems in tuple", "expected {} elems, found {len} elems in tuple",
elems.len() elems.len()
))); ))
.into());
} }
let elems = elems let elems = elems