1#![deny(missing_docs)]
6
7use std::{collections::HashMap, path::Path};
8
9use proc_macro::TokenStream;
10use quote::{quote, ToTokens};
11use serde::Deserialize;
12use serde_tokenstream::{ParseWrapper, TokenStreamWrapper};
13use syn::LitStr;
14use token_utils::TypeAndImpls;
15use typify_impl::{
16 CrateVers, MapType, TypeSpace, TypeSpacePatch, TypeSpaceSettings, UnknownPolicy,
17};
18
19mod token_utils;
20
21#[proc_macro]
70pub fn import_types(item: TokenStream) -> TokenStream {
71 match do_import_types(item) {
72 Err(err) => err.to_compile_error().into(),
73 Ok(out) => out,
74 }
75}
76
77#[derive(Deserialize)]
78struct MacroSettings {
79 schema: ParseWrapper<LitStr>,
80 #[serde(default)]
81 derives: Vec<ParseWrapper<syn::Path>>,
82 #[serde(default)]
83 attrs: Vec<TokenStreamWrapper>,
84 #[serde(default)]
85 struct_builder: bool,
86
87 #[serde(default)]
88 unknown_crates: UnknownPolicy,
89 #[serde(default)]
90 crates: HashMap<CrateName, MacroCrateSpec>,
91 #[serde(default)]
92 map_type: Option<ParseWrapper<syn::Type>>,
93
94 #[serde(default)]
95 patch: HashMap<ParseWrapper<syn::Ident>, MacroPatch>,
96 #[serde(default)]
97 replace: HashMap<ParseWrapper<syn::Ident>, ParseWrapper<TypeAndImpls>>,
98 #[serde(default)]
99 convert:
100 serde_tokenstream::OrderedMap<schemars::schema::SchemaObject, ParseWrapper<TypeAndImpls>>,
101}
102
103struct MacroCrateSpec {
104 original: Option<String>,
105 version: CrateVers,
106}
107
108impl<'de> Deserialize<'de> for MacroCrateSpec {
109 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
110 where
111 D: serde::Deserializer<'de>,
112 {
113 let ss = String::deserialize(deserializer)?;
114
115 let (original, vers_str) = if let Some(ii) = ss.find('@') {
116 let original_str = &ss[..ii];
117 let rest = &ss[ii + 1..];
118 if !is_crate(original_str) {
119 return Err(<D::Error as serde::de::Error>::invalid_value(
120 serde::de::Unexpected::Str(&ss),
121 &"valid crate name",
122 ));
123 }
124
125 (Some(original_str.to_string()), rest)
126 } else {
127 (None, ss.as_ref())
128 };
129
130 let Some(version) = CrateVers::parse(vers_str) else {
131 return Err(<D::Error as serde::de::Error>::invalid_value(
132 serde::de::Unexpected::Str(&ss),
133 &"valid version",
134 ));
135 };
136
137 Ok(Self { original, version })
138 }
139}
140
141#[derive(Hash, PartialEq, Eq)]
142struct CrateName(String);
143impl<'de> Deserialize<'de> for CrateName {
144 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
145 where
146 D: serde::Deserializer<'de>,
147 {
148 let ss = String::deserialize(deserializer)?;
149
150 if is_crate(&ss) {
151 Ok(Self(ss))
152 } else {
153 Err(<D::Error as serde::de::Error>::invalid_value(
154 serde::de::Unexpected::Str(&ss),
155 &"valid crate name",
156 ))
157 }
158 }
159}
160
161fn is_crate(s: &str) -> bool {
162 !s.contains(|cc: char| !cc.is_alphanumeric() && cc != '_' && cc != '-')
163}
164
165#[derive(Deserialize)]
166struct MacroPatch {
167 #[serde(default)]
168 rename: Option<String>,
169 #[serde(default)]
170 derives: Vec<ParseWrapper<syn::Path>>,
171 #[serde(default)]
172 attrs: Vec<TokenStreamWrapper>,
173}
174
175impl From<MacroPatch> for TypeSpacePatch {
176 fn from(a: MacroPatch) -> Self {
177 let mut s = Self::default();
178 a.rename.iter().for_each(|rename| {
179 s.with_rename(rename);
180 });
181 a.derives.iter().for_each(|derive| {
182 s.with_derive(derive.to_token_stream());
183 });
184 a.attrs.iter().for_each(|attr| {
185 s.with_attr(attr.to_token_stream());
186 });
187 s
188 }
189}
190
191fn do_import_types(item: TokenStream) -> Result<TokenStream, syn::Error> {
192 let (schema, settings) = if let Ok(ll) = syn::parse::<LitStr>(item.clone()) {
194 (ll, TypeSpaceSettings::default())
195 } else {
196 let MacroSettings {
197 schema,
198 derives,
199 replace,
200 patch,
201 struct_builder,
202 convert,
203 unknown_crates,
204 crates,
205 map_type,
206 attrs,
207 } = serde_tokenstream::from_tokenstream(&item.into())?;
208 let mut settings = TypeSpaceSettings::default();
209 derives.into_iter().for_each(|derive| {
210 settings.with_derive(derive.to_token_stream().to_string());
211 });
212 attrs.into_iter().for_each(|attr| {
213 settings.with_attr(attr.to_token_stream().to_string());
214 });
215 settings.with_struct_builder(struct_builder);
216
217 patch.into_iter().for_each(|(type_name, patch)| {
218 settings.with_patch(type_name.to_token_stream(), &patch.into());
219 });
220 replace.into_iter().for_each(|(type_name, type_and_impls)| {
221 let (replace_type, impls) = type_and_impls.into_inner().into_name_and_impls();
222 settings.with_replacement(type_name.to_token_stream(), replace_type, impls.into_iter());
223 });
224 convert.into_iter().for_each(|(schema, type_and_impls)| {
225 let (type_name, impls) = type_and_impls.into_inner().into_name_and_impls();
226 settings.with_conversion(schema, type_name, impls);
227 });
228
229 crates.into_iter().for_each(
230 |(CrateName(crate_name), MacroCrateSpec { original, version })| {
231 if let Some(original_crate) = original {
232 settings.with_crate(original_crate, version, Some(&crate_name));
233 } else {
234 settings.with_crate(crate_name, version, None);
235 }
236 },
237 );
238 settings.with_unknown_crates(unknown_crates);
239
240 if let Some(map_type) = map_type {
241 settings.with_map_type(MapType(map_type.into_inner()));
242 }
243
244 (schema.into_inner(), settings)
245 };
246
247 let dir = std::env::var("CARGO_MANIFEST_DIR").map_or_else(
248 |_| std::env::current_dir().unwrap(),
249 |s| Path::new(&s).to_path_buf(),
250 );
251
252 let path = dir.join(schema.value());
253
254 let root_schema: schemars::schema::RootSchema =
255 serde_json::from_reader(std::fs::File::open(&path).map_err(|e| {
256 syn::Error::new(
257 schema.span(),
258 format!("couldn't read file {}: {}", schema.value(), e),
259 )
260 })?)
261 .unwrap();
262
263 let mut type_space = TypeSpace::new(&settings);
264 type_space
265 .add_root_schema(root_schema)
266 .map_err(|e| into_syn_err(e, schema.span()))?;
267
268 let path_str = path.to_string_lossy();
269 let output = quote! {
270 #type_space
271
272 const _: &str = include_str!(#path_str);
274 };
275
276 Ok(output.into())
277}
278
279fn into_syn_err(e: typify_impl::Error, span: proc_macro2::Span) -> syn::Error {
280 syn::Error::new(span, e.to_string())
281}
282
283#[cfg(test)]
284mod tests {
285 use quote::quote;
286
287 use crate::MacroSettings;
288
289 #[test]
290 fn test_settings() {
291 let item = quote! {
292 schema = "foo.json",
293 derives = [::foo::Foo, ::bar::Bar],
294 replace = {
295 Baz = ::baz::Baz,
296 },
297 struct_builder = true,
298 map_type = ::my::map::Type,
299 };
300
301 let MacroSettings { .. } = serde_tokenstream::from_tokenstream(&item).unwrap();
302 }
303}