1use bluejay_core::{
2 definition::{prelude::*, SchemaDefinition, TypeDefinitionReference},
3 BuiltinScalarDefinition,
4};
5use bluejay_parser::{
6 ast::{
7 definition::{DefinitionDocument, SchemaDefinition as ParserSchemaDefinition},
8 Parse,
9 },
10 Error as ParserError,
11};
12use bluejay_validator::definition::BuiltinRulesValidator;
13use std::collections::{HashMap, HashSet};
14use syn::{parse_quote, spanned::Spanned};
15
16mod attributes;
17mod builtin_scalar;
18mod code_generator;
19mod enum_type_definition;
20mod executable_definition;
21mod input;
22mod input_object_type_definition;
23pub mod names;
24mod types;
25mod validation;
26
27use attributes::doc_string;
28pub use code_generator::CodeGenerator;
29use enum_type_definition::EnumTypeDefinitionBuilder;
30use executable_definition::generate_executable_definition;
31pub use executable_definition::{
32 ExecutableEnum, ExecutableField, ExecutableStruct, ExecutableType, WrappedExecutableType,
33};
34use input::DocumentInput;
35pub use input::Input;
36use input_object_type_definition::InputObjectTypeDefinitionBuilder;
37
38pub(crate) struct Config<'a, S: SchemaDefinition, C: CodeGenerator> {
39 borrow: bool,
40 schema_definition: &'a S,
41 custom_scalar_borrows: HashMap<String, bool>,
42 enums_as_str: HashSet<String>,
43 code_generator: &'a C,
44}
45
46impl<'a, S: SchemaDefinition, C: CodeGenerator> Config<'a, S, C> {
47 pub(crate) fn schema_definition(&self) -> &'a S {
48 self.schema_definition
49 }
50
51 pub(crate) fn borrow(&self) -> bool {
52 self.borrow
53 }
54
55 pub(crate) fn custom_scalar_borrows(&self, cstd: &S::CustomScalarTypeDefinition) -> bool {
56 *self
57 .custom_scalar_borrows
58 .get(&names::type_name(cstd.name()))
59 .expect("No type alias for custom scalar")
60 }
61
62 pub(crate) fn builtin_scalar_borrows(&self, bstd: BuiltinScalarDefinition) -> bool {
63 self.borrow && builtin_scalar::scalar_is_reference(bstd)
64 }
65
66 pub(crate) fn enum_as_str(&self, etd: &S::EnumTypeDefinition) -> bool {
67 self.enums_as_str.contains(etd.name())
68 }
69
70 pub(crate) fn code_generator(&self) -> &C {
71 self.code_generator
72 }
73}
74
75pub fn generate_schema(
76 input: Input,
77 module: &mut syn::ItemMod,
78 known_custom_scalar_types: HashMap<String, KnownCustomScalarType>,
79 code_generator: impl CodeGenerator,
80) -> syn::Result<()> {
81 let Input {
82 ref schema,
83 borrow,
84 enums_as_str,
85 } = input;
86
87 let borrow = borrow.is_some_and(|lit| lit.value());
88
89 let (schema_contents, schema_path) = schema.read_to_string_and_path()?;
90
91 let definition_document: DefinitionDocument = DefinitionDocument::parse(&schema_contents)
92 .result
93 .map_err(|errors| {
94 map_parser_errors(schema, &schema_contents, schema_path.as_deref(), errors)
95 })?;
96 let schema_definition =
97 ParserSchemaDefinition::try_from(&definition_document).map_err(|errors| {
98 map_parser_errors(schema, &schema_contents, schema_path.as_deref(), errors)
99 })?;
100 let schema_errors: Vec<_> = BuiltinRulesValidator::validate(&schema_definition).collect();
101 if !schema_errors.is_empty() {
102 return Err(map_parser_errors(
103 schema,
104 &schema_contents,
105 schema_path.as_deref(),
106 schema_errors,
107 ));
108 }
109
110 let custom_scalar_borrows = custom_scalar_borrows(
111 module,
112 &schema_definition,
113 borrow,
114 known_custom_scalar_types,
115 )?;
116
117 let enums_as_str = validate_enums_as_str(enums_as_str, &schema_definition)?;
118
119 let config = Config {
120 schema_definition: &schema_definition,
121 borrow,
122 custom_scalar_borrows,
123 enums_as_str,
124 code_generator: &code_generator,
125 };
126
127 if let Some((_, items)) = module.content.take() {
128 let new_items = process_module_items(&config, items)?;
129 module.content = Some((syn::token::Brace::default(), new_items));
130 } else {
131 let new_items = process_module_items(&config, Vec::new())?;
132 module.content = Some((syn::token::Brace::default(), new_items));
133 }
134
135 if let Some(description) = schema_definition.description() {
136 module.attrs.push(doc_string(description));
137 }
138
139 Ok(())
140}
141
142fn custom_scalar_borrows(
143 module: &mut syn::ItemMod,
144 schema_definition: &impl SchemaDefinition,
145 borrow: bool,
146 known_custom_scalar_types: HashMap<String, KnownCustomScalarType>,
147) -> syn::Result<HashMap<String, bool>> {
148 let items = module
149 .content
150 .as_ref()
151 .map(|(_, items)| items.as_slice())
152 .unwrap_or_default();
153
154 let type_aliases = items
155 .iter()
156 .filter_map(|item| match item {
157 syn::Item::Type(ty) => Some(ty),
158 _ => None,
159 })
160 .collect::<Vec<_>>();
161
162 type_aliases.iter().try_for_each(|type_alias| {
163 let generics = &type_alias.generics;
164
165 if let Some(type_param) = generics.type_params().next() {
166 return Err(syn::Error::new(
167 type_param.span(),
168 "Type aliases for custom scalars must not contain type parameters",
169 ));
170 }
171
172 if let Some(const_param) = generics.const_params().next() {
173 return Err(syn::Error::new(
174 const_param.span(),
175 "Type aliases for custom scalars must not contain const parameters",
176 ));
177 }
178
179 if !borrow {
180 if let Some(lifetime_param) = generics.lifetimes().next() {
181 return Err(syn::Error::new(
182 lifetime_param.span(),
183 "Type aliases for custom scalars cannot contain lifetime parameters when `borrow` is set to true",
184 ));
185 }
186 } else if let Some(lifetime_param) = generics.lifetimes().nth(1) {
187 return Err(syn::Error::new(
188 lifetime_param.span(),
189 "Type aliases for custom scalars must contain at most one lifetime parameter",
190 ));
191 }
192
193 let name = type_alias.ident.to_string();
194
195 if !schema_definition.type_definitions().any(|type_definition| {
196 matches!(type_definition, TypeDefinitionReference::CustomScalar(cstd) if names::type_name(cstd.name()) == name)
197 }) {
198 return Err(syn::Error::new(
199 type_alias.ident.span(),
200 format!("No custom scalar definition named {name}"),
201 ));
202 }
203
204 Ok(())
205 })?;
206
207 let mut custom_scalars: HashMap<String, bool> = type_aliases
208 .into_iter()
209 .map(|type_alias| {
210 (
211 type_alias.ident.to_string(),
212 type_alias.generics.lifetimes().next().is_some(),
213 )
214 })
215 .collect();
216
217 schema_definition
218 .type_definitions()
219 .try_for_each(|td| match td {
220 TypeDefinitionReference::CustomScalar(cstd) => {
221 let name = names::type_name(cstd.name());
222 #[allow(clippy::map_entry)]
223 if custom_scalars.contains_key(&name) {
224 Ok(())
225 } else if let Some(known_custom_scalar_type) = known_custom_scalar_types.get(&name)
226 {
227 let (ty, lifetime): (_, Option<syn::Generics>) =
228 match known_custom_scalar_type.type_for_borrowed.as_ref() {
229 Some(ty) if borrow => (ty, Some(parse_quote! { <'a> })),
230 _ => (&known_custom_scalar_type.type_for_owned, None),
231 };
232 let ident = quote::format_ident!("{}", name);
233 let alias: syn::ItemType = parse_quote! {
234 pub type #ident #lifetime = #ty;
235 };
236 if let Some((_, items)) = module.content.as_mut() {
237 items.push(syn::Item::Type(alias));
238 }
239 custom_scalars.insert(
240 name,
241 borrow && known_custom_scalar_type.type_for_borrowed.is_some(),
242 );
243 Ok(())
244 } else {
245 Err(syn::Error::new(
246 module.span(),
247 format!("Missing type alias for custom scalar {name}"),
248 ))
249 }
250 }
251 _ => Ok(()),
252 })?;
253
254 Ok(custom_scalars)
255}
256
257fn validate_enums_as_str(
258 enums_as_str: syn::punctuated::Punctuated<syn::LitStr, syn::Token![,]>,
259 schema_definition: &impl SchemaDefinition,
260) -> syn::Result<HashSet<String>> {
261 let mut enum_names = HashSet::new();
262 enums_as_str.iter().try_for_each(|lit| {
263 let name: String = lit.value();
264 if matches!(
265 schema_definition.get_type_definition(&name),
266 Some(TypeDefinitionReference::Enum(_))
267 ) {
268 if enum_names.insert(name.clone()) {
269 Ok(())
270 } else {
271 Err(syn::Error::new(
272 lit.span(),
273 format!("Duplicate enum definition named {name}"),
274 ))
275 }
276 } else {
277 Err(syn::Error::new(
278 lit.span(),
279 format!("No enum definition named {name}"),
280 ))
281 }
282 })?;
283 Ok(enum_names)
284}
285
286fn process_module_items<S: SchemaDefinition, C: CodeGenerator>(
287 config: &Config<S, C>,
288 items: Vec<syn::Item>,
289) -> syn::Result<Vec<syn::Item>> {
290 config
291 .schema_definition
292 .type_definitions()
293 .filter_map(|type_definition| match type_definition {
294 TypeDefinitionReference::Enum(etd) if !config.enum_as_str(etd) => Some(
295 EnumTypeDefinitionBuilder::<S, C>::build(etd, config.code_generator()),
296 ),
297 TypeDefinitionReference::InputObject(iotd) => {
298 Some(InputObjectTypeDefinitionBuilder::build(iotd, config))
299 }
300 _ => None,
301 })
302 .flatten()
303 .map(Ok)
304 .chain(
305 items
306 .into_iter()
307 .map(|item| process_module_item(config, item)),
308 )
309 .collect()
310}
311
312fn process_module_item<S: SchemaDefinition, C: CodeGenerator>(
313 config: &Config<S, C>,
314 item: syn::Item,
315) -> syn::Result<syn::Item> {
316 if let syn::Item::Mod(mut module) = item {
317 if let Some((attribute, &mut [])) = module.attrs.split_first_mut() {
318 if matches!(attribute.style, syn::AttrStyle::Inner(_)) {
319 Err(syn::Error::new(
320 attribute.span(),
321 "Expected an outer attribute",
322 ))
323 } else if let syn::Meta::List(list) = &mut attribute.meta {
324 if list.path.is_ident("query") {
325 if !matches!(list.delimiter, syn::MacroDelimiter::Bracket(_)) {
326 let items = generate_executable_definition(
327 config,
328 std::mem::take(&mut list.tokens),
329 )?;
330 module.content = Some((syn::token::Brace::default(), items));
331 module.attrs = Vec::new();
332 Ok(syn::Item::Mod(module))
333 } else {
334 Err(syn::Error::new(
335 list.delimiter.span().open(),
336 "Expected brackets",
337 ))
338 }
339 } else {
340 Err(syn::Error::new(list.path.span(), "Expected `query`"))
341 }
342 } else {
343 Err(syn::Error::new(
344 attribute.meta.span(),
345 "Expected a list meta attribute, e.g. `#[query(...)]`",
346 ))
347 }
348 } else {
349 Err(syn::Error::new(
350 module.span(),
351 "Expected a single `#[query(...)]` attribute",
352 ))
353 }
354 } else if matches!(item, syn::Item::Type(_)) {
355 Ok(item)
356 } else {
357 Err(syn::Error::new(item.span(), "Expected a module"))
358 }
359}
360
361fn map_parser_errors<E: Into<ParserError>>(
362 span: &impl syn::spanned::Spanned,
363 schema_contents: &str,
364 schema_path: Option<&str>,
365 errors: impl IntoIterator<Item = E>,
366) -> syn::Error {
367 syn::Error::new(
368 span.span(),
369 ParserError::format_errors(schema_contents, schema_path, errors),
370 )
371}
372
373#[derive(Clone)]
374pub struct KnownCustomScalarType {
375 pub type_for_owned: syn::Type,
376 pub type_for_borrowed: Option<syn::Type>,
377}