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