Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions bluejay-typegen-codegen/src/code_generator.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use bluejay_core::definition::{
EnumTypeDefinition, EnumValueDefinition, InputObjectTypeDefinition, InputValueDefinition,
};
use bluejay_core::executable::{OperationDefinition, VariableDefinition};

use crate::{ExecutableEnum, ExecutableStruct};

Expand Down Expand Up @@ -133,4 +134,31 @@ pub trait CodeGenerator {
) -> Vec<syn::Attribute> {
Vec::new()
}

/// Any attributes for the struct holding the variables of an operation. Only generated for operations that define
/// variables.
fn attributes_for_variables_struct(
&self,
#[allow(unused_variables)] operation_definition: &impl OperationDefinition,
) -> Vec<syn::Attribute> {
Vec::new()
}

/// Any additional impl blocks for the struct holding the variables of an operation.
fn additional_impls_for_variables_struct(
&self,
#[allow(unused_variables)] operation_definition: &impl OperationDefinition,
) -> Vec<syn::ItemImpl> {
Vec::new()
}

/// Any attributes for a field of the struct holding the variables of an operation. Does not need to include the doc
/// string attribute, that will be added automatically.
fn attributes_for_variables_struct_field(
&self,
#[allow(unused_variables)] variable_definition: &impl VariableDefinition,
#[allow(unused_variables)] borrows: bool,
) -> Vec<syn::Attribute> {
Vec::new()
}
}
107 changes: 63 additions & 44 deletions bluejay-typegen-codegen/src/executable_definition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ mod executable_enum_variant_builder;
mod executable_struct_builder;
mod executable_type_builder;
mod intermediate_representation;
mod variables_struct_builder;

use executable_enum_builder::ExecutableEnumBuilder;
use executable_enum_variant_builder::ExecutableEnumVariantBuilder;
Expand All @@ -24,16 +25,17 @@ use executable_type_builder::ExecutableTypeBuilder;
pub use intermediate_representation::{
ExecutableEnum, ExecutableField, ExecutableStruct, ExecutableType, WrappedExecutableType,
};
use variables_struct_builder::VariablesStructBuilder;

mod kw {
syn::custom_keyword!(custom_scalar_overrides);
}

pub(crate) struct CustomScalarOverride {
graphql_path_token: syn::LitStr,
graphql_path: Vec<String>,
pub(crate) graphql_path: Vec<String>,
type_token: syn::Type,
borrows: bool,
pub(crate) borrows: bool,
}

impl Parse for CustomScalarOverride {
Expand All @@ -55,6 +57,46 @@ impl Parse for CustomScalarOverride {
}

impl CustomScalarOverride {
/// Parses the braced map that is the value of `custom_scalar_overrides`.
pub(crate) fn parse_all(
input: syn::parse::ParseStream,
) -> syn::Result<syn::punctuated::Punctuated<Self, syn::Token![,]>> {
let content;
syn::braced!(content in input);
syn::punctuated::Punctuated::parse_terminated(&content)
}

/// Returns `overrides`, or an error for each whose path `is_custom_scalar_path` rejects or that borrows without the
/// `borrow` option.
pub(crate) fn validate_all(
overrides: Option<syn::punctuated::Punctuated<Self, syn::Token![,]>>,
borrow: bool,
path_error: &str,
is_custom_scalar_path: impl Fn(&[String]) -> bool,
) -> syn::Result<Vec<Self>> {
let (valid, errors): (Vec<_>, Vec<syn::Error>) =
overrides.into_iter().flatten().partition_map(|c| {
if !is_custom_scalar_path(&c.graphql_path) {
Either::Right(syn::Error::new(c.graphql_path_token.span(), path_error))
} else if c.borrows && !borrow {
Either::Right(syn::Error::new(
c.type_token.span(),
"Custom scalar overrides must not borrow if the `borrow` option is not enabled",
))
} else {
Either::Left(c)
}
});

match errors.into_iter().reduce(|mut acc, error| {
acc.combine(error);
acc
}) {
Some(error) => Err(error),
None => Ok(valid),
}
}

fn graphql_path(lit_str: &syn::LitStr) -> Vec<String> {
lit_str.value().split('.').map(|s| s.to_string()).collect()
}
Expand Down Expand Up @@ -105,7 +147,7 @@ impl CustomScalarOverride {
Ok(true)
}

fn r#type(&self) -> &syn::Type {
pub(crate) fn r#type(&self) -> &syn::Type {
&self.type_token
}
}
Expand All @@ -126,11 +168,11 @@ impl Parse for Input {
input.parse::<syn::Token![,]>()?;
let lookahead = input.lookahead1();
if lookahead.peek(kw::custom_scalar_overrides) {
parse_key_value_with(input, &mut custom_scalar_overrides, |input| {
let content;
syn::braced!(content in input);
syn::punctuated::Punctuated::parse_terminated(&content)
})?;
parse_key_value_with(
input,
&mut custom_scalar_overrides,
CustomScalarOverride::parse_all,
)?;
} else {
return Err(lookahead.error());
}
Expand Down Expand Up @@ -196,50 +238,27 @@ pub(crate) fn generate_executable_definition<S: SchemaDefinition, C: CodeGenerat
));
}

