use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DomainSpec {
pub project: ProjectSpec,
#[serde(default)]
pub modules: Vec<ModuleSpec>,
#[serde(default)]
pub common_types: Option<CommonTypesSpec>,
#[serde(default)]
pub entities: Vec<EntitySpec>,
#[serde(default)]
pub errors: Vec<ErrorSpec>,
#[serde(default)]
pub implementations: Vec<ImplSpec>,
#[serde(default)]
pub refactors: Vec<RefactorSpec>,
#[serde(default)]
pub verification: Option<VerificationSpec>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectSpec {
pub name: String,
#[serde(default)]
pub crate_name: Option<String>,
}
impl ProjectSpec {
pub fn crate_name(&self) -> String {
self.crate_name
.clone()
.unwrap_or_else(|| self.name.replace('-', "_"))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModuleSpec {
pub name: String,
#[serde(default)]
pub is_pub: bool,
#[serde(default)]
pub children: Vec<ModuleSpec>,
}
impl ModuleSpec {
pub fn flatten(&self, parent_path: &str) -> Vec<(String, bool)> {
let mut result = Vec::new();
let path = if parent_path.is_empty() {
self.name.clone()
} else {
format!("{}::{}", parent_path, self.name)
};
result.push((path.clone(), self.is_pub));
for child in &self.children {
result.extend(child.flatten(&path));
}
result
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct CommonTypesSpec {
#[serde(default)]
pub newtypes: Vec<NewtypeSpec>,
#[serde(default)]
pub value_objects: Vec<EntitySpec>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NewtypeSpec {
pub name: String,
pub inner: String,
pub module: String,
#[serde(default)]
pub derives: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EntitySpec {
pub name: String,
#[serde(default)]
pub module: String,
#[serde(default)]
pub kind: EntityKind,
#[serde(default)]
pub fields: Vec<FieldSpec>,
#[serde(default)]
pub variants: Vec<VariantSpec>,
#[serde(default)]
pub derives: Vec<String>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum EntityKind {
#[default]
Struct,
Enum,
Newtype,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FieldSpec {
pub name: String,
#[serde(rename = "type")]
pub ty: String,
#[serde(default)]
pub is_pub: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum VariantSpec {
Unit(String),
Complex {
name: String,
#[serde(rename = "type", default)]
variant_type: Option<String>,
},
}
impl VariantSpec {
pub fn name(&self) -> &str {
match self {
VariantSpec::Unit(name) => name,
VariantSpec::Complex { name, .. } => name,
}
}
pub fn variant_type(&self) -> Option<&str> {
match self {
VariantSpec::Unit(_) => None,
VariantSpec::Complex { variant_type, .. } => variant_type.as_deref(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ErrorSpec {
pub name: String,
pub module: String,
#[serde(default)]
pub kind: EntityKind,
#[serde(default)]
pub variants: Vec<VariantSpec>,
#[serde(default)]
pub derives: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImplSpec {
pub target: String,
#[serde(default)]
pub trait_name: Option<String>,
#[serde(default)]
pub methods: Vec<MethodSpec>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MethodSpec {
pub name: String,
#[serde(default)]
pub self_param: Option<SelfParamSpec>,
#[serde(default)]
pub params: Vec<(String, String)>,
#[serde(default)]
pub return_type: Option<String>,
#[serde(default = "default_body")]
pub body: String,
#[serde(default)]
pub is_pub: bool,
}
fn default_body() -> String {
"todo!()".to_string()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SelfParamSpec {
Ref,
Mut,
Owned,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind")]
pub enum RefactorSpec {
AddBuilderPattern { targets: Vec<String> },
AddFromInto { pairs: Vec<(String, String)> },
AddDefault { targets: Vec<String> },
OrganizeImports {
#[serde(default)]
target_modules: TargetModules,
},
}
#[derive(Debug, Clone, Default, Serialize)]
pub enum TargetModules {
#[default]
All,
List(Vec<String>),
}
impl<'de> Deserialize<'de> for TargetModules {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::{self, Visitor};
struct TargetModulesVisitor;
impl<'de> Visitor<'de> for TargetModulesVisitor {
type Value = TargetModules;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str(r#""all" or a list of module names"#)
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
if v == "all" {
Ok(TargetModules::All)
} else {
Err(de::Error::custom(format!(
"expected 'all' or a list, got '{}'",
v
)))
}
}
fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
where
A: de::SeqAccess<'de>,
{
let modules = Vec::deserialize(de::value::SeqAccessDeserializer::new(seq))?;
Ok(TargetModules::List(modules))
}
fn visit_unit<E>(self) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(TargetModules::All)
}
}
deserializer.deserialize_any(TargetModulesVisitor)
}
}
impl TargetModules {
pub fn is_all(&self) -> bool {
matches!(self, TargetModules::All)
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct VerificationSpec {
#[serde(default)]
pub phase1: Vec<VerificationPointSpec>,
#[serde(default)]
pub phase2: Vec<VerificationPointSpec>,
#[serde(default)]
pub phase3: Vec<VerificationPointSpec>,
#[serde(default)]
pub phase4: Vec<VerificationPointSpec>,
#[serde(default)]
pub phase5: Vec<VerificationPointSpec>,
#[serde(default)]
pub phase6: Vec<VerificationPointSpec>,
#[serde(default, rename = "final")]
pub final_: Vec<VerificationPointSpec>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind")]
pub enum VerificationPointSpec {
ModuleExists { paths: Vec<String> },
TypeExists { types: Vec<String> },
HasDerive {
#[serde(rename = "type")]
ty: String,
derives: Vec<String>,
},
HasField {
#[serde(rename = "type")]
ty: String,
field: String,
field_type: String,
},
MethodExists {
#[serde(rename = "type")]
ty: String,
method: String,
},
Compiles,
NoWarnings {
#[serde(default)]
allowed: Vec<String>,
},
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_module_flatten() {
let module = ModuleSpec {
name: "lib".to_string(),
is_pub: true,
children: vec![
ModuleSpec {
name: "user".to_string(),
is_pub: true,
children: vec![],
},
ModuleSpec {
name: "common".to_string(),
is_pub: true,
children: vec![ModuleSpec {
name: "types".to_string(),
is_pub: true,
children: vec![],
}],
},
],
};
let flattened = module.flatten("");
assert_eq!(flattened.len(), 4);
assert!(flattened.iter().any(|(p, _)| p == "lib"));
assert!(flattened.iter().any(|(p, _)| p == "lib::user"));
assert!(flattened.iter().any(|(p, _)| p == "lib::common"));
assert!(flattened.iter().any(|(p, _)| p == "lib::common::types"));
}
#[test]
fn test_variant_spec_parsing() {
let yaml = r#""Active""#;
let variant: VariantSpec = serde_yaml::from_str(yaml).unwrap();
assert_eq!(variant.name(), "Active");
assert!(variant.variant_type().is_none());
let yaml = r#"{ name: "NotFound", type: "struct:entity:String" }"#;
let variant: VariantSpec = serde_yaml::from_str(yaml).unwrap();
assert_eq!(variant.name(), "NotFound");
assert_eq!(variant.variant_type(), Some("struct:entity:String"));
}
}