mod number;
use crate::prelude::*;
use darling::{Error as DarlingError, FromMeta, ast::NestedMeta};
use syn::{Lit, LitStr, Path};
pub use number::*;
#[derive(Clone, Debug)]
pub enum Arg {
Bool(bool),
Char(char),
Number(ArgNumber),
ConstPath(Path), FuncPath(Path), String(LitStr),
}
impl FromMeta for Arg {
fn from_value(value: &Lit) -> Result<Self, DarlingError> {
match value {
Lit::Bool(lit) => Ok(Self::Bool(lit.value)),
Lit::Char(lit) => Ok(Self::Char(lit.value())),
Lit::Int(_) | Lit::Float(_) => ArgNumber::from_value(value).map(Self::Number),
Lit::Str(lit) => {
if lit.value().contains("::") {
let path: Path = syn::parse_str(&lit.value())
.map_err(|_| DarlingError::custom("Failed to parse path"))?;
let last = path
.segments
.last()
.ok_or_else(|| DarlingError::custom("path has no segments"))?
.ident
.to_string();
if last.chars().next().is_some_and(char::is_uppercase) {
Ok(Self::ConstPath(path))
} else {
Ok(Self::FuncPath(path))
}
} else {
Ok(Self::String(lit.clone()))
}
}
_ => Err(DarlingError::custom(format!(
"Unsupported literal type: {value:?}"
))),
}
}
fn from_nested_meta(item: &NestedMeta) -> Result<Self, DarlingError> {
match item {
NestedMeta::Lit(lit) => Self::from_value(lit),
NestedMeta::Meta(syn::Meta::Path(path)) => {
let last = path
.segments
.last()
.ok_or_else(|| DarlingError::custom("path has no segments"))?
.ident
.to_string();
if last.chars().next().is_some_and(char::is_uppercase) {
Ok(Self::ConstPath(path.clone()))
} else {
Ok(Self::FuncPath(path.clone()))
}
}
NestedMeta::Meta(syn::Meta::NameValue(nv)) => Err(DarlingError::custom(format!(
"NameValue not supported here: {nv:?}"
))),
NestedMeta::Meta(syn::Meta::List(list)) => Err(DarlingError::custom(format!(
"Nested list not supported here: {list:?}"
))),
}
}
}
impl HasSchemaPart for Arg {
fn schema_part(&self) -> TokenStream {
match self {
Self::Bool(v) => quote!(::icydb_model::node::Arg::Bool(#v)),
Self::Char(v) => quote!(::icydb_model::node::Arg::Char(#v)),
Self::Number(v) => {
let num = quote_one(v, ArgNumber::schema_part);
quote!(::icydb_model::node::Arg::Number(#num))
}
Self::ConstPath(p) => {
let path = quote_one(p, to_str_lit);
quote!(::icydb_model::node::Arg::ConstPath(#path))
}
Self::FuncPath(p) => {
let path = quote_one(p, to_str_lit);
quote!(::icydb_model::node::Arg::FuncPath(#path))
}
Self::String(v) => quote!(::icydb_model::node::Arg::String(#v)),
}
}
}
impl ToTokens for Arg {
fn to_tokens(&self, tokens: &mut TokenStream) {
let q = match self {
Self::Bool(v) => quote!(#v),
Self::Char(v) => quote!(#v), Self::Number(v) => quote!(#v), Self::ConstPath(p) => quote!(#p), Self::FuncPath(p) => quote!(#p()), Self::String(v) => quote!(#v),
};
tokens.extend(q);
}
}
#[derive(Clone, Debug, Default)]
pub struct Args(Vec<Arg>);
impl Args {
#[must_use]
pub(crate) const fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub(crate) fn iter(&self) -> std::slice::Iter<'_, Arg> {
self.0.iter()
}
}
impl FromMeta for Args {
fn from_list(items: &[NestedMeta]) -> Result<Self, DarlingError> {
let mut args = Vec::new();
for item in items {
args.push(Arg::from_nested_meta(item)?);
}
Ok(Self(args))
}
}
impl HasSchemaPart for Args {
fn schema_part(&self) -> TokenStream {
let args = quote_slice(&self.0, Arg::schema_part);
quote! {
::icydb_model::node::Args(#args)
}
}
}
#[cfg(test)]
mod arg_tests {
use super::*;
use syn::parse_quote;
#[test]
fn test_bool_parsing() {
let lit = parse_quote!(true);
if let Ok(Arg::Bool(b)) = Arg::from_value(&lit) {
assert!(b, "Parsed boolean should be true");
} else {
panic!("Expected Bool variant");
}
}
#[test]
fn test_char_parsing() {
let lit = parse_quote!('a');
if let Ok(Arg::Char(c)) = Arg::from_value(&lit) {
assert_eq!(c, 'a', "Parsed char should be 'a'");
} else {
panic!("Expected Char variant");
}
}
#[test]
fn test_number_parsing() {
let lit = parse_quote!(42);
if let Ok(Arg::Number(num)) = Arg::from_value(&lit) {
assert_eq!(num, ArgNumber::Int32(42), "Parsed number does not match");
} else {
panic!("Expected Number variant");
}
}
#[test]
fn test_const_path_parsing() {
let arg: Arg = Arg::from_nested_meta(&parse_quote!(MY_CONST)).unwrap();
match arg {
Arg::ConstPath(path) => {
assert_eq!(path.segments.last().unwrap().ident.to_string(), "MY_CONST");
}
_ => panic!("Expected ConstPath variant"),
}
}
#[test]
fn test_func_path_parsing() {
let arg: Arg = Arg::from_nested_meta(&parse_quote!(my_func)).unwrap();
match arg {
Arg::FuncPath(path) => {
assert_eq!(path.segments.last().unwrap().ident.to_string(), "my_func");
}
_ => panic!("Expected FuncPath variant"),
}
}
#[test]
fn test_invalid_input() {
let lit = parse_quote!(b"invalid");
assert!(
Arg::from_value(&lit).is_err(),
"Expected an error for unsupported literal type."
);
}
}