let custom_scalar_overrides: Vec<CustomScalarOverride> = custom_scalar_overrides
.map(|c| c.into_iter().collect())
.unwrap_or_default();

let (valid_custom_scalar_overrides, custom_scalar_override_errors): (Vec<_>, Vec<syn::Error>) =
custom_scalar_overrides
.into_iter()
.partition_map(|c| {
if paths_with_custom_scalar_type.contains(&c.graphql_path) {
if c.borrows && !config.borrow() {
Either::Right(syn::Error::new(
c.type_token.span(),
"Custom scalar overrides must not borrow if the `borrow` option is not enabled",
))
} else {
Either::Left(c)
}
} else {
Either::Right(syn::Error::new(
c.graphql_path_token.span(),
"Custom scalar overrides must correspond to a path in the query that is a custom scalar type",
))
}
});

if let Some(combined_error) =
custom_scalar_override_errors
.into_iter()
.reduce(|mut acc, error| {
acc.combine(error);
acc
})
{
return Err(combined_error);
}
let custom_scalar_overrides = CustomScalarOverride::validate_all(
custom_scalar_overrides,
config.borrow(),
"Custom scalar overrides must correspond to a path in the query that is a custom scalar type",
|path| paths_with_custom_scalar_type.contains(path),
)?;

let executable_types = ExecutableType::for_executable_document(
&executable_document,
config,
valid_custom_scalar_overrides,
custom_scalar_overrides,
);

Ok(executable_types
.iter()
.flat_map(|et| ExecutableTypeBuilder::build(et, config.code_generator()))
.chain(
executable_document
.operation_definitions()
.iter()
.flat_map(|od| VariablesStructBuilder::build(od, config, &validation_cache)),
)
.collect())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
use crate::attributes::doc_string;
use crate::input_type::{base_input_type, base_input_type_borrows};
use crate::names::{field_ident, variables_type_ident};
use crate::{types, CodeGenerator, Config};
use bluejay_core::definition::{BaseInputTypeReference, SchemaDefinition};
use bluejay_core::executable::{ExecutableDocument, OperationDefinition, VariableDefinition};
use bluejay_core::AsIter;
use bluejay_validator::executable::{document::VariableDefinitionInputType, Cache};
use std::collections::HashSet;
use syn::parse_quote;

/// Builds the struct holding the variables of an operation, which sits next to the struct for the operation.
pub(crate) struct VariablesStructBuilder<
'a,
E: ExecutableDocument,
S: SchemaDefinition,
C: CodeGenerator,
> {
config: &'a Config<'a, S, C>,
cache: &'a Cache<'a, E, S>,
}

