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