1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{Field, ItemStruct, LitStr, parse_macro_input};
4
5#[proc_macro_attribute]
6pub fn docs(attr: TokenStream, item: TokenStream) -> TokenStream {
7 docs_impl(attr, item)
8}
9
10macro_rules! format_some {
12 ($($arg:tt)*) => {{
13 let res = format!($($arg)*);
14
15 let res = quote! {
16 #[doc = #res]
17 };
18
19 Some(res)
20 }}
21}
22
23pub(crate) fn docs_impl(attr: TokenStream, item: TokenStream) -> TokenStream {
24 let ast: ItemStruct = syn::parse(item.clone()).unwrap();
25 let ItemStruct {
26 vis,
27 ident,
28 fields,
29 attrs,
30 ..
31 } = ast;
32
33 let mut item_name = ident.to_string().to_lowercase();
35
36 let parser = syn::meta::parser(|meta| {
39 if meta.path.is_ident("name") {
40 let value: LitStr = meta.value()?.parse()?;
41 item_name = value.value();
42
43 Ok(())
44 } else {
45 Err(meta.error("unsupported property"))
46 }
47 });
48
49 parse_macro_input!(attr with parser);
50
51 let (docs, fields): (Vec<_>, Vec<Field>) = fields
52 .into_iter()
53 .map(|f| {
54 let field_name = f.ident.as_ref().unwrap().to_string();
55 let struct_name = &item_name;
56
57 let doc = field_documentation(&field_name, struct_name);
60
61 (doc, f)
62 })
63 .collect();
64
65 quote! {
67 #(#attrs)*
68 #vis struct #ident {
69 #(
70 #docs
71 #fields
72
73 ),*
74 }
75
76 }
77 .into()
78}
79
80fn field_documentation(field_name: &str, name: &str) -> Option<proc_macro2::TokenStream> {
81 match field_name {
82 "available_markets" => format_some!(
83 "The [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) codes for the markets in which the {name} is available."
84 ),
85
86 "external_urls" => format_some!("Known external URLs for the {name}."),
87
88 "href" => format_some!(
89 "A link to the Spotify Web API endpoint providing full details of the {name}."
90 ),
91
92 "id" => format_some!(
93 "The [Spotify ID](https://developer.spotify.com/documentation/web-api/concepts/spotify-uris-ids) for the {name}."
94 ),
95
96 "r#type" | "type" => format_some!("The object type. Allowed values: `{name}`."),
97
98 "uri" => format_some!(
99 "The [Spotify URI](https://developer.spotify.com/documentation/web-api/concepts/spotify-uris-ids) for the {name}."
100 ),
101
102 "copyrights" => format_some!("The copyright statements of the {name}."),
103
104 "external_ids" => format_some!("Known external IDs for the {name}."),
105
106 "images" => {
107 format_some!("The cover art for the {name} in various sizes, with the widest first.")
108 }
109
110 "name" => format_some!("The name of the {name}."),
111
112 "release_date" => format_some!("The date the {name} was first released."),
113
114 "release_date_precision" => {
115 format_some!("The precision with which the `release_date` field is known.")
116 }
117
118 "description" => format_some!("A text description of the {name}."),
119
120 "html_description" => {
121 format_some!("A description of the {name} that may contain HTML tags.")
122 }
123
124 "restrictions" => {
125 format_some!("Included in the response when a content restriction is applied.")
126 }
127
128 "explicit" => format_some!(
129 "Whether or not the {name} contains explicit content.\n\nNote: `false` can also mean *unknown*."
130 ),
131
132 "languages" => format_some!(
133 "A list of [ISO 639](https://en.wikipedia.org/wiki/ISO_639) codes for the languages spoken in the {name}."
134 ),
135
136 "media_type" => format_some!("The type of the media of the {name}."),
137
138 "publisher" => format_some!("The publisher of the {name}."),
139
140 "duration_ms" => format_some!("The duration of the {name} in miliseconds."),
141
142 "is_playable" => {
143 format_some!("Indicates whether or not the {name} is playable in the current market.")
144 }
145
146 "resume_point" => format_some!(
147 "The user's latest position in the {name}.\n\nNote: this field is only available if the user is authorised with the `user-read-playback-position` scope."
148 ),
149
150 _ => None,
151 }
152}