impl<'a, E: ExecutableDocument, S: SchemaDefinition, C: CodeGenerator>
VariablesStructBuilder<'a, E, S, C>
{
/// Builds nothing if the operation does not define any variables.
pub(crate) fn build(
operation_definition: &'a E::OperationDefinition,
config: &'a Config<'a, S, C>,
cache: &'a Cache<'a, E, S>,
) -> Vec<syn::Item> {
let operation_definition_reference = operation_definition.as_ref();
let Some(variable_definitions) = operation_definition_reference
.variable_definitions()
.filter(|variable_definitions| !variable_definitions.is_empty())
else {
return Vec::new();
};

let instance = Self { config, cache };

let (fields, borrows): (Vec<syn::Field>, Vec<bool>) = variable_definitions
.iter()
.map(|variable_definition| instance.field(variable_definition))
.unzip();

let attributes = config
.code_generator()
.attributes_for_variables_struct(operation_definition);
let name_ident = variables_type_ident(operation_definition_reference.name());
let lifetime: Option<syn::Generics> =
borrows.contains(&true).then(|| parse_quote! { <'a> });

let mut items = vec![parse_quote! {
#(#attributes)*
pub struct #name_ident #lifetime {
#(#fields,)*
}
}];

items.extend(
config
.code_generator()
.additional_impls_for_variables_struct(operation_definition)
.into_iter()
.map(syn::Item::Impl),
);

items
}

/// The field for the variable, and whether its type borrows.
fn field(&self, variable_definition: &'a E::VariableDefinition) -> (syn::Field, bool) {
let input_type = self
.cache
.variable_definition_input_type(variable_definition.r#type())
.expect("Variable types are validated to be input types");
let borrows =
base_input_type_borrows(self.config, Self::base(input_type), &mut HashSet::new());

let field_ident = field_ident(variable_definition.variable());
let field_type = self.type_for(input_type, variable_definition.default_value().is_some());
let description_attribute = variable_definition.description().map(doc_string);
let field_attributes = self
.config
.code_generator()
.attributes_for_variables_struct_field(variable_definition, borrows);

let field = parse_quote! {
#description_attribute
#(#field_attributes)*
pub #field_ident: #field_type
};

(field, borrows)
}

/// A variable with a default value can be omitted even if its type is required, so its type is optional.
fn type_for(
&self,
input_type: &VariableDefinitionInputType<'a, S>,
has_default_value: bool,
) -> syn::Type {
let (inner, required) = match input_type {
// the struct is at the root of the query module, one level below the schema definition module
VariableDefinitionInputType::Base(base, required) => {
(base_input_type(self.config, *base, 1), *required)
}
VariableDefinitionInputType::List(inner, required) => {
(types::vec(self.type_for(inner, false)), *required)
}
};

if required && !has_default_value {
inner
} else {
types::option(inner)
}
}

fn base(input_type: &VariableDefinitionInputType<'a, S>) -> BaseInputTypeReference<'a, S> {
match input_type {
VariableDefinitionInputType::Base(base, _) => *base,
VariableDefinitionInputType::List(inner, _) => Self::base(inner),
}
}
}
12 changes: 12 additions & 0 deletions bluejay-typegen-codegen/src/input.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use crate::executable_definition::CustomScalarOverride;
use quote::{ToTokens, TokenStreamExt};
use syn::parse::Parse;

mod kw {
syn::custom_keyword!(borrow);
syn::custom_keyword!(enums_as_str);
syn::custom_keyword!(custom_scalar_overrides);
}

pub enum DocumentInput {
Expand Down Expand Up @@ -65,6 +67,8 @@ pub struct Input {
pub(crate) schema: DocumentInput,
pub borrow: Option<syn::LitBool>,
pub enums_as_str: syn::punctuated::Punctuated<syn::LitStr, syn::Token![,]>,
pub(crate) custom_scalar_overrides:
Option<syn::punctuated::Punctuated<CustomScalarOverride, syn::Token![,]>>,
}

impl Parse for Input {
Expand All @@ -73,6 +77,7 @@ impl Parse for Input {

let mut borrow: Option<syn::LitBool> = None;
let mut enums_as_str = None;
let mut custom_scalar_overrides = None;

while !input.is_empty() {
input.parse::<syn::Token![,]>()?;
Expand All @@ -85,6 +90,12 @@ impl Parse for Input {
syn::bracketed!(content in input);
syn::punctuated::Punctuated::parse_separated_nonempty(&content)
})?;
} else if lookahead.peek(kw::custom_scalar_overrides) {
parse_key_value_with(
input,
&mut custom_scalar_overrides,
CustomScalarOverride::parse_all,
)?;
} else {
return Err(lookahead.error());
}
Expand All @@ -96,6 +107,7 @@ impl Parse for Input {
schema,
borrow,
enums_as_str,
custom_scalar_overrides,
})
}
}
Expand Down
Loading
Loading