refactor(firmware): centralize configuration and diagnostics
This commit is contained in:
1 parent
7048ed89d0
commit
387fdaad23
140 files changed
+6891
-4541
No files matched your search
@@ -15,7 +15,7 @@ pub(crate) fn expand(model: Model) -> Result<TokenStream> {
|
||||
} = model;
|
||||
match kind {
|
||||
ModelKind::Scalars => expand_scalars(ident, generics, crate_path, post_validate, fields),
|
||||
ModelKind::Sections => expand_sections(ident, generics, crate_path, post_validate, fields),
|
||||
ModelKind::Sections => expand_children(ident, generics, crate_path, post_validate, fields),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ fn expand_scalars(
|
||||
let (impl_generics, type_generics, where_clause) = generics.split_for_impl();
|
||||
let mut checks = Vec::new();
|
||||
let mut descriptors = Vec::new();
|
||||
let mut defaults = Vec::new();
|
||||
let mut reads = Vec::new();
|
||||
let mut writes = Vec::new();
|
||||
for field in &fields {
|
||||
@@ -37,38 +36,62 @@ fn expand_scalars(
|
||||
descriptor,
|
||||
checks: field_checks,
|
||||
} = expand_field(&crate_path, field)?;
|
||||
if let Some(descriptor) = descriptor {
|
||||
descriptors.push(descriptor);
|
||||
}
|
||||
descriptors.push(descriptor);
|
||||
checks.extend(field_checks);
|
||||
let id = field.id.expect("ConfigValidate requires configured fields");
|
||||
let default = field
|
||||
.default
|
||||
.as_ref()
|
||||
.expect("ConfigValidate requires defaults");
|
||||
let (kind, value) = config_value_tokens(&crate_path, &field.ty)?;
|
||||
let id = field.id.expect("scalar configuration fields have IDs");
|
||||
let ty = &field.ty;
|
||||
let field_ident = &field.ident;
|
||||
defaults.push(quote! { #field_ident: #default });
|
||||
let (kind, read, decode, out_of_domain_rule) = if !field.enum_backed {
|
||||
(
|
||||
quote! { <#ty as #crate_path::ConfigScalar>::KIND },
|
||||
quote! { #crate_path::ConfigScalar::into_config_value(self.#field_ident) },
|
||||
quote! { <#ty as #crate_path::ConfigScalar>::try_from_config_value(value) },
|
||||
quote! { #crate_path::ValidationRule::AboveMaximum },
|
||||
)
|
||||
} else {
|
||||
(
|
||||
quote! { #crate_path::ValueKind::U32 },
|
||||
quote! {
|
||||
#crate_path::ConfigValue::U32(u32::from(self.#field_ident.wire_value()))
|
||||
},
|
||||
quote! {
|
||||
match value {
|
||||
#crate_path::ConfigValue::U32(value) => u8::try_from(value)
|
||||
.ok()
|
||||
.and_then(<#ty>::try_from_wire)
|
||||
.ok_or(#crate_path::ScalarValueError::OutOfDomain),
|
||||
value => Err(#crate_path::ScalarValueError::WrongKind(value.kind())),
|
||||
}
|
||||
},
|
||||
quote! { #crate_path::ValidationRule::InvalidEnumValue },
|
||||
)
|
||||
};
|
||||
reads.push(quote! {
|
||||
#id => Some(#value(self.#field_ident)),
|
||||
#id => Some(#read),
|
||||
});
|
||||
writes.push(quote! {
|
||||
#id => {
|
||||
let next = match value {
|
||||
#value(next) => next,
|
||||
actual => {
|
||||
let next = match #decode {
|
||||
Ok(next) => next,
|
||||
Err(#crate_path::ScalarValueError::WrongKind(actual)) => {
|
||||
return Err(#crate_path::FieldWriteError::WrongType {
|
||||
field: #crate_path::FieldId::new(#id),
|
||||
expected: #kind,
|
||||
actual: actual.kind(),
|
||||
actual,
|
||||
});
|
||||
}
|
||||
Err(#crate_path::ScalarValueError::OutOfDomain) => {
|
||||
return Err(#crate_path::FieldWriteError::Violation(
|
||||
#crate_path::ConfigViolation::field(
|
||||
#crate_path::FieldId::new(#id),
|
||||
#out_of_domain_rule,
|
||||
),
|
||||
));
|
||||
}
|
||||
};
|
||||
let previous = self.#field_ident;
|
||||
self.#field_ident = next;
|
||||
if let Err(violation) =
|
||||
<Self as #crate_path::ConfigSection>::validate_local_fields(self)
|
||||
{
|
||||
if let Err(violation) = self.__validate_local_configuration() {
|
||||
self.#field_ident = previous;
|
||||
return Err(#crate_path::FieldWriteError::Violation(violation));
|
||||
}
|
||||
@@ -78,30 +101,32 @@ fn expand_scalars(
|
||||
}
|
||||
let post_validate = post_validate.map(|hook| quote! { #hook(self)?; });
|
||||
Ok(quote! {
|
||||
impl #impl_generics #crate_path::ValidateFields for #ident #type_generics #where_clause {
|
||||
fn validate_fields(&self) -> Result<(), #crate_path::ConfigViolation> {
|
||||
<Self as #crate_path::ConfigSection>::validate_local_fields(self)?;
|
||||
#post_validate
|
||||
impl #impl_generics #ident #type_generics #where_clause {
|
||||
/// Field metadata generated from the same declarations as validation.
|
||||
pub const DESCRIPTORS: &'static [#crate_path::FieldDescriptor] = &[
|
||||
#(#descriptors),*
|
||||
];
|
||||
|
||||
fn __validate_local_configuration(
|
||||
&self,
|
||||
) -> Result<(), #crate_path::ConfigViolation> {
|
||||
if !#crate_path::validation::__valid_enum_options(Self::DESCRIPTORS) {
|
||||
return Err(#crate_path::ConfigViolation::global(
|
||||
#crate_path::ValidationRule::Inconsistent,
|
||||
));
|
||||
}
|
||||
#(#checks)*
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl #impl_generics #ident #type_generics #where_clause {
|
||||
/// Field metadata generated from the same attributes as validation.
|
||||
pub const DESCRIPTORS: &'static [#crate_path::FieldDescriptor] = &[
|
||||
#(#descriptors),*
|
||||
];
|
||||
}
|
||||
|
||||
impl #impl_generics #crate_path::ConfigSection for #ident #type_generics #where_clause {
|
||||
impl #impl_generics #crate_path::Config for #ident #type_generics #where_clause {
|
||||
const FIELD_COUNT: usize = Self::DESCRIPTORS.len();
|
||||
const SCHEMA_ID: [u8; 16] =
|
||||
#crate_path::validation::__schema_id(Self::DESCRIPTORS);
|
||||
|
||||
fn default_section() -> Result<Self, #crate_path::ConfigViolation> {
|
||||
let section = Self {
|
||||
#(#defaults),*
|
||||
};
|
||||
<Self as #crate_path::ValidateFields>::validate_fields(§ion)?;
|
||||
Ok(section)
|
||||
fn descriptor(index: usize) -> Option<&'static #crate_path::FieldDescriptor> {
|
||||
Self::DESCRIPTORS.get(index)
|
||||
}
|
||||
|
||||
fn field_value(&self, id: #crate_path::FieldId) -> Option<#crate_path::ConfigValue> {
|
||||
@@ -122,15 +147,16 @@ fn expand_scalars(
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_local_fields(&self) -> Result<(), #crate_path::ConfigViolation> {
|
||||
#(#checks)*
|
||||
fn validate(&self) -> Result<(), #crate_path::ConfigViolation> {
|
||||
self.__validate_local_configuration()?;
|
||||
#post_validate
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn expand_sections(
|
||||
fn expand_children(
|
||||
ident: syn::Ident,
|
||||
generics: syn::Generics,
|
||||
crate_path: syn::Path,
|
||||
@@ -138,71 +164,55 @@ fn expand_sections(
|
||||
fields: Vec<crate::parse::FieldModel>,
|
||||
) -> Result<TokenStream> {
|
||||
let (impl_generics, type_generics, where_clause) = generics.split_for_impl();
|
||||
let validations = fields.iter().map(|field| {
|
||||
let mut validations = Vec::new();
|
||||
let mut reads = Vec::new();
|
||||
let mut writes = Vec::new();
|
||||
let mut counts = Vec::new();
|
||||
let mut schema_ids = Vec::new();
|
||||
let mut descriptor_lookups = Vec::new();
|
||||
for field in &fields {
|
||||
let field_ident = &field.ident;
|
||||
let ty = &field.ty;
|
||||
quote! {
|
||||
<#ty as #crate_path::ValidateFields>::validate_fields(&self.#field_ident)?;
|
||||
}
|
||||
});
|
||||
let defaults = fields.iter().map(|field| {
|
||||
let field_ident = &field.ident;
|
||||
let ty = &field.ty;
|
||||
quote! {
|
||||
#field_ident: <#ty as #crate_path::ConfigSection>::default_section()?
|
||||
}
|
||||
});
|
||||
let reads = fields.iter().map(|field| {
|
||||
let field_ident = &field.ident;
|
||||
let ty = &field.ty;
|
||||
quote! {
|
||||
if let Some(value) = <#ty as #crate_path::ConfigSection>::field_value(
|
||||
validations.push(quote! {
|
||||
<#ty as #crate_path::Config>::validate(&self.#field_ident)?;
|
||||
});
|
||||
reads.push(quote! {
|
||||
if let Some(value) = <#ty as #crate_path::Config>::field_value(
|
||||
&self.#field_ident,
|
||||
id,
|
||||
) {
|
||||
return Some(value);
|
||||
}
|
||||
}
|
||||
});
|
||||
let writes = fields.iter().map(|field| {
|
||||
let field_ident = &field.ident;
|
||||
let ty = &field.ty;
|
||||
quote! {
|
||||
if <#ty as #crate_path::ConfigSection>::field_value(&self.#field_ident, id).is_some() {
|
||||
return <#ty as #crate_path::ConfigSection>::set_field(
|
||||
});
|
||||
writes.push(quote! {
|
||||
if <#ty as #crate_path::Config>::field_value(&self.#field_ident, id).is_some() {
|
||||
return <#ty as #crate_path::Config>::set_field(
|
||||
&mut self.#field_ident,
|
||||
id,
|
||||
value,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
let counts = fields.iter().map(|field| {
|
||||
let ty = &field.ty;
|
||||
quote! { <#ty as #crate_path::ConfigSection>::FIELD_COUNT }
|
||||
});
|
||||
let post_validate = post_validate.map(|hook| quote! { #hook(self)?; });
|
||||
Ok(quote! {
|
||||
impl #impl_generics #crate_path::ValidateFields for #ident #type_generics #where_clause {
|
||||
fn validate_fields(&self) -> Result<(), #crate_path::ConfigViolation> {
|
||||
#(#validations)*
|
||||
#post_validate
|
||||
Ok(())
|
||||
});
|
||||
counts.push(quote! { <#ty as #crate_path::Config>::FIELD_COUNT });
|
||||
schema_ids.push(quote! { <#ty as #crate_path::Config>::SCHEMA_ID });
|
||||
descriptor_lookups.push(quote! {
|
||||
if index < <#ty as #crate_path::Config>::FIELD_COUNT {
|
||||
return <#ty as #crate_path::Config>::descriptor(index);
|
||||
}
|
||||
}
|
||||
index -= <#ty as #crate_path::Config>::FIELD_COUNT;
|
||||
});
|
||||
}
|
||||
let post_validate = post_validate.map(|hook| quote! { #hook(self)?; });
|
||||
|
||||
impl #impl_generics #ident #type_generics #where_clause {
|
||||
/// Number of fields aggregated from derived nested sections.
|
||||
pub const FIELD_COUNT: usize = 0 #(+ #counts)*;
|
||||
}
|
||||
Ok(quote! {
|
||||
impl #impl_generics #crate_path::Config for #ident #type_generics #where_clause {
|
||||
const FIELD_COUNT: usize = 0 #(+ #counts)*;
|
||||
const SCHEMA_ID: [u8; 16] =
|
||||
#crate_path::validation::__combine_schema_ids(&[#(#schema_ids),*]);
|
||||
|
||||
impl #impl_generics #crate_path::ConfigAggregate for #ident #type_generics #where_clause {
|
||||
fn default_aggregate() -> Result<Self, #crate_path::ConfigViolation> {
|
||||
let aggregate = Self {
|
||||
#(#defaults),*
|
||||
};
|
||||
<Self as #crate_path::ValidateFields>::validate_fields(&aggregate)?;
|
||||
Ok(aggregate)
|
||||
fn descriptor(mut index: usize) -> Option<&'static #crate_path::FieldDescriptor> {
|
||||
#(#descriptor_lookups)*
|
||||
None
|
||||
}
|
||||
|
||||
fn field_value(&self, id: #crate_path::FieldId) -> Option<#crate_path::ConfigValue> {
|
||||
@@ -218,23 +228,39 @@ fn expand_sections(
|
||||
#(#writes)*
|
||||
Err(#crate_path::FieldWriteError::UnknownField(id))
|
||||
}
|
||||
|
||||
fn validate(&self) -> Result<(), #crate_path::ConfigViolation> {
|
||||
if !#crate_path::validation::__unique_field_ids::<Self>() {
|
||||
return Err(#crate_path::ConfigViolation::global(
|
||||
#crate_path::ValidationRule::Inconsistent,
|
||||
));
|
||||
}
|
||||
if !#crate_path::validation::__unique_display_keys::<Self>() {
|
||||
return Err(#crate_path::ConfigViolation::global(
|
||||
#crate_path::ValidationRule::Inconsistent,
|
||||
));
|
||||
}
|
||||
#(#validations)*
|
||||
#post_validate
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl #impl_generics #ident #type_generics #where_clause {
|
||||
/// Number of fields contributed by the complete generated tree.
|
||||
pub const FIELD_COUNT: usize = <Self as #crate_path::Config>::FIELD_COUNT;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
struct FieldMetadata {
|
||||
descriptor: Option<TokenStream>,
|
||||
descriptor: TokenStream,
|
||||
checks: Vec<TokenStream>,
|
||||
}
|
||||
|
||||
fn expand_field(crate_path: &syn::Path, field: &crate::parse::FieldModel) -> Result<FieldMetadata> {
|
||||
let mut checks = Vec::new();
|
||||
let Some(id) = field.id else {
|
||||
return Ok(FieldMetadata {
|
||||
descriptor: None,
|
||||
checks,
|
||||
});
|
||||
};
|
||||
let id = field.id.expect("scalar configuration fields have IDs");
|
||||
let minimum = field.checks.iter().find_map(|check| match check {
|
||||
Check::Minimum(value) => Some(value),
|
||||
_ => None,
|
||||
@@ -248,23 +274,33 @@ fn expand_field(crate_path: &syn::Path, field: &crate::parse::FieldModel) -> Res
|
||||
}
|
||||
let key = field.display_key.as_ref().expect("id fields have a key");
|
||||
let unit = field.unit.as_ref().expect("id fields have a unit");
|
||||
let default = field.default.as_ref().expect("id fields have a default");
|
||||
let (kind, value) = config_value_tokens(crate_path, &field.ty)?;
|
||||
let ty = &field.ty;
|
||||
let (kind, enum_options) = if !field.enum_backed {
|
||||
(
|
||||
quote! { <#ty as #crate_path::ConfigScalar>::KIND },
|
||||
quote! { &[] },
|
||||
)
|
||||
} else {
|
||||
(
|
||||
quote! { #crate_path::ValueKind::U32 },
|
||||
quote! { <#ty>::CONFIG_ENUM_OPTIONS },
|
||||
)
|
||||
};
|
||||
let value = config_value_constructor_tokens(crate_path, &field.ty)?;
|
||||
let minimum = option_value_tokens(value.clone(), minimum);
|
||||
let maximum = option_value_tokens(value.clone(), maximum);
|
||||
Ok(FieldMetadata {
|
||||
descriptor: Some(quote! {
|
||||
descriptor: quote! {
|
||||
#crate_path::FieldDescriptor {
|
||||
id: #crate_path::FieldId::new(#id),
|
||||
display_key: #key,
|
||||
kind: #kind,
|
||||
minimum: #minimum,
|
||||
maximum: #maximum,
|
||||
default: #value(#default),
|
||||
enum_options: #enum_options,
|
||||
unit: #unit,
|
||||
apply_policy: #crate_path::ApplyPolicy::RestartRequired,
|
||||
}
|
||||
}),
|
||||
},
|
||||
checks,
|
||||
})
|
||||
}
|
||||
@@ -276,10 +312,7 @@ fn option_value_tokens(value: TokenStream, expression: Option<&syn::Expr>) -> To
|
||||
}
|
||||
}
|
||||
|
||||
fn config_value_tokens(
|
||||
crate_path: &syn::Path,
|
||||
ty: &syn::Type,
|
||||
) -> Result<(TokenStream, TokenStream)> {
|
||||
fn config_value_constructor_tokens(crate_path: &syn::Path, ty: &syn::Type) -> Result<TokenStream> {
|
||||
let syn::Type::Path(path) = ty else {
|
||||
return Err(syn::Error::new_spanned(
|
||||
ty,
|
||||
@@ -292,39 +325,18 @@ fn config_value_tokens(
|
||||
"configuration metadata requires a scalar type",
|
||||
));
|
||||
};
|
||||
let (kind, value) = match segment.ident.to_string().as_str() {
|
||||
"bool" => (
|
||||
quote! { #crate_path::ValueKind::Bool },
|
||||
quote! { #crate_path::ConfigValue::Bool },
|
||||
),
|
||||
"i32" => (
|
||||
quote! { #crate_path::ValueKind::I32 },
|
||||
quote! { #crate_path::ConfigValue::I32 },
|
||||
),
|
||||
"u32" => (
|
||||
quote! { #crate_path::ValueKind::U32 },
|
||||
quote! { #crate_path::ConfigValue::U32 },
|
||||
),
|
||||
"u64" => (
|
||||
quote! { #crate_path::ValueKind::U64 },
|
||||
quote! { #crate_path::ConfigValue::U64 },
|
||||
),
|
||||
"f32" => (
|
||||
quote! { #crate_path::ValueKind::F32 },
|
||||
quote! { #crate_path::ConfigValue::F32 },
|
||||
),
|
||||
"f64" => (
|
||||
quote! { #crate_path::ValueKind::F64 },
|
||||
quote! { #crate_path::ConfigValue::F64 },
|
||||
),
|
||||
let value = match segment.ident.to_string().as_str() {
|
||||
"bool" => quote! { #crate_path::ConfigValue::Bool },
|
||||
"i32" => quote! { #crate_path::ConfigValue::I32 },
|
||||
"u8" | "u16" | "u32" => quote! { #crate_path::ConfigValue::U32 },
|
||||
"u64" => quote! { #crate_path::ConfigValue::U64 },
|
||||
"f32" => quote! { #crate_path::ConfigValue::F32 },
|
||||
"f64" => quote! { #crate_path::ConfigValue::F64 },
|
||||
_ => {
|
||||
return Err(syn::Error::new_spanned(
|
||||
ty,
|
||||
"configuration metadata supports bool, i32, u32, u64, f32, and f64",
|
||||
));
|
||||
return Ok(quote! { #crate_path::ConfigValue::U32 });
|
||||
}
|
||||
};
|
||||
Ok((kind, value))
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
fn expand_check(crate_path: &syn::Path, field: &syn::Ident, id: u16, check: Check) -> TokenStream {
|
||||
|
||||
Reference in new issue
Block a user