use syn::{Attribute, DeriveInput, Field, Lit, Meta};
use syn::spanned::Spanned;
use crate::common::{MacroError, MacroResult};
use crate::parser::default_value::DefaultValue;
#[derive(Debug, Clone, Default)]
pub struct NodeConfig {
pub node_type: Option<String>,
pub desc: Option<String>,
pub marks: Option<String>,
pub content: Option<String>,
pub attr_fields: Vec<FieldConfig>,
pub id_field: Option<FieldConfig>,
}
#[derive(Debug, Clone, Default)]
pub struct MarkConfig {
pub mark_type: Option<String>,
pub attr_fields: Vec<FieldConfig>,
}
#[derive(Debug, Clone)]
pub struct FieldConfig {
pub name: String,
pub type_name: String,
pub is_optional: bool,
pub is_attr: bool,
pub field: Field,
pub default_value: Option<DefaultValue>,
}
pub struct AttributeParser;
impl AttributeParser {
pub fn parse_node_attributes(
input: &DeriveInput
) -> MacroResult<NodeConfig> {
let mut config = NodeConfig::default();
for attr in &input.attrs {
match attr.path().get_ident().map(|i| i.to_string()).as_deref() {
Some("node_type") => {
config.node_type =
Some(Self::parse_string_attribute(attr)?);
},
Some("marks") => {
let marks_str = Self::parse_string_attribute(attr)?;
config.marks = Some(marks_str);
},
Some("content") => {
config.content = Some(Self::parse_string_attribute(attr)?);
},
Some("desc") => {
config.desc = Some(Self::parse_string_attribute(attr)?);
},
_ => {
},
}
}
if config.node_type.is_none() {
return Err(MacroError::missing_attribute("node_type", input));
}
config.attr_fields = Self::parse_field_attributes(input)?;
config.id_field = Self::parse_id_field(input)?;
Ok(config)
}
pub fn parse_mark_attributes(
input: &DeriveInput
) -> MacroResult<MarkConfig> {
let mut config = MarkConfig::default();
for attr in &input.attrs {
if let Some(ident) = attr.path().get_ident() {
if ident == "mark_type" {
config.mark_type =
Some(Self::parse_string_attribute(attr)?);
}
}
}
if config.mark_type.is_none() {
return Err(MacroError::missing_attribute("mark_type", input));
}
config.attr_fields = Self::parse_field_attributes(input)?;
Ok(config)
}
fn parse_string_attribute(attr: &Attribute) -> MacroResult<String> {
match &attr.meta {
Meta::NameValue(meta) => match &meta.value {
syn::Expr::Lit(expr_lit) => match &expr_lit.lit {
Lit::Str(lit_str) => {
let value = lit_str.value();
if value.is_empty() {
return Err(MacroError::invalid_attribute_value(
&attr
.path()
.get_ident()
.map(|i| i.to_string())
.unwrap_or_default(),
&value,
"属性值不能为空字符串",
attr,
));
}
Ok(value)
},
_ => Err(MacroError::invalid_attribute_value(
&attr
.path()
.get_ident()
.map(|i| i.to_string())
.unwrap_or_default(),
"非字符串值",
"属性值必须是字符串字面量",
attr,
)),
},
_ => Err(MacroError::invalid_attribute_value(
&attr
.path()
.get_ident()
.map(|i| i.to_string())
.unwrap_or_default(),
"复杂表达式",
"属性值必须是字符串字面量",
attr,
)),
},
_ => Err(MacroError::parse_error(
"属性格式错误,期望 key = \"value\" 格式",
attr,
)),
}
}
fn parse_space_separated_list(input: &str) -> Vec<String> {
input
.split_whitespace()
.map(|s| s.to_string())
.filter(|s| !s.is_empty())
.collect()
}
fn parse_field_attr_attribute(
field: &Field
) -> MacroResult<(bool, Option<DefaultValue>)> {
use syn::{Meta};
let mut is_attr = false;
let mut default_value = None;
let mut attr_count = 0;
for attr in &field.attrs {
if let Some(ident) = attr.path().get_ident() {
if ident == "attr" {
attr_count += 1;
is_attr = true;
if attr_count > 1 {
return Err(MacroError::parse_error(
"字段不能有多个 #[attr] 属性",
field,
));
}
match &attr.meta {
Meta::Path(_) => {
},
Meta::List(meta_list) => {
default_value =
Self::parse_attr_meta_list(meta_list, field)?;
},
Meta::NameValue(_) => {
return Err(MacroError::parse_error(
"不支持 #[attr = \"value\"] 语法,请使用 #[attr(default=\"value\")]",
field,
));
},
}
}
}
}
Ok((is_attr, default_value))
}
fn parse_attr_meta_list(
meta_list: &syn::MetaList,
field: &Field,
) -> MacroResult<Option<DefaultValue>> {
use syn::{Meta, Token, parse::ParseStream, parse::Parse};
use crate::parser::default_value::DefaultValueParser;
struct MetaArgs {
metas: Vec<syn::Meta>,
}
impl Parse for MetaArgs {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut metas = Vec::new();
while !input.is_empty() {
metas.push(input.parse::<syn::Meta>()?);
if !input.is_empty() {
input.parse::<Token![,]>()?;
}
}
Ok(MetaArgs { metas })
}
}
let args: MetaArgs = meta_list.parse_args().map_err(|e| {
MacroError::parse_error(
&format!("无法解析 attr 属性参数: {e}"),
field,
)
})?;
let mut default_value = None;
for nested_meta in args.metas {
match nested_meta {
Meta::NameValue(name_value) => {
if let Some(ident) = name_value.path.get_ident() {
if ident == "default" {
if default_value.is_some() {
return Err(MacroError::parse_error(
"不能有多个 default 参数",
field,
));
}
let value_str = Self::extract_value_from_expr(
&name_value.value,
)?;
default_value = Some(DefaultValueParser::parse(
&value_str,
Some(name_value.value.span()),
)?);
} else {
return Err(MacroError::parse_error(
&format!("不支持的 attr 参数: {ident}"),
field,
));
}
}
},
_ => {
return Err(MacroError::parse_error(
"attr 参数必须是 name=value 形式,如 default=\"value\"",
field,
));
},
}
}
Ok(default_value)
}
fn extract_value_from_expr(expr: &syn::Expr) -> MacroResult<String> {
use syn::Lit;
match expr {
syn::Expr::Lit(expr_lit) => {
match &expr_lit.lit {
Lit::Str(lit_str) => Ok(lit_str.value()),
Lit::Int(lit_int) => {
Ok(lit_int.base10_digits().to_string())
},
Lit::Float(lit_float) => {
Ok(lit_float.base10_digits().to_string())
},
Lit::Bool(lit_bool) => Ok(lit_bool.value.to_string()),
_ => {
Ok(quote::quote! { #expr_lit }.to_string())
},
}
},
syn::Expr::Path(expr_path) => {
if let Some(ident) = expr_path.path.get_ident() {
match ident.to_string().as_str() {
"true" => Ok("true".to_string()),
"false" => Ok("false".to_string()),
"null" => Ok("null".to_string()),
other => Ok(other.to_string()),
}
} else {
Ok(quote::quote! { #expr_path }.to_string())
}
},
syn::Expr::Unary(expr_unary) => {
if matches!(expr_unary.op, syn::UnOp::Neg(_)) {
let inner =
Self::extract_value_from_expr(&expr_unary.expr)?;
Ok(format!("-{inner}"))
} else {
Ok(quote::quote! { #expr_unary }.to_string())
}
},
_ => {
let token_stream = quote::quote! { #expr };
let mut result = token_stream.to_string();
result = result.replace(" ", "");
if (result.starts_with('{') && result.ends_with('}'))
|| (result.starts_with('[') && result.ends_with(']'))
{
result = quote::quote! { #expr }.to_string();
}
Ok(result)
},
}
}
fn parse_field_attributes(
input: &DeriveInput
) -> MacroResult<Vec<FieldConfig>> {
let mut fields = Vec::new();
match &input.data {
syn::Data::Struct(data_struct) => {
match &data_struct.fields {
syn::Fields::Named(named_fields) => {
for field in &named_fields.named {
if let Some(field_name) = &field.ident {
let (is_attr, default_value) =
Self::parse_field_attr_attribute(field)?;
if is_attr {
let field_ty = &field.ty;
let type_name = quote::quote! { #field_ty }
.to_string()
.replace(" ", "");
let is_optional =
crate::common::utils::is_option_type(
&field.ty,
);
fields.push(FieldConfig {
name: field_name.to_string(),
type_name,
is_optional,
is_attr: true,
field: field.clone(),
default_value, });
}
}
}
},
syn::Fields::Unnamed(_) => {
return Err(MacroError::parse_error(
"不支持元组结构体,请使用具名字段的结构体",
input,
));
},
syn::Fields::Unit => {
},
}
},
syn::Data::Enum(_) => {
return Err(MacroError::parse_error(
"不支持枚举类型,请使用结构体",
input,
));
},
syn::Data::Union(_) => {
return Err(MacroError::parse_error(
"不支持联合体类型,请使用结构体",
input,
));
},
}
Ok(fields)
}
fn parse_id_field(input: &DeriveInput) -> MacroResult<Option<FieldConfig>> {
let mut id_field = None;
match &input.data {
syn::Data::Struct(data_struct) => {
match &data_struct.fields {
syn::Fields::Named(named_fields) => {
for field in &named_fields.named {
if let Some(field_name) = &field.ident {
let has_id_attr =
Self::check_id_attribute(field)?;
if has_id_attr {
if id_field.is_some() {
return Err(MacroError::parse_error(
"一个结构体只能有一个 #[id] 字段",
field,
));
}
let field_ty = &field.ty;
let type_name = quote::quote! { #field_ty }
.to_string()
.replace(" ", "");
let is_optional =
crate::common::utils::is_option_type(
&field.ty,
);
id_field = Some(FieldConfig {
name: field_name.to_string(),
type_name,
is_optional,
is_attr: false, field: field.clone(),
default_value: None, });
}
}
}
},
syn::Fields::Unnamed(_) => {
return Err(MacroError::parse_error(
"不支持元组结构体,请使用具名字段的结构体",
input,
));
},
syn::Fields::Unit => {
},
}
},
syn::Data::Enum(_) => {
return Err(MacroError::parse_error(
"不支持枚举类型,请使用结构体",
input,
));
},
syn::Data::Union(_) => {
return Err(MacroError::parse_error(
"不支持联合体类型,请使用结构体",
input,
));
},
}
Ok(id_field)
}
fn check_id_attribute(field: &Field) -> MacroResult<bool> {
let mut id_count = 0;
for attr in &field.attrs {
if let Some(ident) = attr.path().get_ident() {
if ident == "id" {
id_count += 1;
if id_count > 1 {
return Err(MacroError::parse_error(
"字段不能有多个 #[id] 属性",
field,
));
}
match &attr.meta {
syn::Meta::Path(_) => {
},
syn::Meta::List(_) => {
return Err(MacroError::parse_error(
"#[id] 属性不支持参数,请使用简单的 #[id] 格式",
field,
));
},
syn::Meta::NameValue(_) => {
return Err(MacroError::parse_error(
"#[id] 属性不支持赋值,请使用简单的 #[id] 格式",
field,
));
},
}
}
}
}
Ok(id_count > 0)
}
}
impl NodeConfig {
pub fn validate(&self) -> MacroResult<()> {
if self.node_type.is_none() {
return Err(MacroError::ValidationError {
message: "缺少必需的 node_type 属性".to_string(),
span: None,
});
}
if let Some(marks) = &self.marks {
if marks.trim().is_empty() {
return Err(MacroError::ValidationError {
message: "marks 属性不能为空字符串".to_string(),
span: None,
});
}
for mark in marks.split_whitespace() {
if !crate::common::utils::is_valid_identifier(mark) {
return Err(MacroError::ValidationError {
message: format!("无效的标记名称: '{mark}'"),
span: None,
});
}
}
}
Ok(())
}
pub fn marks_string(&self) -> Option<String> {
self.marks.clone()
}
}
impl MarkConfig {
pub fn validate(&self) -> MacroResult<()> {
if self.mark_type.is_none() {
return Err(MacroError::ValidationError {
message: "缺少必需的 mark_type 属性".to_string(),
span: None,
});
}
if let Some(mark_type) = &self.mark_type {
if !crate::common::utils::is_valid_identifier(mark_type) {
return Err(MacroError::ValidationError {
message: format!("无效的标记类型名称: '{mark_type}'"),
span: None,
});
}
}
Ok(())
}
}
impl FieldConfig {
pub fn new(
name: String,
type_name: String,
is_optional: bool,
is_attr: bool,
field: Field,
) -> Self {
Self {
name,
type_name,
is_optional,
is_attr,
field,
default_value: None, }
}
pub fn with_default_value(
mut self,
default_value: DefaultValue,
) -> Self {
self.default_value = Some(default_value);
self
}
pub fn has_default_value(&self) -> bool {
self.default_value.is_some()
}
pub fn get_default_value(&self) -> Option<&DefaultValue> {
self.default_value.as_ref()
}
pub fn get_default_value_mut(&mut self) -> Option<&mut DefaultValue> {
self.default_value.as_mut()
}
pub fn set_default_value(
&mut self,
default_value: Option<DefaultValue>,
) {
self.default_value = default_value;
}
}
#[cfg(test)]
mod tests {
use super::*;
use syn::parse_quote;
#[test]
fn test_parse_basic_node_attributes() {
let input: DeriveInput = parse_quote! {
#[derive(Node)]
#[node_type = "paragraph"]
struct ParagraphNode {
#[attr]
content: String,
}
};
let result = AttributeParser::parse_node_attributes(&input);
assert!(result.is_ok());
let config = result.unwrap();
assert_eq!(config.node_type, Some("paragraph".to_string()));
assert_eq!(config.attr_fields.len(), 1);
assert_eq!(config.attr_fields[0].name, "content");
assert!(!config.attr_fields[0].is_optional);
}
#[test]
fn test_parse_full_node_attributes() {
let input: DeriveInput = parse_quote! {
#[derive(Node)]
#[node_type = "paragraph"]
#[marks = "bold italic underline"]
#[content = "text*"]
struct ParagraphNode {
#[attr]
content: String,
#[attr]
alignment: Option<String>,
private_field: i32,
}
};
let result = AttributeParser::parse_node_attributes(&input);
assert!(result.is_ok());
let config = result.unwrap();
assert_eq!(config.node_type, Some("paragraph".to_string()));
assert_eq!(config.marks, Some("bold italic underline".to_string()));
assert_eq!(config.content, Some("text*".to_string()));
assert_eq!(config.attr_fields.len(), 2);
assert_eq!(config.attr_fields[0].name, "content");
assert!(!config.attr_fields[0].is_optional);
assert_eq!(config.attr_fields[1].name, "alignment");
assert!(config.attr_fields[1].is_optional);
}
#[test]
fn test_parse_basic_mark_attributes() {
let input: DeriveInput = parse_quote! {
#[derive(Mark)]
#[mark_type = "bold"]
struct BoldMark {
#[attr]
strength: i32,
}
};
let result = AttributeParser::parse_mark_attributes(&input);
assert!(result.is_ok());
let config = result.unwrap();
assert_eq!(config.mark_type, Some("bold".to_string()));
assert_eq!(config.attr_fields.len(), 1);
assert_eq!(config.attr_fields[0].name, "strength");
}
#[test]
fn test_missing_required_attribute_error() {
let input: DeriveInput = parse_quote! {
#[derive(Node)]
struct InvalidNode {
#[attr]
content: String,
}
};
let result = AttributeParser::parse_node_attributes(&input);
assert!(result.is_err());
if let Err(MacroError::MissingAttribute { attribute, .. }) = result {
assert_eq!(attribute, "node_type");
} else {
panic!("期望 MissingAttribute 错误");
}
}
#[test]
fn test_empty_attribute_value_error() {
let input: DeriveInput = parse_quote! {
#[derive(Node)]
#[node_type = ""] struct InvalidNode {
#[attr]
content: String,
}
};
let result = AttributeParser::parse_node_attributes(&input);
assert!(result.is_err());
if let Err(MacroError::InvalidAttributeValue { reason, .. }) = result {
assert!(reason.contains("不能为空"));
} else {
panic!("期望 InvalidAttributeValue 错误");
}
}
#[test]
fn test_parse_space_separated_list() {
let result = AttributeParser::parse_space_separated_list(
"bold italic underline",
);
assert_eq!(result, vec!["bold", "italic", "underline"]);
let result = AttributeParser::parse_space_separated_list(
"bold italic underline",
);
assert_eq!(result, vec!["bold", "italic", "underline"]);
let result = AttributeParser::parse_space_separated_list(
" bold italic underline ",
);
assert_eq!(result, vec!["bold", "italic", "underline"]);
let result = AttributeParser::parse_space_separated_list("bold");
assert_eq!(result, vec!["bold"]);
let result = AttributeParser::parse_space_separated_list("");
assert_eq!(result, Vec::<String>::new());
let result = AttributeParser::parse_space_separated_list(" ");
assert_eq!(result, Vec::<String>::new());
}
#[test]
fn test_unsupported_struct_types() {
let input: DeriveInput = parse_quote! {
#[derive(Node)]
#[node_type = "tuple"]
struct TupleStruct(String, i32);
};
let result = AttributeParser::parse_node_attributes(&input);
assert!(result.is_err());
let input: DeriveInput = parse_quote! {
#[derive(Node)]
#[node_type = "enum"]
enum EnumType {
Variant1,
Variant2,
}
};
let result = AttributeParser::parse_node_attributes(&input);
assert!(result.is_err());
}
#[test]
#[allow(clippy::field_reassign_with_default)]
fn test_node_config_validation() {
let mut config = NodeConfig::default();
config.node_type = Some("paragraph".to_string());
assert!(config.validate().is_ok());
let config = NodeConfig::default();
assert!(config.validate().is_err());
let mut config = NodeConfig::default();
config.node_type = Some("paragraph".to_string());
config.marks = Some("".to_string());
assert!(config.validate().is_err());
let mut config = NodeConfig::default();
config.node_type = Some("paragraph".to_string());
config.marks = Some("invalid-mark".to_string());
assert!(config.validate().is_err());
}
#[test]
#[allow(clippy::field_reassign_with_default)]
fn test_mark_config_validation() {
let mut config = MarkConfig::default();
config.mark_type = Some("bold".to_string());
assert!(config.validate().is_ok());
let config = MarkConfig::default();
assert!(config.validate().is_err());
let mut config = MarkConfig::default();
config.mark_type = Some("invalid-type".to_string());
assert!(config.validate().is_err());
}
#[test]
fn test_node_config_marks_string() {
let mut config = NodeConfig::default();
assert_eq!(config.marks_string(), None);
config.marks = Some("bold italic".to_string());
assert_eq!(config.marks_string(), Some("bold italic".to_string()));
}
#[test]
fn test_field_config_backward_compatibility() {
use syn::parse_quote;
let field: Field = parse_quote! { content: String };
let field_config = FieldConfig::new(
"content".to_string(),
"String".to_string(),
false,
true,
field,
);
assert_eq!(field_config.name, "content");
assert_eq!(field_config.type_name, "String");
assert!(!field_config.is_optional);
assert!(field_config.is_attr);
assert!(!field_config.has_default_value()); assert!(field_config.get_default_value().is_none());
}
#[test]
fn test_field_config_default_value_methods() {
use syn::parse_quote;
use crate::parser::default_value::{DefaultValueType, DefaultValueParser};
let field: Field = parse_quote! { content: String };
let mut field_config = FieldConfig::new(
"content".to_string(),
"String".to_string(),
false,
true,
field,
);
assert!(!field_config.has_default_value());
assert!(field_config.get_default_value().is_none());
let default_value =
DefaultValueParser::parse("hello world", None).unwrap();
field_config.set_default_value(Some(default_value.clone()));
assert!(field_config.has_default_value());
assert!(field_config.get_default_value().is_some());
let stored_value = field_config.get_default_value().unwrap();
assert_eq!(stored_value.raw_value, "hello world");
assert!(
matches!(stored_value.value_type, DefaultValueType::String(ref s) if s == "hello world")
);
field_config.set_default_value(None);
assert!(!field_config.has_default_value());
let field2: Field = parse_quote! { title: String };
let field_config2 = FieldConfig::new(
"title".to_string(),
"String".to_string(),
false,
true,
field2,
)
.with_default_value(default_value);
assert!(field_config2.has_default_value());
assert_eq!(
field_config2.get_default_value().unwrap().raw_value,
"hello world"
);
}
#[test]
fn test_field_config_mutable_default_value() {
use syn::parse_quote;
use crate::parser::default_value::{DefaultValueParser};
let field: Field = parse_quote! { content: String };
let default_value = DefaultValueParser::parse("initial", None).unwrap();
let mut field_config = FieldConfig::new(
"content".to_string(),
"String".to_string(),
false,
true,
field,
)
.with_default_value(default_value);
if let Some(default_value_mut) = field_config.get_default_value_mut() {
assert_eq!(default_value_mut.raw_value, "initial");
} else {
panic!("应该有默认值");
}
}
#[test]
fn test_parse_field_attr_with_default_values() {
use syn::parse_quote;
use crate::parser::default_value::{DefaultValueType};
let field: Field = parse_quote! {
#[attr]
content: String
};
let (is_attr, default_value) =
AttributeParser::parse_field_attr_attribute(&field).unwrap();
assert!(is_attr);
assert!(default_value.is_none());
let field: Field = parse_quote! {
#[attr(default = "hello world")]
content: String
};
let (is_attr, default_value) =
AttributeParser::parse_field_attr_attribute(&field).unwrap();
assert!(is_attr);
assert!(default_value.is_some());
let default_val = default_value.unwrap();
assert_eq!(default_val.raw_value, "hello world");
assert!(
matches!(default_val.value_type, DefaultValueType::String(ref s) if s == "hello world")
);
let field: Field = parse_quote! {
#[attr(default = 42)]
count: i32
};
let (is_attr, default_value) =
AttributeParser::parse_field_attr_attribute(&field).unwrap();
assert!(is_attr);
assert!(default_value.is_some());
let default_val = default_value.unwrap();
assert_eq!(default_val.raw_value, "42");
assert!(matches!(
default_val.value_type,
DefaultValueType::Integer(42)
));
let field: Field = parse_quote! {
#[attr(default = true)]
enabled: bool
};
let (is_attr, default_value) =
AttributeParser::parse_field_attr_attribute(&field).unwrap();
assert!(is_attr);
assert!(default_value.is_some());
let default_val = default_value.unwrap();
assert_eq!(default_val.raw_value, "true");
assert!(matches!(
default_val.value_type,
DefaultValueType::Boolean(true)
));
}
#[test]
fn test_parse_id_field() {
use syn::parse_quote;
let input: DeriveInput = parse_quote! {
#[derive(Node)]
#[node_type = "test_node"]
struct TestNode {
#[id]
node_id: String,
#[attr]
content: String,
}
};
let config = AttributeParser::parse_node_attributes(&input).unwrap();
assert!(config.id_field.is_some());
let id_field = config.id_field.unwrap();
assert_eq!(id_field.name, "node_id");
assert_eq!(id_field.type_name, "String");
assert!(!id_field.is_optional);
assert!(!id_field.is_attr); assert!(id_field.default_value.is_none()); }
#[test]
fn test_parse_optional_id_field() {
use syn::parse_quote;
let input: DeriveInput = parse_quote! {
#[derive(Node)]
#[node_type = "test_node"]
struct TestNode {
#[id]
node_id: Option<String>,
#[attr]
content: String,
}
};
let config = AttributeParser::parse_node_attributes(&input).unwrap();
assert!(config.id_field.is_some());
let id_field = config.id_field.unwrap();
assert_eq!(id_field.name, "node_id");
assert_eq!(id_field.type_name, "Option<String>");
assert!(id_field.is_optional);
}
#[test]
fn test_parse_no_id_field() {
use syn::parse_quote;
let input: DeriveInput = parse_quote! {
#[derive(Node)]
#[node_type = "test_node"]
struct TestNode {
#[attr]
content: String,
}
};
let config = AttributeParser::parse_node_attributes(&input).unwrap();
assert!(config.id_field.is_none());
}
#[test]
fn test_multiple_id_fields_error() {
use syn::parse_quote;
let input: DeriveInput = parse_quote! {
#[derive(Node)]
#[node_type = "test_node"]
struct TestNode {
#[id]
node_id1: String,
#[id]
node_id2: String,
#[attr]
content: String,
}
};
let result = AttributeParser::parse_node_attributes(&input);
assert!(result.is_err());
if let Err(error) = result {
let error_msg = format!("{error:?}");
assert!(error_msg.contains("一个结构体只能有一个"));
}
}
#[test]
fn test_duplicate_id_attribute_error() {
use syn::parse_quote;
let input: DeriveInput = parse_quote! {
#[derive(Node)]
#[node_type = "test_node"]
struct TestNode {
#[id]
#[id]
node_id: String,
#[attr]
content: String,
}
};
let result = AttributeParser::parse_node_attributes(&input);
assert!(result.is_err());
if let Err(error) = result {
let error_msg = format!("{error:?}");
assert!(error_msg.contains("多个 #[id] 属性"));
}
}
#[test]
fn test_id_attribute_with_params_error() {
use syn::parse_quote;
let input: DeriveInput = parse_quote! {
#[derive(Node)]
#[node_type = "test_node"]
struct TestNode {
#[id(param = "value")]
node_id: String,
#[attr]
content: String,
}
};
let result = AttributeParser::parse_node_attributes(&input);
assert!(result.is_err());
if let Err(error) = result {
let error_msg = format!("{error:?}");
assert!(error_msg.contains("不支持参数"));
}
}
#[test]
fn test_id_attribute_with_value_error() {
use syn::parse_quote;
let input: DeriveInput = parse_quote! {
#[derive(Node)]
#[node_type = "test_node"]
struct TestNode {
#[id = "value"]
node_id: String,
#[attr]
content: String,
}
};
let result = AttributeParser::parse_node_attributes(&input);
assert!(result.is_err());
if let Err(error) = result {
let error_msg = format!("{error:?}");
assert!(error_msg.contains("不支持赋值"));
}
}
#[test]
fn test_complete_parsing_with_id_and_attr_fields() {
use syn::parse_quote;
let input: DeriveInput = parse_quote! {
#[derive(Node)]
#[node_type = "complex_node"]
#[marks = "bold italic"]
#[content = "text*"]
struct ComplexNode {
#[id]
node_id: String,
#[attr]
title: String,
#[attr(default = "default content")]
content: String,
#[attr]
optional_field: Option<String>,
internal_data: Vec<u8>,
}
};
let config = AttributeParser::parse_node_attributes(&input).unwrap();
assert_eq!(config.node_type, Some("complex_node".to_string()));
assert_eq!(config.marks, Some("bold italic".to_string()));
assert_eq!(config.content, Some("text*".to_string()));
assert!(config.id_field.is_some());
let id_field = config.id_field.unwrap();
assert_eq!(id_field.name, "node_id");
assert_eq!(id_field.type_name, "String");
assert_eq!(config.attr_fields.len(), 3);
let title_field =
config.attr_fields.iter().find(|f| f.name == "title").unwrap();
assert_eq!(title_field.type_name, "String");
assert!(!title_field.has_default_value());
let content_field =
config.attr_fields.iter().find(|f| f.name == "content").unwrap();
assert_eq!(content_field.type_name, "String");
assert!(content_field.has_default_value());
assert_eq!(
content_field.get_default_value().unwrap().raw_value,
"default content"
);
let optional_field = config
.attr_fields
.iter()
.find(|f| f.name == "optional_field")
.unwrap();
assert_eq!(optional_field.type_name, "Option<String>");
assert!(optional_field.is_optional);
assert!(!optional_field.has_default_value());
}
#[test]
fn test_parse_field_attr_error_handling() {
use syn::parse_quote;
let field: Field = parse_quote! {
#[attr]
#[attr(default = "test")]
content: String
};
let result = AttributeParser::parse_field_attr_attribute(&field);
assert!(result.is_err());
let field: Field = parse_quote! {
#[attr = "value"]
content: String
};
let result = AttributeParser::parse_field_attr_attribute(&field);
assert!(result.is_err());
}
#[test]
fn test_extract_value_from_expr() {
use syn::parse_quote;
let expr: syn::Expr = parse_quote! { "hello" };
let result = AttributeParser::extract_value_from_expr(&expr).unwrap();
assert_eq!(result, "hello");
let expr: syn::Expr = parse_quote! { 42 };
let result = AttributeParser::extract_value_from_expr(&expr).unwrap();
assert_eq!(result, "42");
let expr: syn::Expr = parse_quote! { 3.14 };
let result = AttributeParser::extract_value_from_expr(&expr).unwrap();
assert_eq!(result, "3.14");
let expr: syn::Expr = parse_quote! { true };
let result = AttributeParser::extract_value_from_expr(&expr).unwrap();
assert_eq!(result, "true");
let expr: syn::Expr = parse_quote! { false };
let result = AttributeParser::extract_value_from_expr(&expr).unwrap();
assert_eq!(result, "false");
let expr: syn::Expr = parse_quote! { null };
let result = AttributeParser::extract_value_from_expr(&expr).unwrap();
assert_eq!(result, "null");
let expr: syn::Expr = parse_quote! { -42 };
let result = AttributeParser::extract_value_from_expr(&expr).unwrap();
assert_eq!(result, "-42");
}
#[test]
fn test_complete_field_parsing_with_defaults() {
use syn::parse_quote;
let input: syn::DeriveInput = parse_quote! {
#[derive(Node)]
#[node_type = "test_node"]
struct TestNode {
#[attr]
simple_field: String,
#[attr(default = "default value")]
field_with_default: String,
#[attr(default = 42)]
numeric_field: i32,
#[attr(default = true)]
boolean_field: bool,
regular_field: String,
}
};
let config = AttributeParser::parse_node_attributes(&input).unwrap();
assert_eq!(config.attr_fields.len(), 4);
let simple_field = config
.attr_fields
.iter()
.find(|f| f.name == "simple_field")
.expect("应该找到 simple_field");
assert!(!simple_field.has_default_value());
let field_with_default = config
.attr_fields
.iter()
.find(|f| f.name == "field_with_default")
.expect("应该找到 field_with_default");
assert!(field_with_default.has_default_value());
assert_eq!(
field_with_default.get_default_value().unwrap().raw_value,
"default value"
);
let numeric_field = config
.attr_fields
.iter()
.find(|f| f.name == "numeric_field")
.expect("应该找到 numeric_field");
assert!(numeric_field.has_default_value());
assert_eq!(numeric_field.get_default_value().unwrap().raw_value, "42");
let boolean_field = config
.attr_fields
.iter()
.find(|f| f.name == "boolean_field")
.expect("应该找到 boolean_field");
assert!(boolean_field.has_default_value());
assert_eq!(
boolean_field.get_default_value().unwrap().raw_value,
"true"
);
}
}