mongodb_ext_derive/
lib.rs

1//! This crate is the `proc_macro` library associated with the `mongodb_ext` crate.
2//!
3//! Since recent changes, this crate has an unfortunate name.
4//! "derive" is not quite correct, because this crate's purpose is to provide macros, not **derive** macros explicitly.
5//!
6//! This crate currently provides one macro: [`case!`].
7
8extern crate convert_case;
9extern crate proc_macro;
10extern crate proc_macro2;
11#[macro_use]
12extern crate syn;
13extern crate quote;
14
15use {
16    crate::{
17        convert_case::{Case, Casing},
18        proc_macro::TokenStream,
19        proc_macro2::Span,
20        quote::ToTokens,
21        syn::{
22            parse::{Error as SynError, Parse, ParseStream, Result as SynResult},
23            spanned::Spanned,
24            token::FatArrow,
25            LitStr, Path,
26        },
27    },
28    std::convert::From,
29};
30
31struct CaseInput(LitStr);
32impl Parse for CaseInput {
33    fn parse(input: ParseStream) -> SynResult<Self> {
34        // parse first path
35        let first_path: Path = input.parse::<Path>()?;
36        // get first path's span
37        let first_span: Span = first_path.span();
38        // convert first path to String
39        let first_string: String = if let Some(last_of_first) = first_path.segments.last() {
40            last_of_first.ident.to_string()
41        } else {
42            // throw error if there is no last element
43            return Err(SynError::new(first_span, "Cannot get last element of path"));
44        };
45
46        // parse `=>`
47        let _: FatArrow = input.parse::<FatArrow>()?;
48
49        // parse last (second) case
50        let last_path: Path = input.parse::<Path>()?;
51        // get last path's span
52        let last_span: Span = last_path.span();
53        // convert last path to String
54        let last_string: String = if let Some(last_of_last) = last_path.segments.last() {
55            last_of_last.ident.to_string()
56        } else {
57            // throw error if there is no last element
58            return Err(SynError::new(last_span, "Cannot get last element of path"));
59        };
60
61        // parse case as instance of `convert_case::Case`
62        let mut case: Option<Case> = None;
63        for c in Case::all_cases() {
64            if format!("{:?}", c).eq(&last_string) {
65                case = Some(c);
66                break;
67            }
68        }
69        if case.is_none() {
70            return Err(SynError::new(
71                last_span,
72                "Cannot parse case parameter as `Case`",
73            ));
74        }
75        let case = case.unwrap();
76
77        // change first path's case and return
78        let parsed_path: String = first_string.to_case(case);
79        Ok(Self(LitStr::new(&parsed_path, first_span)))
80    }
81}
82
83/// Small macro that converts the input path to a given case.
84///
85/// The general accepted format is: `case!(path::to::Type => Case)`
86///
87/// Hereby
88/// - `path::to::Type` can be any path. It does not need to exist.
89/// - `=>` is just a fat arrow that separates the two parameters.
90/// - `Case` is any path that points to any value of the [`convert_case`] crate's [`Case`] enum.
91///
92/// This macro always expands to a [`&str`] literal ([`LitStr`](struct@syn::LitStr)).
93///
94/// # Examples
95///
96/// The identifier given does not need to be an existing type:
97///
98/// ```rust
99/// use mongodb_ext_derive::case;
100///
101/// // `MyImaginaryType` is not imported, but works anyways
102/// // `Case` is not imported, but works anyways
103/// assert_eq!(
104///     case!(MyImaginaryType => Case::Camel),
105///     "myImaginaryType"
106/// );
107/// ```
108///
109/// If a path is given, only the last element will be parsed:
110///
111/// ```rust
112/// use mongodb_ext_derive::case;
113///
114/// assert_eq!(
115///     case!(std::collection::HashMap => Snake),
116///     "hash_map"
117/// );
118/// ```
119///
120/// ```rust
121/// use mongodb_ext_derive::case;
122///
123/// assert_eq!(
124///     case!(std::this::r#type::does::not::exist::THIS_CONSTANT_DOES_NOT_EXIST => Pascal),
125///     "ThisConstantDoesNotExist"
126/// );
127///
128/// assert_eq!(
129///     case!(std::this::r#type::does::not::exist::this_function_does_not_exist => Title),
130///     "This Function Does Not Exist"
131/// );
132///
133/// assert_eq!(
134///     case!(std::this::r#type::does::not::exist::ThisTypeDoesNotExist => Camel),
135///     "thisTypeDoesNotExist"
136/// );
137/// ```
138#[proc_macro]
139pub fn case(input: TokenStream) -> TokenStream {
140    parse_macro_input!(input as CaseInput)
141        .0
142        .to_token_stream()
143        .into()
144}