1use ignore::Walk;
3use itertools::Itertools;
4use rayon::iter::{IntoParallelIterator, ParallelIterator};
5use std::{
6 collections::{hash_map::Entry, HashMap, HashSet},
7 path::PathBuf,
8};
9use syn::{
10 punctuated::Punctuated, visit::Visit, Attribute, Expr, ExprGroup, ExprLit, ExprParen, Fields,
11 GenericParam, Ident, ItemConst, ItemEnum, ItemStruct, ItemType, Lit, Meta, Token,
12};
13
14use typeshare_model::{
15 decorator::{self, DecoratorSet},
16 prelude::*,
17};
18
19use crate::{
20 rename::RenameExt,
21 type_parser::{parse_rust_type, parse_rust_type_from_string},
22 visitors::TypeShareVisitor,
23 FileParseErrors, ParseError, ParseErrorKind, ParseErrorSet,
24};
25
26const SERDE: &str = "serde";
27const TYPESHARE: &str = "typeshare";
28
29#[non_exhaustive]
32#[derive(Debug, Clone, PartialEq)]
33pub enum RustItem {
34 Struct(RustStruct),
36 Enum(RustEnum),
38 Alias(RustTypeAlias),
40 Const(RustConst),
42}
43
44#[derive(Default, Debug)]
46pub struct ParsedData {
47 pub structs: Vec<RustStruct>,
49 pub enums: Vec<RustEnum>,
51 pub aliases: Vec<RustTypeAlias>,
53 pub consts: Vec<RustConst>,
55 pub import_types: HashSet<ImportedType>,
60}
61
62impl ParsedData {
63 pub fn merge(&mut self, other: Self) {
64 self.structs.extend(other.structs);
65 self.enums.extend(other.enums);
66 self.aliases.extend(other.aliases);
67 self.import_types.extend(other.import_types);
68 }
69
70 pub fn add(&mut self, item: RustItem) {
71 match item {
72 RustItem::Struct(rust_struct) => self.structs.push(rust_struct),
73 RustItem::Enum(rust_enum) => self.enums.push(rust_enum),
74 RustItem::Alias(rust_type_alias) => self.aliases.push(rust_type_alias),
75 RustItem::Const(rust_const) => self.consts.push(rust_const),
76 }
77 }
78
79 pub fn all_type_names(&self) -> impl Iterator<Item = &'_ TypeName> + use<'_> {
80 let s = self.structs.iter().map(|s| &s.id.renamed);
81 let e = self.enums.iter().map(|e| &e.shared().id.renamed);
82 let a = self.aliases.iter().map(|a| &a.id.renamed);
83
84 s.chain(e).chain(a)
85 }
86
87 pub fn sort_contents(&mut self) {
88 self.structs
89 .sort_unstable_by(|lhs, rhs| Ord::cmp(&lhs.id.original, &rhs.id.original));
90
91 self.enums.sort_unstable_by(|lhs, rhs| {
92 Ord::cmp(&lhs.shared().id.original, &rhs.shared().id.original)
93 });
94
95 self.aliases
96 .sort_unstable_by(|lhs, rhs| Ord::cmp(&lhs.id.original, &rhs.id.original));
97 }
98}
99
100#[derive(Debug)]
102pub struct ParserInput {
103 file_path: PathBuf,
105 crate_name: Option<CrateName>,
107}
108
109pub fn parser_inputs(walker_builder: Walk) -> Vec<ParserInput> {
111 walker_builder
112 .filter_map(Result::ok)
113 .filter(|dir_entry| !dir_entry.path().is_dir())
114 .map(|dir_entry| {
115 let path = dir_entry.path();
116 let crate_name = CrateName::find_crate_name(path);
117 let file_path = path.to_path_buf();
118
119 ParserInput {
120 file_path,
121 crate_name,
122 }
123 })
124 .collect()
125}
126
127fn add_parsed_data(
151 container: &mut HashMap<Option<CrateName>, ParsedData>,
152 crate_name: Option<CrateName>,
153 parsed_data: ParsedData,
154) {
155 match container.entry(crate_name) {
156 Entry::Vacant(entry) => {
157 entry.insert(parsed_data);
158 }
159 Entry::Occupied(entry) => {
160 entry.into_mut().merge(parsed_data);
161 }
162 }
163}
164
165pub fn parse_input(
167 inputs: Vec<ParserInput>,
168 ignored_types: &[&str],
169 mode: FilesMode<()>,
170) -> Result<HashMap<Option<CrateName>, ParsedData>, Vec<FileParseErrors>> {
171 inputs
172 .into_par_iter()
173 .map(|parser_input| {
174 let content = std::fs::read_to_string(&parser_input.file_path).map_err(|err| {
177 FileParseErrors::new(
178 parser_input.file_path.clone(),
179 parser_input.crate_name.clone(),
180 crate::FileErrorKind::ReadError(err),
181 )
182 })?;
183
184 let parsed_data = parse(
185 &content,
186 ignored_types,
187 match mode {
188 FilesMode::Single => FilesMode::Single,
189 FilesMode::Multi(()) => match parser_input.crate_name {
190 None => {
191 return Err(FileParseErrors::new(
192 parser_input.file_path.clone(),
193 parser_input.crate_name,
194 crate::FileErrorKind::UnknownCrate,
195 ))
196 }
197 Some(ref crate_name) => FilesMode::Multi(crate_name),
198 },
199 },
200 )
201 .map_err(|err| {
202 FileParseErrors::new(
203 parser_input.file_path.clone(),
204 parser_input.crate_name.clone(),
205 crate::FileErrorKind::ParseErrors(err),
206 )
207 })?;
208
209 let parsed_data = parsed_data.and_then(|parsed_data| {
210 if is_parsed_data_empty(&parsed_data) {
211 None
212 } else {
213 Some(parsed_data)
214 }
215 });
216
217 Ok(parsed_data.map(|parsed_data| (parser_input.crate_name, parsed_data)))
218 })
219 .filter_map(|data| data.transpose())
220 .fold(
221 || Ok(HashMap::new()),
222 |mut accum, result| {
223 match (&mut accum, result) {
224 (Ok(accum), Ok((crate_name, parsed_data))) => {
225 add_parsed_data(accum, crate_name, parsed_data)
226 }
227 (Ok(_), Err(error)) => {
228 accum = Err(Vec::from([error]));
229 }
230 (Err(accum), Err(error)) => accum.push(error),
231 (Err(_), Ok(_)) => {}
232 }
233
234 accum
235 },
236 )
237 .reduce(
238 || Ok(HashMap::new()),
239 |old, new| match (old, new) {
240 (Ok(mut old), Ok(new)) => {
241 new.into_iter().for_each(|(crate_name, parsed_data)| {
242 add_parsed_data(&mut old, crate_name, parsed_data)
243 });
244 Ok(old)
245 }
246 (Err(errors), Ok(_)) | (Ok(_), Err(errors)) => Err(errors),
247 (Err(mut err1), Err(err2)) => {
248 err1.extend(err2);
249 Err(err1)
250 }
251 },
252 )
253}
254
255fn is_parsed_data_empty(parsed_data: &ParsedData) -> bool {
257 parsed_data.enums.is_empty() && parsed_data.aliases.is_empty() && parsed_data.structs.is_empty()
258}
259
260pub fn parse(
262 source_code: &str,
263 ignored_types: &[&str],
264 file_mode: FilesMode<&CrateName>,
265) -> Result<Option<ParsedData>, ParseErrorSet> {
266 if !source_code.contains("#[typeshare") {
269 return Ok(None);
270 }
271
272 let mut import_visitor = TypeShareVisitor::new(ignored_types, file_mode);
275 let file_contents = syn::parse_file(source_code)
276 .map_err(|err| ParseError::new(&err.span(), ParseErrorKind::SynError(err)))?;
277
278 import_visitor.visit_file(&file_contents);
279
280 import_visitor.parsed_data().map(Some)
281}
282
283pub(crate) fn parse_struct(s: &ItemStruct) -> Result<RustItem, ParseError> {
289 let serde_rename_all = serde_rename_all(&s.attrs);
290
291 let generic_types = s
292 .generics
293 .params
294 .iter()
295 .filter_map(|param| match param {
296 GenericParam::Type(type_param) => Some(TypeName::new(&type_param.ident)),
297 _ => None,
298 })
299 .collect();
300
301 let decorators = get_decorators(&s.attrs);
302
303 if let Some(ty) = get_serialized_as_type(&decorators) {
307 return Ok(RustItem::Alias(RustTypeAlias {
308 id: get_ident(Some(&s.ident), &s.attrs, &None),
309 ty: parse_rust_type_from_string(&ty)?,
310 comments: parse_comment_attrs(&s.attrs),
311 generic_types,
312 }));
313 }
314
315 Ok(match &s.fields {
316 Fields::Named(f) => {
318 let fields = f
319 .named
320 .iter()
321 .filter(|field| !is_skipped(&field.attrs))
322 .map(|f| {
323 let decorators = get_decorators(&f.attrs);
324
325 let ty = match get_serialized_as_type(&decorators) {
326 Some(ty) => parse_rust_type_from_string(&ty)?,
327 None => parse_rust_type(&f.ty)?,
328 };
329
330 if serde_flatten(&f.attrs) {
331 return Err(ParseError::new(&f, ParseErrorKind::SerdeFlattenNotAllowed));
332 }
333
334 let has_default = serde_default(&f.attrs);
335
336 Ok(RustField {
337 id: get_ident(f.ident.as_ref(), &f.attrs, &serde_rename_all),
338 ty,
339 comments: parse_comment_attrs(&f.attrs),
340 has_default,
341 decorators,
342 })
343 })
344 .collect::<Result<_, ParseError>>()?;
345
346 RustItem::Struct(RustStruct {
347 id: get_ident(Some(&s.ident), &s.attrs, &None),
348 generic_types,
349 fields,
350 comments: parse_comment_attrs(&s.attrs),
351 decorators,
352 })
353 }
354 Fields::Unnamed(f) => {
356 let Some(f) = f.unnamed.iter().exactly_one().ok() else {
357 return Err(ParseError::new(f, ParseErrorKind::ComplexTupleStruct));
358 };
359
360 let decorators = get_decorators(&f.attrs);
361
362 let ty = match get_serialized_as_type(&decorators) {
363 Some(ty) => parse_rust_type_from_string(&ty)?,
364 None => parse_rust_type(&f.ty)?,
365 };
366
367 RustItem::Alias(RustTypeAlias {
368 id: get_ident(Some(&s.ident), &s.attrs, &None),
369 ty: ty,
370 comments: parse_comment_attrs(&s.attrs),
371 generic_types,
372 })
373 }
374 Fields::Unit => RustItem::Struct(RustStruct {
376 id: get_ident(Some(&s.ident), &s.attrs, &None),
377 generic_types,
378 fields: vec![],
379 comments: parse_comment_attrs(&s.attrs),
380 decorators,
381 }),
382 })
383}
384
385pub(crate) fn parse_enum(e: &ItemEnum) -> Result<RustItem, ParseError> {
391 let generic_types = e
392 .generics
393 .params
394 .iter()
395 .filter_map(|param| match param {
396 GenericParam::Type(type_param) => Some(TypeName::new(&type_param.ident)),
397 _ => None,
398 })
399 .collect();
400
401 let serde_rename_all = serde_rename_all(&e.attrs);
402 let decorators = get_decorators(&e.attrs);
403
404 if let Some(ty) = get_serialized_as_type(&decorators) {
407 return Ok(RustItem::Alias(RustTypeAlias {
408 id: get_ident(Some(&e.ident), &e.attrs, &None),
409 ty: parse_rust_type_from_string(&ty)?,
410 comments: parse_comment_attrs(&e.attrs),
411 generic_types,
412 }));
413 }
414
415 let original_enum_ident = TypeName::new(&e.ident);
416
417 let maybe_tag_key = get_tag_key(&e.attrs);
419 let maybe_content_key = get_content_key(&e.attrs);
420
421 let variants = e
423 .variants
424 .iter()
425 .filter(|v| !is_skipped(&v.attrs))
427 .map(|v| parse_enum_variant(v, &serde_rename_all))
428 .collect::<Result<Vec<_>, _>>()?;
429
430 let is_recursive = variants.iter().any(|v| match v {
432 RustEnumVariant::Unit(_) => false,
433 RustEnumVariant::Tuple { ty, .. } => ty.contains_type(&original_enum_ident),
434 RustEnumVariant::AnonymousStruct { fields, .. } => fields
435 .iter()
436 .any(|f| f.ty.contains_type(&original_enum_ident)),
437 _ => panic!("unrecgonized enum type"),
438 });
439
440 let shared = RustEnumShared {
441 id: get_ident(Some(&e.ident), &e.attrs, &None),
442 comments: parse_comment_attrs(&e.attrs),
443 decorators,
444 generic_types,
445 is_recursive,
446 };
447
448 if variants
450 .iter()
451 .all(|v| matches!(v, RustEnumVariant::Unit(_)))
452 {
453 if maybe_tag_key.is_some() {
455 return Err(ParseError::new(
456 &e,
457 ParseErrorKind::SerdeTagNotAllowed {
458 enum_ident: original_enum_ident,
459 },
460 ));
461 }
462 if maybe_content_key.is_some() {
463 return Err(ParseError::new(
464 &e,
465 ParseErrorKind::SerdeContentNotAllowed {
466 enum_ident: original_enum_ident,
467 },
468 ));
469 }
470
471 Ok(RustItem::Enum(RustEnum::Unit {
472 shared,
473 unit_variants: variants
474 .into_iter()
475 .map(|variant| match variant {
476 RustEnumVariant::Unit(unit) => unit,
477 _ => unreachable!("non-unit variant; this was checked earlier"),
478 })
479 .collect(),
480 }))
481 } else {
482 Ok(RustItem::Enum(RustEnum::Algebraic {
484 tag_key: maybe_tag_key.ok_or_else(|| {
485 ParseError::new(
486 &e,
487 ParseErrorKind::SerdeTagRequired {
488 enum_ident: original_enum_ident.clone(),
489 },
490 )
491 })?,
492 content_key: maybe_content_key.ok_or_else(|| {
493 ParseError::new(
494 &e,
495 ParseErrorKind::SerdeContentRequired {
496 enum_ident: original_enum_ident.clone(),
497 },
498 )
499 })?,
500 shared,
501 variants,
502 }))
503 }
504}
505
506fn parse_enum_variant(
508 v: &syn::Variant,
509 enum_serde_rename_all: &Option<String>,
510) -> Result<RustEnumVariant, ParseError> {
511 let shared = RustEnumVariantShared {
512 id: get_ident(Some(&v.ident), &v.attrs, enum_serde_rename_all),
513 comments: parse_comment_attrs(&v.attrs),
514 };
515
516 let variant_serde_rename_all = serde_rename_all(&v.attrs);
522
523 match &v.fields {
524 syn::Fields::Unit => Ok(RustEnumVariant::Unit(shared)),
525 syn::Fields::Unnamed(associated_type) => {
526 let Some(field) = associated_type.unnamed.iter().exactly_one().ok() else {
527 return Err(ParseError::new(
528 associated_type,
529 ParseErrorKind::MultipleUnnamedAssociatedTypes,
530 ));
531 };
532 let decorators = get_decorators(&field.attrs);
533
534 let ty = match get_serialized_as_type(&decorators) {
535 Some(ty) => parse_rust_type_from_string(&ty)?,
536 None => parse_rust_type(&field.ty)?,
537 };
538
539 Ok(RustEnumVariant::Tuple { ty, shared })
540 }
541 syn::Fields::Named(fields_named) => Ok(RustEnumVariant::AnonymousStruct {
542 fields: fields_named
543 .named
544 .iter()
545 .filter(|f| !is_skipped(&f.attrs))
546 .map(|f| {
547 let decorators = get_decorators(&f.attrs);
548
549 let field_type = match get_serialized_as_type(&decorators) {
550 Some(ty) => parse_rust_type_from_string(&ty)?,
551 None => parse_rust_type(&f.ty)?,
552 };
553
554 let has_default = serde_default(&f.attrs);
555
556 Ok(RustField {
557 id: get_ident(f.ident.as_ref(), &f.attrs, &variant_serde_rename_all),
558 ty: field_type,
559 comments: parse_comment_attrs(&f.attrs),
560 has_default,
561 decorators,
562 })
563 })
564 .try_collect()?,
565 shared,
566 }),
567 }
568}
569
570pub(crate) fn parse_type_alias(t: &ItemType) -> Result<RustItem, ParseError> {
573 let decorators = get_decorators(&t.attrs);
574
575 let ty = match get_serialized_as_type(&decorators) {
576 Some(ty) => parse_rust_type_from_string(&ty)?,
577 None => parse_rust_type(&t.ty)?,
578 };
579
580 let generic_types = t
581 .generics
582 .params
583 .iter()
584 .filter_map(|param| match param {
585 GenericParam::Type(type_param) => Some(TypeName::new(&type_param.ident)),
586 _ => None,
587 })
588 .collect();
589
590 Ok(RustItem::Alias(RustTypeAlias {
591 id: get_ident(Some(&t.ident), &t.attrs, &None),
592 ty,
593 comments: parse_comment_attrs(&t.attrs),
594 generic_types,
595 }))
596}
597
598pub(crate) fn parse_const(c: &ItemConst) -> Result<RustItem, ParseError> {
600 let expr = parse_const_expr(&c.expr)?;
601 let decorators = get_decorators(&c.attrs);
602
603 let ty = match get_serialized_as_type(&decorators) {
606 Some(ty) => parse_rust_type_from_string(ty)?,
607 None => parse_rust_type(&c.ty)?,
608 };
609
610 match &ty {
611 RustType::Special(SpecialRustType::HashMap(_, _))
612 | RustType::Special(SpecialRustType::Vec(_))
613 | RustType::Special(SpecialRustType::Option(_)) => {
614 return Err(ParseError::new(&c.ty, ParseErrorKind::RustConstTypeInvalid));
615 }
616 RustType::Special(_) => (),
617 RustType::Simple { .. } => (),
618 _ => return Err(ParseError::new(&c.ty, ParseErrorKind::RustConstTypeInvalid)),
619 };
620
621 Ok(RustItem::Const(RustConst {
622 id: get_ident(Some(&c.ident), &c.attrs, &None),
623 ty,
624 expr,
625 }))
626}
627
628fn parse_const_expr(e: &Expr) -> Result<RustConstExpr, ParseError> {
629 let value = match e {
630 Expr::Lit(ExprLit {
631 lit: Lit::Int(lit), ..
632 }) => lit
633 .base10_parse()
634 .map_err(|_| ParseError::new(&lit, ParseErrorKind::RustConstExprInvalid))?,
635
636 Expr::Group(ExprGroup { expr, .. }) | Expr::Paren(ExprParen { expr, .. }) => {
637 return parse_const_expr(expr)
638 }
639 _ => return Err(ParseError::new(e, ParseErrorKind::RustConstExprInvalid)),
640 };
641
642 Ok(RustConstExpr::Int(value))
643}
644
645pub(crate) fn has_typeshare_annotation(attrs: &[syn::Attribute]) -> bool {
649 attrs
650 .iter()
651 .flat_map(|attr| attr.path().segments.clone())
652 .any(|segment| segment.ident == TYPESHARE)
653}
654
655pub(crate) fn serde_rename_all(attrs: &[syn::Attribute]) -> Option<String> {
656 get_name_value_meta_items(attrs, "rename_all", SERDE).next()
657}
658
659pub(crate) fn get_serialized_as_type(decorators: &DecoratorSet) -> Option<&str> {
660 match decorators.get("serialized_as")?.first()? {
662 decorator::Value::String(s) => Some(s),
663 _ => None,
664 }
665}
666
667pub(crate) fn get_name_value_meta_items<'a>(
668 attrs: &'a [syn::Attribute],
669 name: &'a str,
670 ident: &'static str,
671) -> impl Iterator<Item = String> + 'a {
672 attrs.iter().flat_map(move |attr| {
673 get_meta_items(attr, ident)
674 .iter()
675 .filter_map(|arg| match arg {
676 Meta::NameValue(name_value) if name_value.path.is_ident(name) => {
677 expr_to_string(&name_value.value)
678 }
679 _ => None,
680 })
681 .collect::<Vec<_>>()
682 })
683}
684
685fn get_meta_items(attr: &syn::Attribute, ident: &str) -> Vec<Meta> {
687 if attr.path().is_ident(ident) {
688 attr.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)
689 .iter()
690 .flat_map(|meta| meta.iter())
691 .cloned()
692 .collect()
693 } else {
694 Vec::default()
695 }
696}
697
698fn get_ident(ident: Option<&Ident>, attrs: &[syn::Attribute], rename_all: &Option<String>) -> Id {
699 let original = ident.map_or("???".to_string(), |id| id.to_string().replace("r#", ""));
700
701 let mut renamed = rename_all_to_case(original.clone(), rename_all);
702
703 if let Some(s) = serde_rename(attrs) {
704 renamed = s;
705 }
706
707 Id {
708 original: TypeName::new_string(original),
709 renamed: TypeName::new_string(renamed),
710 }
711}
712
713fn rename_all_to_case(original: String, case: &Option<String>) -> String {
714 match case {
717 None => original,
718 Some(value) => match value.as_str() {
719 "lowercase" => original.to_lowercase(),
720 "UPPERCASE" => original.to_uppercase(),
721 "PascalCase" => original.to_pascal_case(),
722 "camelCase" => original.to_camel_case(),
723 "snake_case" => original.to_snake_case(),
724 "SCREAMING_SNAKE_CASE" => original.to_screaming_snake_case(),
725 "kebab-case" => original.to_kebab_case(),
726 "SCREAMING-KEBAB-CASE" => original.to_screaming_kebab_case(),
727 _ => original,
728 },
729 }
730}
731
732fn serde_rename(attrs: &[syn::Attribute]) -> Option<String> {
733 get_name_value_meta_items(attrs, "rename", SERDE).next()
734}
735
736fn parse_comment_attrs(attrs: &[Attribute]) -> Vec<String> {
738 attrs
739 .iter()
740 .map(|attr| attr.meta.clone())
741 .filter_map(|meta| match meta {
742 Meta::NameValue(name_value) if name_value.path.is_ident("doc") => {
743 expr_to_string(&name_value.value)
744 }
745 _ => None,
746 })
747 .collect()
748}
749
750fn is_skipped(attrs: &[syn::Attribute]) -> bool {
752 attrs.iter().any(|attr| {
753 get_meta_items(attr, SERDE)
754 .into_iter()
755 .chain(get_meta_items(attr, TYPESHARE))
756 .any(|arg| matches!(arg, Meta::Path(path) if path.is_ident("skip")))
757 })
758}
759
760fn serde_attr(attrs: &[syn::Attribute], ident: &str) -> bool {
761 attrs.iter().any(|attr| {
762 get_meta_items(attr, SERDE)
763 .iter()
764 .any(|arg| matches!(arg, Meta::Path(path) if path.is_ident(ident)))
765 })
766}
767
768fn serde_default(attrs: &[syn::Attribute]) -> bool {
769 serde_attr(attrs, "default")
770}
771
772fn serde_flatten(attrs: &[syn::Attribute]) -> bool {
773 serde_attr(attrs, "flatten")
774}
775
776fn get_decorators(attrs: &[Attribute]) -> DecoratorSet {
780 attrs
781 .iter()
782 .flat_map(|attr| match attr.meta {
783 Meta::List(ref meta) => Some(meta),
784 Meta::Path(_) | Meta::NameValue(_) => None,
785 })
786 .filter(|meta| meta.path.is_ident(TYPESHARE))
787 .filter_map(|meta| {
788 meta.parse_args_with(Punctuated::<KeyMaybeValue, Token![,]>::parse_terminated)
789 .ok()
790 })
791 .flatten()
792 .fold(DecoratorSet::new(), |mut set, decorator| {
793 set.entry(decorator.key).or_default().push(decorator.value);
794
795 set
796 })
797}
798
799fn expr_to_string(expr: &Expr) -> Option<String> {
800 match expr {
801 Expr::Lit(expr_lit) => literal_to_string(&expr_lit.lit),
802 _ => None,
803 }
804}
805
806fn literal_to_string(lit: &syn::Lit) -> Option<String> {
807 match lit {
808 syn::Lit::Str(str) => Some(str.value().trim().to_string()),
809 _ => None,
810 }
811}
812
813fn get_tag_key(attrs: &[syn::Attribute]) -> Option<String> {
814 get_name_value_meta_items(attrs, "tag", SERDE).next()
815}
816
817fn get_content_key(attrs: &[syn::Attribute]) -> Option<String> {
818 get_name_value_meta_items(attrs, "content", SERDE).next()
819}
820
821struct KeyMaybeValue {
824 key: String,
825 value: decorator::Value,
826}
827
828impl syn::parse::Parse for KeyMaybeValue {
829 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
830 let key: Ident = input.parse()?;
831 let eq: Option<syn::Token![=]> = input.parse()?;
832 let value: Option<syn::Lit> = eq.map(|_| input.parse()).transpose()?;
833
834 let value = match value {
835 None => decorator::Value::None,
836 Some(syn::Lit::Str(lit)) => decorator::Value::String(lit.value()),
837 Some(syn::Lit::Int(lit)) => decorator::Value::Int(lit.base10_parse()?),
838 Some(syn::Lit::Bool(lit)) => decorator::Value::Bool(lit.value),
839 Some(lit) => {
840 return Err(syn::Error::new(
841 lit.span(),
842 "unsupported decorator type (need string, int, or bool)",
843 ))
844 }
845 };
846
847 Ok(KeyMaybeValue {
848 key: key.to_string(),
849 value,
850 })
851 }
852}
853
854#[test]
855fn test_rename_all_to_case() {
856 let test_word = "test_case";
857
858 let tests = [
859 ("lowercase", "test_case"),
860 ("UPPERCASE", "TEST_CASE"),
861 ("PascalCase", "TestCase"),
862 ("camelCase", "testCase"),
863 ("snake_case", "test_case"),
864 ("SCREAMING_SNAKE_CASE", "TEST_CASE"),
865 ("kebab-case", "test-case"),
866 ("SCREAMING-KEBAB-CASE", "TEST-CASE"),
867 ("invalid case", "test_case"),
868 ];
869
870 for test in tests {
871 assert_eq!(
872 rename_all_to_case(test_word.to_string(), &Some(test.0.to_string())),
873 test.1
874 );
875 }
876}