1use proc_macro::TokenStream;
2use proc_macro2::{Span, TokenStream as TokenStream2};
3use quote::quote_spanned;
4use quote::{format_ident, quote, ToTokens};
5use std::cmp::Ordering;
6use std::collections::HashMap;
7use std::fmt::Debug;
8use syn::meta::ParseNestedMeta;
9use syn::parse::{Parse, ParseStream};
10use syn::spanned::Spanned;
11use syn::{ext::IdentExt, parse_macro_input, DeriveInput, Field, Ident, LitStr};
12use syn::{DataEnum, Fields, FieldsNamed, LitInt, Path, TypePath, Variant};
13
14#[proc_macro_derive(Parse, attributes(parse))]
140pub fn derive_parse(input: TokenStream) -> TokenStream {
141 let input = parse_macro_input!(input as DeriveInput);
143
144 match input.data {
145 syn::Data::Struct(data) => match data.fields {
146 syn::Fields::Named(fields) => {
147 let ty = input.ident;
148 if fields.named.is_empty() {
149 return TokenStream::from(impl_unit_parser(
150 &input.attrs,
151 &ty,
152 quote! { Self {} },
153 ));
154 }
155 let struct_parser = match StructParser::new(input.attrs, fields, ty) {
156 Ok(parser) => parser,
157 Err(err) => return err.to_compile_error().into(),
158 };
159
160 TokenStream::from(struct_parser.parser())
161 }
162 syn::Fields::Unit => {
163 let ty = input.ident;
164 TokenStream::from(impl_unit_parser(&input.attrs, &ty, quote! { Self }))
165 }
166 _ => syn::Error::new(
167 input.ident.span(),
168 "Only structs with named fields are supported",
169 )
170 .to_compile_error()
171 .into(),
172 },
173 syn::Data::Enum(data) => {
174 let ty = input.ident;
175 if data.variants.is_empty() {
176 return syn::Error::new(ty.span(), "Enums with no variants are not supported")
177 .to_compile_error()
178 .into();
179 }
180
181 let has_fields = data
182 .variants
183 .iter()
184 .any(|variant| !matches!(&variant.fields, syn::Fields::Unit));
185
186 if has_fields {
187 match EnumParser::new(input.attrs, data, ty)
188 .and_then(|parser| parser.quote_parser())
189 {
190 Ok(parser) => parser,
191 Err(err) => err.to_compile_error(),
192 }
193 } else {
194 unit_enum_parser(input.attrs, data, ty)
195 }
196 .into()
197 }
198 _ => syn::Error::new(
199 input.ident.span(),
200 "Only structs and unit value enums are supported",
201 )
202 .to_compile_error()
203 .into(),
204 }
205}
206
207#[proc_macro_derive(Schema, attributes(parse))]
208pub fn derive_schema(input: TokenStream) -> TokenStream {
209 let input = parse_macro_input!(input as DeriveInput);
211
212 match input.data {
213 syn::Data::Struct(data) => match data.fields {
214 syn::Fields::Named(fields) => {
215 let ty = input.ident;
216 if fields.named.is_empty() {
217 return TokenStream::from(unit_schema(&input.attrs, &ty));
218 }
219 let struct_parser = match StructParser::new(input.attrs, fields, ty) {
220 Ok(parser) => parser,
221 Err(err) => return err.to_compile_error().into(),
222 };
223
224 TokenStream::from(struct_parser.quote_schema())
225 }
226 syn::Fields::Unit => {
227 let ty = input.ident;
228 TokenStream::from(unit_schema(&input.attrs, &ty))
229 }
230 _ => syn::Error::new(
231 input.ident.span(),
232 "Only structs with named fields are supported",
233 )
234 .to_compile_error()
235 .into(),
236 },
237 syn::Data::Enum(data) => {
238 let ty = input.ident;
239 if data.variants.is_empty() {
240 return syn::Error::new(ty.span(), "Enums with no variants are not supported")
241 .to_compile_error()
242 .into();
243 }
244
245 let has_fields = data
246 .variants
247 .iter()
248 .any(|variant| !matches!(&variant.fields, syn::Fields::Unit));
249
250 if has_fields {
251 match EnumParser::new(input.attrs, data, ty)
252 .and_then(|parser| parser.quote_schema())
253 {
254 Ok(parser) => parser,
255 Err(err) => err.to_compile_error(),
256 }
257 } else {
258 unit_enum_schema(data, ty)
259 }
260 .into()
261 }
262 _ => syn::Error::new(
263 input.ident.span(),
264 "Only structs and unit value enums are supported",
265 )
266 .to_compile_error()
267 .into(),
268 }
269}
270
271struct StructParser {
272 attributes: Vec<syn::Attribute>,
273 ty: Ident,
274 name: String,
275 fields: FieldsParser,
276}
277
278impl StructParser {
279 fn new(attributes: Vec<syn::Attribute>, fields: FieldsNamed, ty: Ident) -> syn::Result<Self> {
280 let named = fields.named.into_iter().collect::<Vec<_>>();
281
282 let mut name = ty.unraw().to_string();
283 for attr in &attributes {
284 if attr.path().is_ident("parse") {
285 attr.parse_nested_meta(|meta| {
286 if let Some(value) = parse_rename_attribute(&meta)? {
287 name = value.value();
288 } else {
289 return Err(meta.error("expected `rename`"));
290 }
291 Ok(())
292 })?;
293 }
294 }
295
296 Ok(Self {
297 attributes,
298 name,
299 ty,
300 fields: FieldsParser::new(&named)?,
301 })
302 }
303
304 fn parser(&self) -> TokenStream2 {
305 let field_names = self
306 .fields
307 .fields
308 .iter()
309 .map(|f| f.field.ident.as_ref().unwrap());
310 let construct = quote! {
311 Self {
312 #(
313 #field_names
314 ),*
315 }
316 };
317
318 let parser = match self.fields.parser(construct) {
319 Ok(parser) => parser,
320 Err(err) => return err.to_compile_error(),
321 };
322
323 let ty = &self.ty;
324
325 quote! {
326 impl kalosm_sample::Parse for #ty {
327 fn new_parser() -> impl kalosm_sample::SendCreateParserState<Output = Self> {
328 #parser
329 }
330 }
331 }
332 }
333
334 fn quote_schema(&self) -> proc_macro2::TokenStream {
335 let title = &self.name;
336 let ty = &self.ty;
337 let description = doc_comment(&self.attributes);
338 let description = description.map(|description| quote! { .with_description(#description) });
339 let schema = self.fields.quote_schema();
340
341 quote! {
342 impl kalosm_sample::Schema for #ty {
343 fn schema() -> kalosm_sample::SchemaType {
344 kalosm_sample::SchemaType::Object(
345 #schema
346 .with_title(#title)
347 #description
348 )
349 }
350 }
351 }
352 }
353}
354
355fn quote_fields(fields: Fields) -> TokenStream2 {
356 match fields {
357 Fields::Named(fields) => {
358 let field_names = fields.named.iter().map(|f| f.ident.as_ref().unwrap());
359 quote! {
360 {
361 #(
362 #field_names
363 ),*
364 }
365 }
366 }
367 Fields::Unnamed(fields) => {
368 let field_names = (0..fields.unnamed.len()).map(|i| format_ident!("data{}", i));
369 quote! {
370 (
371 #(
372 #field_names
373 ),*
374 )
375 }
376 }
377 Fields::Unit => {
378 quote! {}
379 }
380 }
381}
382
383fn impl_unit_parser(attrs: &[syn::Attribute], ty: &Ident, construct: TokenStream2) -> TokenStream2 {
384 let unit_parser = unit_parser(attrs, ty);
385 quote! {
386 impl kalosm_sample::Parse for #ty {
387 fn new_parser() -> impl kalosm_sample::SendCreateParserState<Output = Self> {
388 kalosm_sample::ParserExt::map_output(
389 #unit_parser,
390 |_| #construct
391 )
392 }
393 }
394 }
395}
396
397fn unit_schema(attrs: &[syn::Attribute], ty: &Ident) -> TokenStream2 {
398 let name = match unit_parse_literal_name(attrs, ty) {
399 Ok(name) => name,
400 Err(err) => return err.to_compile_error(),
401 };
402
403 quote! {
404 impl kalosm_sample::Schema for #ty {
405 fn schema() -> kalosm_sample::SchemaType {
406 kalosm_sample::SchemaType::Enum(kalosm_sample::EnumSchema::new([
407 kalosm_sample::SchemaLiteral::String(#name.to_string())
408 ]))
409 }
410 }
411 }
412}
413
414fn unit_parse_literal(attrs: &[syn::Attribute], ty: &Ident, unquoted: bool) -> syn::Result<String> {
415 let ty_string = unit_parse_literal_name(attrs, ty)?;
416
417 Ok(if unquoted {
418 ty_string
419 } else {
420 format!("\"{ty_string}\"")
421 })
422}
423
424fn unit_parse_literal_name(attrs: &[syn::Attribute], ty: &Ident) -> syn::Result<String> {
425 let mut ty_string = ty.unraw().to_string();
427 for attr in attrs.iter() {
428 if attr.path().is_ident("parse") {
429 attr.parse_nested_meta(|meta| {
430 if let Some(value) = parse_rename_attribute(&meta)? {
431 ty_string = value.value();
432 Ok(())
433 } else {
434 Err(meta.error("expected `rename`"))
435 }
436 })?;
437 }
438 }
439
440 Ok(ty_string)
441}
442
443fn unit_parser(attrs: &[syn::Attribute], ty: &Ident) -> TokenStream2 {
444 let ty_string = match unit_parse_literal(attrs, ty, false) {
445 Ok(ty_string) => ty_string,
446 Err(err) => return err.to_compile_error(),
447 };
448 let ty_string = LitStr::new(&ty_string, ty.span());
449 quote! {
450 kalosm_sample::LiteralParser::new(#ty_string)
451 }
452}
453
454struct EnumParser {
455 ty: Ident,
456 tag: String,
457 data: String,
458 variants: Vec<EnumVariant>,
459}
460
461impl EnumParser {
462 fn new(attrs: Vec<syn::Attribute>, data: DataEnum, ty: Ident) -> syn::Result<Self> {
463 let mut tag = "type".to_string();
465 let mut content = "data".to_string();
466 for attr in attrs.iter() {
467 if attr.path().is_ident("parse") {
468 attr.parse_nested_meta(|meta| {
469 if meta.path.is_ident("tag") {
470 let value = meta
471 .value()
472 .and_then(|value| value.parse::<syn::LitStr>())?;
473 tag = value.value();
474 Ok(())
475 } else if meta.path.is_ident("content") {
476 let value = meta
477 .value()
478 .and_then(|value| value.parse::<syn::LitStr>())?;
479 content = value.value();
480 Ok(())
481 } else {
482 Err(meta.error("expected `tag` or `content`"))
483 }
484 })?;
485 }
486 }
487
488 let variants = data
489 .variants
490 .iter()
491 .map(EnumVariant::new)
492 .collect::<syn::Result<_>>()?;
493
494 Ok(EnumParser {
495 ty,
496 tag,
497 data: content,
498 variants,
499 })
500 }
501
502 fn quote_parser(&self) -> syn::Result<TokenStream2> {
503 let tag = &self.tag;
504 let ty = &self.ty;
505 let content = &self.data;
506 let mut parser = None;
507
508 for variant in &self.variants {
509 let parse_variant = variant.quote_parser(content)?;
510 match &mut parser {
511 Some(current) => {
512 *current = quote! {
513 kalosm_sample::ParserExt::or(
514 #current,
515 #parse_variant
516 )
517 };
518 }
519 None => {
520 parser = Some(parse_variant);
521 }
522 }
523 }
524
525 let struct_start = format!("{{ \"{tag}\": \"");
526
527 Ok(quote! {
528 impl kalosm_sample::Parse for #ty {
529 fn new_parser() -> impl kalosm_sample::SendCreateParserState<Output = Self> {
530 kalosm_sample::ParserExt::then_literal(
531 kalosm_sample::ParserExt::ignore_output_then(
532 kalosm_sample::LiteralParser::from(#struct_start),
533 #parser
534 ),
535 r#" }"#
536 )
537 }
538 }
539 })
540 }
541
542 fn quote_schema(&self) -> syn::Result<proc_macro2::TokenStream> {
543 let tag = &self.tag;
544 let content = &self.data;
545 let ty = &self.ty;
546
547 let variants: Vec<_> = self
548 .variants
549 .iter()
550 .map(|variant| {
551 let variant_name = &variant.name;
552 let variant_parser = variant.quote_schema(tag, content, variant_name)?;
553 Ok(quote! {
554 #variant_parser
555 })
556 })
557 .collect::<syn::Result<_>>()?;
558
559 Ok(quote! {
560 impl kalosm_sample::Schema for #ty {
561 fn schema() -> kalosm_sample::SchemaType {
562 kalosm_sample::SchemaType::AnyOf(
563 kalosm_sample::AnyOfSchema::new([
564 #(#variants),*
565 ])
566 )
567 }
568 }
569 })
570 }
571}
572
573struct EnumVariant {
574 variant: Variant,
575 name: String,
576 ty: EnumVariantType,
577}
578
579impl EnumVariant {
580 fn new(variant: &Variant) -> syn::Result<Self> {
581 let variant_ident = &variant.ident;
582 let mut variant_name = variant_ident.unraw().to_string();
583 for attr in variant.attrs.iter() {
585 if attr.path().is_ident("parse") {
586 attr.parse_nested_meta(|meta| {
587 if let Some(value) = parse_rename_attribute(&meta)? {
588 variant_name = value.value();
589 Ok(())
590 } else {
591 Err(meta.error("expected `rename`"))
592 }
593 })?;
594 }
595 }
596
597 let parse_variant = match &variant.fields {
598 syn::Fields::Named(fields) => {
599 EnumVariantType::Struct(StructEnumVariantParser::new(fields)?)
600 }
601 syn::Fields::Unnamed(fields) => {
602 let field_vec = fields.unnamed.iter().collect::<Vec<_>>();
603 let [inner] = *field_vec else {
604 return Err(syn::Error::new(
605 variant.ident.span(),
606 "Unnamed enum variants with more or less than one field are not supported",
607 ));
608 };
609
610 EnumVariantType::Tuple(TupleEnumVariantParser::new(inner))
611 }
612 syn::Fields::Unit => EnumVariantType::Unit(UnitEnumVariantParser::new()),
614 };
615
616 Ok(Self {
617 variant: variant.clone(),
618 name: variant_name,
619 ty: parse_variant,
620 })
621 }
622
623 fn construct_variant(&self) -> TokenStream2 {
624 let fields = quote_fields(self.variant.fields.clone());
625 let variant_ident = &self.variant.ident;
626 quote! {
627 Self::#variant_ident #fields
628 }
629 }
630
631 fn quote_parser(&self, content_name: &str) -> syn::Result<TokenStream2> {
632 let construct_variant = self.construct_variant();
633 match &self.ty {
634 EnumVariantType::Struct(parser) => {
635 parser.quote_parser(&self.name, content_name, construct_variant)
636 }
637 EnumVariantType::Tuple(parser) => {
638 parser.quote_parser(&self.name, content_name, construct_variant)
639 }
640 EnumVariantType::Unit(parser) => parser.quote_parser(&self.name, construct_variant),
641 }
642 }
643
644 fn quote_schema(
645 &self,
646 tag: &str,
647 content: &str,
648 variant_name: &str,
649 ) -> syn::Result<proc_macro2::TokenStream> {
650 match &self.ty {
651 EnumVariantType::Struct(parser) => parser.quote_schema(tag, content, variant_name),
652 EnumVariantType::Tuple(parser) => parser.quote_schema(tag, content, variant_name),
653 EnumVariantType::Unit(parser) => parser.quote_schema(tag, variant_name),
654 }
655 }
656}
657
658enum EnumVariantType {
659 Unit(UnitEnumVariantParser),
660 Tuple(TupleEnumVariantParser),
661 Struct(StructEnumVariantParser),
662}
663
664struct UnitEnumVariantParser {}
665
666impl UnitEnumVariantParser {
667 fn new() -> Self {
668 Self {}
669 }
670
671 fn quote_parser(
672 &self,
673 variant_name: &str,
674 construct_variant: TokenStream2,
675 ) -> syn::Result<TokenStream2> {
676 let lit_str_name = LitStr::new(&format!("{variant_name}\""), Span::call_site());
677 Ok(quote! {
678 kalosm_sample::ParserExt::map_output(
679 kalosm_sample::LiteralParser::from(#lit_str_name),
680 |_| #construct_variant
681 )
682 })
683 }
684
685 fn quote_schema(&self, tag: &str, variant_name: &str) -> syn::Result<proc_macro2::TokenStream> {
686 Ok(quote! {
687 kalosm_sample::SchemaType::Object(
688 kalosm_sample::JsonObjectSchema::new([
689 kalosm_sample::JsonPropertySchema::new(
690 #tag,
691 kalosm_sample::SchemaType::Enum(
692 kalosm_sample::EnumSchema::new([
693 kalosm_sample::SchemaLiteral::String(#variant_name.to_string())
694 ])
695 )
696 )
697 .with_required(true)
698 ])
699 )
700 })
701 }
702}
703
704struct StructEnumVariantParser {
705 fields: FieldsParser,
706}
707
708impl StructEnumVariantParser {
709 fn new(fields: &FieldsNamed) -> syn::Result<Self> {
710 let fields = fields.named.iter().cloned().collect::<Vec<_>>();
711 Ok(Self {
712 fields: FieldsParser::new(&fields)?,
713 })
714 }
715
716 fn quote_parser(
717 &self,
718 variant_name: &str,
719 content_name: &str,
720 construct_variant: TokenStream2,
721 ) -> syn::Result<TokenStream2> {
722 let parse_name_and_data = LitStr::new(
723 &format!("{variant_name}\", \"{content_name}\": "),
724 Span::call_site(),
725 );
726 let field_parser = self.fields.parser(construct_variant)?;
727 Ok(quote! {
728 kalosm_sample::ParserExt::ignore_output_then(
729 kalosm_sample::LiteralParser::from(#parse_name_and_data),
730 #field_parser
731 )
732 })
733 }
734
735 fn quote_schema(
736 &self,
737 tag: &str,
738 content: &str,
739 variant_name: &str,
740 ) -> syn::Result<proc_macro2::TokenStream> {
741 let variant_parser = self.fields.quote_schema();
742 Ok(quote! {
743 kalosm_sample::SchemaType::Object(
744 kalosm_sample::JsonObjectSchema::new([
745 kalosm_sample::JsonPropertySchema::new(
746 #tag,
747 kalosm_sample::SchemaType::Enum(
748 kalosm_sample::EnumSchema::new([
749 kalosm_sample::SchemaLiteral::String(#variant_name.to_string())
750 ])
751 )
752 )
753 .with_required(true),
754 kalosm_sample::JsonPropertySchema::new(
755 #content,
756 kalosm_sample::SchemaType::Object(
757 #variant_parser
758 )
759 )
760 .with_required(true)
761 ])
762 )
763 })
764 }
765}
766
767struct TupleEnumVariantParser {
768 field: Field,
769}
770
771impl TupleEnumVariantParser {
772 fn new(fields: &Field) -> Self {
773 Self {
774 field: fields.clone(),
775 }
776 }
777
778 fn quote_parser(
779 &self,
780 variant_name: &str,
781 content: &str,
782 construct_variant: TokenStream2,
783 ) -> syn::Result<TokenStream2> {
784 let parse_name_and_data = LitStr::new(
785 &format!("{variant_name}\", \"{content}\": "),
786 Span::call_site(),
787 );
788 let ty = &self.field.ty;
789 Ok(quote! {
790 kalosm_sample::ParserExt::map_output(
791 kalosm_sample::ParserExt::ignore_output_then(
792 kalosm_sample::LiteralParser::from(#parse_name_and_data),
793 <#ty as kalosm_sample::Parse>::new_parser()
794 ),
795 |data0| #construct_variant
796 )
797 })
798 }
799
800 fn quote_schema(
801 &self,
802 tag: &str,
803 content: &str,
804 variant_name: &str,
805 ) -> syn::Result<proc_macro2::TokenStream> {
806 let ty = &self.field.ty;
807 Ok(quote! {
808 kalosm_sample::SchemaType::Object(
809 kalosm_sample::JsonObjectSchema::new([
810 kalosm_sample::JsonPropertySchema::new(
811 #tag,
812 kalosm_sample::SchemaType::Enum(
813 kalosm_sample::EnumSchema::new([
814 kalosm_sample::SchemaLiteral::String(#variant_name.to_string())
815 ])
816 )
817 )
818 .with_required(true),
819 kalosm_sample::JsonPropertySchema::new(
820 #content,
821 <#ty as kalosm_sample::Schema>::schema()
822 )
823 .with_required(true)
824 ])
825 )
826 })
827 }
828}
829
830fn unit_enum_parser(attrs: Vec<syn::Attribute>, data: DataEnum, ty: Ident) -> TokenStream2 {
831 let parser_state = format_ident!("{}ParserState", ty);
833
834 let mut unquoted = false;
836 for attr in attrs.iter() {
837 if attr.path().is_ident("parse") {
838 let result = attr.parse_nested_meta(|meta| {
839 if meta.path.is_ident("unquoted") {
840 unquoted = true;
841 return Ok(());
842 }
843 Err(meta.error("expected `unquoted`"))
844 });
845 if let Err(err) = result {
846 return err.to_compile_error();
847 }
848 }
849 }
850
851 let mut parse_construction_map = HashMap::new();
852 for variant in data.variants.iter() {
853 let variant_name = &variant.ident;
854 let fields = &variant.fields;
855 let construct_variant = quote! {
856 #ty::#variant_name #fields
857 };
858 let literal_string = match unit_parse_literal(&variant.attrs, variant_name, unquoted) {
859 Ok(literal_string) => literal_string,
860 Err(err) => return err.to_compile_error(),
861 };
862 parse_construction_map.insert(literal_string.as_bytes().to_vec(), construct_variant);
863 }
864
865 let mut prefix_state_map = HashMap::new();
866 let mut max_state = 0usize;
867 for bytes in parse_construction_map.keys() {
868 for i in 0..bytes.len() + 1 {
869 let prefix = &bytes[..i];
870 if prefix_state_map.contains_key(prefix) {
871 continue;
872 }
873 prefix_state_map.insert(prefix, max_state);
874 max_state += 1;
875 }
876 }
877
878 let mut parse_states = Vec::new();
879 for (state_prefix, state) in &prefix_state_map {
880 let state = LitInt::new(&state.to_string(), ty.span());
881
882 let mut next_bytes = Vec::new();
883 for (next_state_prefix, next_state) in &prefix_state_map {
884 if let Some(&[byte]) = next_state_prefix.strip_prefix(*state_prefix) {
885 let next_state = LitInt::new(&next_state.to_string(), ty.span());
886
887 next_bytes.push(quote! {
888 #byte => state = #parser_state(#next_state),
889 });
890 }
891 }
892
893 let unrecognized_byte = if let Some(constructor) = parse_construction_map.get(*state_prefix)
894 {
895 quote! {
896 return kalosm_sample::ParseResult::Ok(kalosm_sample::ParseStatus::Finished {
897 result: #constructor,
898 remaining: &input[i..],
899 })
900 }
901 } else {
902 quote! {
903 return kalosm_sample::ParseResult::Err(kalosm_sample::ParserError::msg("Unrecognized byte"))
904 }
905 };
906
907 if !next_bytes.is_empty() {
908 parse_states.push(quote! {
909 #state => match byte {
910 #(#next_bytes)*
911 _ => #unrecognized_byte,
912 },
913 });
914 } else if parse_construction_map.contains_key(*state_prefix) {
915 parse_states.push(quote! {
916 #state => #unrecognized_byte,
917 });
918 }
919 }
920
921 let mut match_required_next = Vec::new();
922 for (state_prefix, state) in &prefix_state_map {
923 let state = LitInt::new(&state.to_string(), ty.span());
924 let mut required_next = Vec::new();
925 let mut current_prefix = state_prefix.to_vec();
926 loop {
927 let mut valid_next_bytes = Vec::new();
928 for prefix in prefix_state_map.keys() {
929 if let Some(&[byte]) = prefix.strip_prefix(current_prefix.as_slice()) {
930 valid_next_bytes.push(byte);
931 }
932 }
933 if let [byte] = *valid_next_bytes {
934 current_prefix.push(byte);
935 required_next.push(byte);
936 } else {
937 break;
938 }
939 }
940 if !required_next.is_empty() {
941 let required_next_str = String::from_utf8_lossy(&required_next);
942 match_required_next.push(quote! {
943 #state => #required_next_str,
944 });
945 }
946 }
947
948 let state_type = if max_state <= u8::MAX as usize {
949 quote! { u8 }
950 } else if max_state <= u16::MAX as usize {
951 quote! { u16 }
952 } else if max_state <= u32::MAX as usize {
953 quote! { u32 }
954 } else if max_state <= u64::MAX as usize {
955 quote! { u64 }
956 } else {
957 quote! { u128 }
958 };
959
960 let impl_parser_state = quote! {
961 #[derive(Debug, Clone, Copy)]
962 struct #parser_state(#state_type);
963
964 impl #parser_state {
965 const fn new() -> Self {
966 Self(0)
967 }
968 }
969 };
970
971 let parser = format_ident!("{}Parser", ty);
972 let impl_parser = quote! {
973 struct #parser;
974
975 impl kalosm_sample::CreateParserState for #parser {
976 fn create_parser_state(&self) -> <Self as kalosm_sample::Parser>::PartialState {
977 #parser_state::new()
978 }
979 }
980 impl kalosm_sample::Parser for #parser {
981 type Output = #ty;
982 type PartialState = #parser_state;
983
984 fn parse<'a>(
985 &self,
986 state: &Self::PartialState,
987 input: &'a [u8],
988 ) -> kalosm_sample::ParseResult<kalosm_sample::ParseStatus<'a, Self::PartialState, Self::Output>> {
989 let mut state = *state;
990 for (i, byte) in input.iter().enumerate() {
991 match state.0 {
992 #(#parse_states)*
993 _ => return kalosm_sample::ParseResult::Err(kalosm_sample::ParserError::msg("Invalid state")),
994 }
995 }
996 kalosm_sample::ParseResult::Ok(kalosm_sample::ParseStatus::Incomplete {
997 new_state: state,
998 required_next: std::borrow::Cow::Borrowed(match state.0 {
999 #(#match_required_next)*
1000 _ => ""
1001 }),
1002 })
1003 }
1004 }
1005 };
1006
1007 quote! {
1008 impl kalosm_sample::Parse for #ty {
1009 fn new_parser() -> impl kalosm_sample::SendCreateParserState<Output = Self> {
1010 #impl_parser_state
1011 #impl_parser
1012
1013 #parser
1014 }
1015 }
1016 }
1017}
1018
1019fn unit_enum_schema(data: DataEnum, ty: Ident) -> TokenStream2 {
1020 let mut variants = Vec::new();
1021 for variant in data.variants.iter() {
1022 let variant_name = &variant.ident;
1023 let literal_string = match unit_parse_literal_name(&variant.attrs, variant_name) {
1024 Ok(literal_string) => literal_string,
1025 Err(err) => return err.to_compile_error(),
1026 };
1027 variants.push(literal_string);
1028 }
1029
1030 let schema = unit_enum_schema_type(variants);
1031
1032 quote! {
1033 impl kalosm_sample::Schema for #ty {
1034 fn schema() -> kalosm_sample::SchemaType {
1035 #schema
1036 }
1037 }
1038 }
1039}
1040
1041fn unit_enum_schema_type(variants: impl IntoIterator<Item = String>) -> proc_macro2::TokenStream {
1042 let variants = variants.into_iter().map(|variant| {
1043 let variant = LitStr::new(&variant, variant.span());
1044 quote! {
1045 kalosm_sample::SchemaLiteral::String(#variant.to_string())
1046 }
1047 });
1048
1049 let schema = quote! {
1050 kalosm_sample::EnumSchema::new([#(#variants),*])
1051 };
1052
1053 quote! {
1054 kalosm_sample::SchemaType::Enum(#schema)
1055 }
1056}
1057
1058fn wrap_tuple(ident: &Ident, current: TokenStream2) -> TokenStream2 {
1059 quote! {
1060 (#current, #ident)
1061 }
1062}
1063
1064fn parse_rename_attribute(meta: &ParseNestedMeta) -> syn::Result<Option<LitStr>> {
1065 if meta.path.is_ident("rename") {
1066 let value = meta
1067 .value()
1068 .and_then(|value| value.parse::<syn::LitStr>())?;
1069 return Ok(Some(value));
1070 }
1071 Ok(None)
1072}
1073
1074struct FieldsParser {
1075 fields: Vec<FieldParser>,
1076}
1077
1078impl FieldsParser {
1079 fn new(fields: &[Field]) -> syn::Result<Self> {
1080 Ok(Self {
1081 fields: fields
1082 .iter()
1083 .map(FieldParser::new)
1084 .collect::<syn::Result<_>>()?,
1085 })
1086 }
1087
1088 fn parser(&self, construct: TokenStream2) -> syn::Result<TokenStream2> {
1089 let mut parsers = Vec::new();
1090 let idents: Vec<_> = self
1091 .fields
1092 .iter()
1093 .map(|f| format_ident!("{}_parser", f.field.ident.as_ref().unwrap().unraw()))
1094 .collect();
1095 for (i, (field, parser_ident)) in self.fields.iter().zip(idents.iter()).enumerate() {
1096 let mut literal_text = String::new();
1097 if i == 0 {
1098 literal_text.push_str("{ ");
1099 } else {
1100 literal_text.push_str(", ");
1101 }
1102 let field_name = &field.name;
1103 let field_parser = &field.parser;
1104 literal_text.push_str(&format!("\"{field_name}\": "));
1105 let literal_text = LitStr::new(&literal_text, field.field.ident.span());
1106
1107 parsers.push(quote! {
1108 let #parser_ident = kalosm_sample::ParserExt::ignore_output_then(
1109 kalosm_sample::LiteralParser::from(#literal_text),
1110 #field_parser
1111 );
1112 });
1113 }
1114
1115 let mut output_tuple = None;
1116 for field in self.fields.iter() {
1117 let name = field.field.ident.as_ref().unwrap();
1118 match output_tuple {
1119 Some(current) => {
1120 output_tuple = Some(wrap_tuple(name, current));
1121 }
1122 None => {
1123 output_tuple = Some(name.to_token_stream());
1124 }
1125 }
1126 }
1127
1128 let mut join_parser: Option<TokenStream2> = None;
1129 for ident in idents.iter() {
1130 match &mut join_parser {
1131 Some(current) => {
1132 *current = quote! {
1133 kalosm_sample::ParserExt::then(#current, #ident)
1134 };
1135 }
1136 None => {
1137 join_parser = Some(ident.to_token_stream());
1138 }
1139 }
1140 }
1141
1142 Ok(quote! {
1143 {
1144 #(
1145 #parsers
1146 )*
1147
1148 kalosm_sample::ParserExt::map_output(
1149 kalosm_sample::ParserExt::then_literal(
1150 #join_parser,
1151 r#" }"#
1152 ),
1153 |#output_tuple| #construct
1154 )
1155 }
1156 })
1157 }
1158
1159 fn quote_schema(&self) -> proc_macro2::TokenStream {
1160 let properties = self.fields.iter().map(|field| field.quote_schema());
1161 quote! {
1162 kalosm_sample::JsonObjectSchema::new(
1163 vec![#(#properties),*]
1164 )
1165 }
1166 }
1167}
1168
1169struct FieldParser {
1170 field: Field,
1171 parser: Parser,
1172 name: String,
1173}
1174
1175impl FieldParser {
1176 fn new(field: &Field) -> syn::Result<Self> {
1177 let mut field_name = field.ident.as_ref().unwrap().unraw().to_string();
1178 let mut parser: Parser = syn::parse2(field.ty.to_token_stream())?;
1179
1180 for attr in field.attrs.iter() {
1182 if attr.path().is_ident("parse") {
1183 attr.parse_nested_meta(|meta| {
1184 if let Some(value) = parse_rename_attribute(&meta)? {
1185 field_name = value.value();
1186 Ok(())
1187 } else {
1188 let attribute_applied = parser.apply_attribute(&meta)?;
1189 if !attribute_applied {
1190 let mut possible_attributes = vec!["rename"];
1191 possible_attributes.extend(parser.possible_attributes());
1192 return Err(meta.error(expected_attributes_error(possible_attributes)));
1193 }
1194 Ok(())
1195 }
1196 })?;
1197 }
1198 }
1199
1200 Ok(Self {
1201 field: field.clone(),
1202 parser,
1203 name: field_name,
1204 })
1205 }
1206
1207 fn quote_schema(&self) -> proc_macro2::TokenStream {
1208 let schema = self.parser.quote_schema();
1209 let name = &self.name;
1210 let description = doc_comment(&self.field.attrs);
1211 let description = description.map(|description| quote! { .with_description(#description) });
1212 quote! {
1213 kalosm_sample::JsonPropertySchema::new(#name.to_string(), #schema)
1214 .with_required(true)
1215 #description
1216 }
1217 }
1218}
1219
1220fn doc_comment(attrs: &[syn::Attribute]) -> Option<String> {
1221 let mut description = String::new();
1222 for attr in attrs {
1223 if !attr.path().is_ident("doc") {
1224 continue;
1225 }
1226 let syn::Meta::NameValue(meta) = &attr.meta else {
1227 continue;
1228 };
1229 if let Ok(lit_str) = syn::parse2::<syn::LitStr>(meta.value.to_token_stream()) {
1230 if !description.is_empty() {
1231 description.push('\n');
1232 }
1233 let value = lit_str.value();
1234 let mut borrowed = &*value;
1235 if borrowed.starts_with(' ') {
1236 borrowed = &borrowed[1..];
1237 }
1238 description.push_str(borrowed);
1239 }
1240 }
1241 (!description.is_empty()).then_some(description)
1242}
1243
1244fn expected_attributes_error(
1245 expected_attributes: impl IntoIterator<Item = &'static str>,
1246) -> String {
1247 let mut error_message = String::from("Expected one of the following attributes: ");
1248 let expected_attributes = expected_attributes.into_iter().collect::<Vec<_>>();
1249 for (i, attribute) in expected_attributes.iter().enumerate() {
1250 error_message.push_str(attribute);
1251 match i.cmp(&(expected_attributes.len().saturating_sub(2))) {
1252 Ordering::Less => {
1253 error_message.push_str(", ");
1254 }
1255 Ordering::Equal => {
1256 error_message.push_str(" or ");
1257 }
1258 Ordering::Greater => {}
1259 }
1260 }
1261 error_message
1262}
1263
1264#[derive(Debug)]
1265enum ParserType {
1266 String(StringParserOptions),
1267 Number(NumberParserOptions),
1268 Integer(NumberParserOptions),
1269 Boolean(BoolOptions),
1270 Custom(proc_macro2::TokenStream),
1271}
1272
1273impl Parse for ParserType {
1274 fn parse(input: ParseStream) -> syn::Result<Self> {
1275 if input.peek(Ident) {
1276 let path = input.parse::<Path>()?;
1277 if let Ok(string) = StringParserOptions::from_path(&path) {
1278 return Ok(Self::String(string));
1279 } else if let Ok(number) = NumberParserOptions::from_path(&path) {
1280 return Ok(match number.ty {
1281 NumberType::F64 | NumberType::F32 => Self::Number(number),
1282 _ => Self::Integer(number),
1283 });
1284 } else if let Ok(boolean) = BoolOptions::from_path(&path) {
1285 return Ok(Self::Boolean(boolean));
1286 }
1287 Ok(Self::Custom(path.to_token_stream()))
1288 } else {
1289 Ok(Self::Custom(input.parse()?))
1290 }
1291 }
1292}
1293
1294#[test]
1295fn type_parses() {
1296 assert!(matches!(
1297 dbg!(syn::parse2::<ParserType>(quote! { String })).unwrap(),
1298 ParserType::String(_)
1299 ));
1300 assert!(matches!(
1301 dbg!(syn::parse2::<ParserType>(quote! { std::string::String })).unwrap(),
1302 ParserType::String(_)
1303 ));
1304 assert!(matches!(
1305 dbg!(syn::parse2::<ParserType>(quote! { i32 })).unwrap(),
1306 ParserType::Integer(_)
1307 ));
1308 assert!(matches!(
1309 dbg!(syn::parse2::<ParserType>(quote! { f32 })).unwrap(),
1310 ParserType::Number(_)
1311 ));
1312}
1313
1314#[derive(Debug)]
1315struct Parser {
1316 ty: ParserType,
1317 with: Option<proc_macro2::TokenStream>,
1318 schema: Option<proc_macro2::TokenStream>,
1319}
1320
1321impl Parse for Parser {
1322 fn parse(input: ParseStream) -> syn::Result<Self> {
1323 Ok(Self {
1324 ty: input.parse()?,
1325 with: None,
1326 schema: None,
1327 })
1328 }
1329}
1330
1331impl Parser {
1332 fn apply_attribute(&mut self, input: &syn::meta::ParseNestedMeta) -> syn::Result<bool> {
1333 if input.path.is_ident("with") {
1334 self.with = Some(input.value()?.parse()?);
1335 Ok(true)
1336 } else if input.path.is_ident("schema") {
1337 self.schema = Some(input.value()?.parse()?);
1338 Ok(true)
1339 } else {
1340 match &mut self.ty {
1341 ParserType::String(options) => options.apply_attribute(input),
1342 ParserType::Number(options) => options.apply_attribute(input),
1343 ParserType::Integer(options) => options.apply_attribute(input),
1344 ParserType::Boolean(options) => options.apply_attribute(input),
1345 ParserType::Custom(_) => Ok(false),
1346 }
1347 }
1348 }
1349
1350 fn possible_attributes(&self) -> Vec<&'static str> {
1351 let mut attributes = vec!["with", "schema"];
1352 match &self.ty {
1353 ParserType::String(_) => attributes.extend(StringParserOptions::ATTRIBUTES),
1354 ParserType::Integer(_) | ParserType::Number(_) => {
1355 attributes.extend(NumberParserOptions::ATTRIBUTES)
1356 }
1357 ParserType::Boolean(_) => attributes.extend(BoolOptions::ATTRIBUTES),
1358 _ => {}
1359 }
1360 attributes
1361 }
1362
1363 fn quote_schema(&self) -> proc_macro2::TokenStream {
1364 if let Some(schema) = &self.schema {
1365 return schema.clone();
1366 }
1367
1368 match &self.ty {
1369 ParserType::String(options) => {
1370 let schema = options.quote_schema();
1371 quote! {
1372 kalosm_sample::SchemaType::String(#schema)
1373 }
1374 }
1375 ParserType::Number(options) => {
1376 let schema = options.quote_schema();
1377 quote! {
1378 kalosm_sample::SchemaType::Number(#schema)
1379 }
1380 }
1381 ParserType::Integer(options) => {
1382 let schema = options.quote_schema();
1383 quote! {
1384 kalosm_sample::SchemaType::Integer(#schema)
1385 }
1386 }
1387 ParserType::Boolean(options) => {
1388 let schema = options.quote_schema();
1389 quote! {
1390 kalosm_sample::SchemaType::Boolean(#schema)
1391 }
1392 }
1393 ParserType::Custom(ty) => {
1394 quote_spanned! {
1395 ty.span() =>
1396 <#ty as kalosm_sample::Schema>::schema()
1397 }
1398 }
1399 }
1400 }
1401}
1402
1403impl ToTokens for Parser {
1404 fn to_tokens(&self, tokens: &mut TokenStream2) {
1405 if let Some(with) = &self.with {
1406 with.to_tokens(tokens);
1407 return;
1408 }
1409
1410 tokens.extend(match &self.ty {
1411 ParserType::String(options) => {
1412 quote! {
1413 #options
1414 }
1415 }
1416 ParserType::Integer(options) => {
1417 quote! {
1418 #options
1419 }
1420 }
1421 ParserType::Number(options) => {
1422 quote! {
1423 #options
1424 }
1425 }
1426 ParserType::Boolean(options) => {
1427 quote! {
1428 #options
1429 }
1430 }
1431 ParserType::Custom(ty) => {
1432 quote! {
1433 <#ty as kalosm_sample::Parse>::new_parser()
1434 }
1435 }
1436 })
1437 }
1438}
1439
1440struct StringParserOptions {
1445 path: Path,
1446 character_filter: Option<proc_macro2::TokenStream>,
1447 len: Option<proc_macro2::TokenStream>,
1448 pattern: Option<LitStr>,
1449}
1450
1451impl Debug for StringParserOptions {
1452 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1453 f.debug_struct("StringParserOptions")
1454 .field("character_filter", &self.character_filter)
1455 .field("len", &self.len)
1456 .field("pattern", &self.pattern.as_ref().map(|p| p.value()))
1457 .finish()
1458 }
1459}
1460
1461impl Parse for StringParserOptions {
1462 fn parse(input: ParseStream) -> syn::Result<Self> {
1463 let path = input.parse()?;
1464 Self::from_path(&path)
1465 }
1466}
1467
1468impl StringParserOptions {
1469 const ATTRIBUTES: &'static [&'static str] = &["character_filter", "len", "pattern"];
1470
1471 fn apply_attribute(&mut self, input: &syn::meta::ParseNestedMeta) -> syn::Result<bool> {
1472 if input.path.is_ident("character_filter") {
1473 self.character_filter = Some(input.value()?.parse()?);
1474 Ok(true)
1475 } else if input.path.is_ident("len") {
1476 self.len = Some(input.value()?.parse()?);
1477 Ok(true)
1478 } else if input.path.is_ident("pattern") {
1479 self.pattern = Some(input.value()?.parse()?);
1480 Ok(true)
1481 } else {
1482 Ok(false)
1483 }
1484 }
1485
1486 fn from_path(path: &Path) -> syn::Result<Self> {
1487 if !is_string(path) {
1488 return Err(syn::Error::new(path.span(), "Expected a string type"));
1489 }
1490 Ok(Self {
1491 path: path.clone(),
1492 character_filter: None,
1493 len: None,
1494 pattern: None,
1495 })
1496 }
1497
1498 fn quote_schema(&self) -> proc_macro2::TokenStream {
1499 let len = self.len.as_ref().map(|len| {
1500 quote_spanned! {
1501 len.span() =>
1502 .with_length(#len)
1503 }
1504 });
1505 let pattern = self.pattern.as_ref().map(|pattern| {
1506 quote_spanned! {
1507 pattern.span() =>
1508 .with_pattern(#pattern)
1509 }
1510 });
1511 let quote = quote_spanned! {
1512 self.path.span() =>
1513 kalosm_sample::StringSchema::new()
1514 #len
1515 #pattern
1516 };
1517 quote
1518 }
1519}
1520
1521impl ToTokens for StringParserOptions {
1522 fn to_tokens(&self, tokens: &mut TokenStream2) {
1523 if let Some(pattern) = &self.pattern {
1524 let pattern_str = pattern.value();
1525 let mut pattern_str = pattern_str.as_str();
1526 if let Some(new_pattern_str) = pattern_str.strip_prefix("^") {
1528 pattern_str = new_pattern_str;
1529 }
1530 if !pattern_str.ends_with("\\$") {
1531 if let Some(new_pattern_str) = pattern_str.strip_suffix('$') {
1532 pattern_str = new_pattern_str;
1533 }
1534 }
1535 let pattern = LitStr::new(&format!(r#""{}""#, pattern_str), pattern.span());
1536 let quote = quote_spanned! {
1537 pattern.span() =>
1538 kalosm_sample::ParserExt::map_output(
1539 kalosm_sample::RegexParser::new(#pattern)
1540 .unwrap(),
1541 |string| string[1..string.len() - 1].to_string()
1543 )
1544 };
1545 tokens.extend(quote);
1546 return;
1547 }
1548
1549 let character_filter = self.character_filter.as_ref().map(|filter| {
1550 let quote = quote_spanned! {
1551 filter.span() =>
1552 .with_allowed_characters(#filter)
1553 };
1554 quote
1555 });
1556 let len = self
1557 .len
1558 .as_ref()
1559 .map(|len| len.to_token_stream())
1560 .unwrap_or_else(|| {
1561 quote! {
1562 0..=usize::MAX
1563 }
1564 });
1565 let quote = quote_spanned! {
1566 self.path.span() =>
1567 kalosm_sample::StringParser::new(#len)
1568 #character_filter
1569 };
1570 tokens.extend(quote);
1571 }
1572}
1573
1574fn is_string(ty: &syn::Path) -> bool {
1575 let string_path = syn::parse_quote!(::std::string::String);
1576 is_path_type(ty, &string_path)
1577}
1578
1579#[derive(Debug)]
1580enum NumberType {
1581 F64,
1582 F32,
1583 I128,
1584 I64,
1585 I32,
1586 I16,
1587 I8,
1588 Isize,
1589 U128,
1590 U64,
1591 U32,
1592 U16,
1593 U8,
1594 Usize,
1595}
1596
1597impl Parse for NumberType {
1598 fn parse(input: ParseStream) -> syn::Result<Self> {
1599 let ty = input.parse()?;
1600
1601 Self::from_path(&ty)
1602 }
1603}
1604
1605impl NumberType {
1606 fn from_path(ty: &Path) -> syn::Result<Self> {
1607 let f64_path = syn::parse_quote!(::std::primitive::f64);
1608 if is_path_type(ty, &f64_path) {
1609 return Ok(Self::F64);
1610 }
1611
1612 let f32_path = syn::parse_quote!(::std::primitive::f32);
1613 if is_path_type(ty, &f32_path) {
1614 return Ok(Self::F32);
1615 }
1616
1617 let i128_path = syn::parse_quote!(::std::primitive::i128);
1618 if is_path_type(ty, &i128_path) {
1619 return Ok(Self::I128);
1620 }
1621
1622 let i64_path = syn::parse_quote!(::std::primitive::i64);
1623 if is_path_type(ty, &i64_path) {
1624 return Ok(Self::I64);
1625 }
1626
1627 let i32_path = syn::parse_quote!(::std::primitive::i32);
1628 if is_path_type(ty, &i32_path) {
1629 return Ok(Self::I32);
1630 }
1631
1632 let i16_path = syn::parse_quote!(::std::primitive::i16);
1633 if is_path_type(ty, &i16_path) {
1634 return Ok(Self::I16);
1635 }
1636
1637 let i8_path = syn::parse_quote!(::std::primitive::i8);
1638 if is_path_type(ty, &i8_path) {
1639 return Ok(Self::I8);
1640 }
1641
1642 let isize_path = syn::parse_quote!(::std::primitive::isize);
1643 if is_path_type(ty, &isize_path) {
1644 return Ok(Self::Isize);
1645 }
1646
1647 let u128_path = syn::parse_quote!(::std::primitive::u128);
1648 if is_path_type(ty, &u128_path) {
1649 return Ok(Self::U128);
1650 }
1651
1652 let u64_path = syn::parse_quote!(::std::primitive::u64);
1653 if is_path_type(ty, &u64_path) {
1654 return Ok(Self::U64);
1655 }
1656
1657 let u32_path = syn::parse_quote!(::std::primitive::u32);
1658 if is_path_type(ty, &u32_path) {
1659 return Ok(Self::U32);
1660 }
1661
1662 let u16_path = syn::parse_quote!(::std::primitive::u16);
1663 if is_path_type(ty, &u16_path) {
1664 return Ok(Self::U16);
1665 }
1666
1667 let u8_path = syn::parse_quote!(::std::primitive::u8);
1668 if is_path_type(ty, &u8_path) {
1669 return Ok(Self::U8);
1670 }
1671
1672 let usize_path = syn::parse_quote!(::std::primitive::usize);
1673 if is_path_type(ty, &usize_path) {
1674 return Ok(Self::Usize);
1675 }
1676
1677 Err(syn::Error::new(ty.span(), "Expected a number type"))
1678 }
1679}
1680
1681impl ToTokens for NumberType {
1682 fn to_tokens(&self, tokens: &mut TokenStream2) {
1683 let quote = match self {
1684 Self::F64 => quote! {kalosm_sample::F64Parser::new()},
1685 Self::F32 => quote! {kalosm_sample::F32Parser::new()},
1686 Self::I128 => quote! {kalosm_sample::I128Parser::new()},
1687 Self::I64 => quote! {kalosm_sample::I64Parser::new()},
1688 Self::I32 => quote! {kalosm_sample::I32Parser::new()},
1689 Self::I16 => quote! {kalosm_sample::I16Parser::new()},
1690 Self::I8 => quote! {kalosm_sample::I8Parser::new()},
1691 Self::Isize => quote! {kalosm_sample::IsizeParser::new()},
1692 Self::U128 => quote! {kalosm_sample::U128Parser::new()},
1693 Self::U64 => quote! {kalosm_sample::U64Parser::new()},
1694 Self::U32 => quote! {kalosm_sample::U32Parser::new()},
1695 Self::U16 => quote! {kalosm_sample::U16Parser::new()},
1696 Self::U8 => quote! {kalosm_sample::U8Parser::new()},
1697 Self::Usize => quote! {kalosm_sample::UsizeParser::new()},
1698 };
1699
1700 tokens.extend(quote);
1701 }
1702}
1703
1704struct NumberParserOptions {
1707 path: Path,
1708 ty: NumberType,
1709 range: Option<proc_macro2::TokenStream>,
1710}
1711
1712impl Debug for NumberParserOptions {
1713 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1714 f.debug_struct("NumberParserOptions")
1715 .field("ty", &self.ty)
1716 .field("range", &self.range)
1717 .finish()
1718 }
1719}
1720
1721impl Parse for NumberParserOptions {
1722 fn parse(input: ParseStream) -> syn::Result<Self> {
1723 let path = input.parse::<Path>()?;
1724
1725 Self::from_path(&path)
1726 }
1727}
1728
1729impl NumberParserOptions {
1730 fn from_path(path: &Path) -> syn::Result<Self> {
1731 let ty = NumberType::from_path(path)?;
1732 Ok(Self {
1733 path: path.clone(),
1734 ty,
1735 range: None,
1736 })
1737 }
1738}
1739
1740impl ToTokens for NumberParserOptions {
1741 fn to_tokens(&self, tokens: &mut TokenStream2) {
1742 let range = self.range.as_ref().map(|range| {
1743 let quote = quote_spanned! {
1744 range.span() =>
1745 .with_range(#range)
1746 };
1747 quote
1748 });
1749 let ty = &self.ty;
1750 let quote = quote_spanned! {
1751 self.path.span() =>
1752 #ty
1753 #range
1754 };
1755 tokens.extend(quote);
1756 }
1757}
1758
1759impl NumberParserOptions {
1760 const ATTRIBUTES: &'static [&'static str] = &["range"];
1761
1762 fn apply_attribute(&mut self, input: &syn::meta::ParseNestedMeta) -> syn::Result<bool> {
1763 if input.path.is_ident("range") {
1764 self.range = Some(input.value()?.parse()?);
1765 Ok(true)
1766 } else {
1767 Ok(false)
1768 }
1769 }
1770
1771 fn quote_schema(&self) -> proc_macro2::TokenStream {
1772 match self.ty {
1773 NumberType::F64 | NumberType::F32 => {
1774 let range = self.range.as_ref().map(|range| {
1775 quote_spanned! {
1776 range.span() =>
1777 .with_range({
1778 let range = #range;
1779 let start = range.start() as f64;
1780 let end = range.end() as f64;
1781 start..=end
1782 })
1783 }
1784 });
1785 quote_spanned! {
1786 self.path.span() =>
1787 kalosm_sample::NumberSchema::new()
1788 #range
1789 }
1790 }
1791 _ => quote_spanned! {
1792 self.path.span() =>
1793 kalosm_sample::IntegerSchema::new()
1794 },
1795 }
1796 }
1797}
1798
1799struct BoolOptions {
1800 path: Path,
1801}
1802
1803impl Debug for BoolOptions {
1804 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1805 f.debug_struct("BoolOptions").finish()
1806 }
1807}
1808
1809impl BoolOptions {
1810 const ATTRIBUTES: &'static [&'static str] = &[];
1811
1812 fn apply_attribute(&mut self, _input: &syn::meta::ParseNestedMeta) -> syn::Result<bool> {
1813 Ok(false)
1814 }
1815
1816 fn from_path(path: &Path) -> syn::Result<Self> {
1817 if !is_bool(path) {
1818 return Err(syn::Error::new(path.span(), "Expected a boolean type"));
1819 }
1820 Ok(Self { path: path.clone() })
1821 }
1822
1823 fn quote_schema(&self) -> proc_macro2::TokenStream {
1824 quote_spanned! {
1825 self.path.span() =>
1826 kalosm_sample::BooleanSchema::new()
1827 }
1828 }
1829}
1830
1831impl Parse for BoolOptions {
1832 fn parse(input: ParseStream) -> syn::Result<Self> {
1833 let path = input.parse()?;
1834 Self::from_path(&path)
1835 }
1836}
1837
1838impl ToTokens for BoolOptions {
1839 fn to_tokens(&self, tokens: &mut TokenStream2) {
1840 let quote = quote! {
1841 <bool as kalosm_sample::Parse>::new_parser()
1842 };
1843 tokens.extend(quote);
1844 }
1845}
1846
1847fn is_bool(ty: &syn::Path) -> bool {
1848 let bool_path = syn::parse_quote!(::std::bool);
1849 is_path_type(ty, &bool_path)
1850}
1851
1852fn is_path_type(path: &syn::Path, match_path: &TypePath) -> bool {
1854 let mut path_segments = path.segments.iter().rev();
1855 let mut match_path_segments = match_path.path.segments.iter().rev();
1856 loop {
1857 match (path_segments.next(), match_path_segments.next()) {
1858 (Some(first), Some(second)) => {
1859 if first.ident != second.ident {
1860 return false;
1861 }
1862 }
1863 (None, None) => return true,
1864 (Some(_), None) => return false,
1865 (None, Some(_)) => return true,
1866 }
1867 }
1868}