Skip to content
Merged
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
141 changes: 15 additions & 126 deletions bluejay-typegen-codegen/src/executable_definition.rs

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we should move CustomScalarOverride to input.rs now that it isn't specific to executables now?

@saga-dasgupta saga-dasgupta Oct 6, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, moved to input.rs in f28d633

Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
use crate::{
input::parse_key_value_with, map_parser_errors, validation, CodeGenerator, Config,
DocumentInput,
input::{parse_key_value_with, CustomScalarOverride},
map_parser_errors, validation, CodeGenerator, Config, DocumentInput,
};
use bluejay_core::definition::SchemaDefinition;
use bluejay_parser::ast::{executable::ExecutableDocument, Parse as _};
use bluejay_validator::executable::{
document::{BuiltinRulesValidator, Orchestrator},
Cache,
};
use itertools::{Either, Itertools};
use syn::{parse::Parse, parse2, spanned::Spanned};
use syn::{parse::Parse, parse2};

mod executable_enum_builder;
mod executable_enum_variant_builder;
Expand All @@ -29,87 +28,6 @@ mod kw {
syn::custom_keyword!(custom_scalar_overrides);
}

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

impl Parse for CustomScalarOverride {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let graphql_path_token = input.parse()?;
let graphql_path = Self::graphql_path(&graphql_path_token);
input.parse::<syn::Token![=>]>()?;
let type_token = input.parse()?;

let borrows = Self::type_borrows(&type_token)?;

Ok(Self {
graphql_path_token,
graphql_path,
type_token,
borrows,
})
}
}

impl CustomScalarOverride {
fn graphql_path(lit_str: &syn::LitStr) -> Vec<String> {
lit_str.value().split('.').map(|s| s.to_string()).collect()
}

fn type_borrows(ty: &syn::Type) -> syn::Result<bool> {
let path = match ty {
syn::Type::Path(path) => path,
// allow the `()` type
syn::Type::Tuple(tuple) if tuple.elems.is_empty() => return Ok(false),
_ => {
return Err(syn::Error::new(
ty.span(),
"Unsupported type for custom scalar overrides",
));
}
};

let Some(last_segment) = path.path.segments.last() else {
return Err(syn::Error::new(
path.span(),
"Path must have at least one segment",
));
};

let path_arguments = match &last_segment.arguments {
syn::PathArguments::None => return Ok(false),
syn::PathArguments::AngleBracketed(bracketed) => bracketed,
syn::PathArguments::Parenthesized(parenthesized) => {
return Err(syn::Error::new(
parenthesized.span(),
"Paths for custom scalar overrides must not contain parenthesized generic arguments",
));
}
};

if path_arguments.args.len() != 1
|| !matches!(
path_arguments.args.first(),
Some(syn::GenericArgument::Lifetime(lifetime)) if lifetime.ident != "'a"
)
{
return Err(syn::Error::new(
ty.span(),
"Paths for custom scalar overrides with generic arguments must contain a single lifetime parameter 'a",
));
}

Ok(true)
}

fn r#type(&self) -> &syn::Type {
&self.type_token
}
}

struct Input {
query: DocumentInput,
custom_scalar_overrides:
Expand All @@ -126,11 +44,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,46 +114,17 @@ 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

use crate::{
builtin_scalar::builtin_scalar_type,
executable_definition::CustomScalarOverride,
input::CustomScalarOverride,
names::{module_ident, type_ident, ANONYMOUS_OPERATION_STRUCT_NAME},
types, CodeGenerator, Config,
};
Expand Down
135 changes: 134 additions & 1 deletion bluejay-typegen-codegen/src/input.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use itertools::{Either, Itertools};
use quote::{ToTokens, TokenStreamExt};
use syn::parse::Parse;
use syn::{parse::Parse, spanned::Spanned};

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 @@ -61,10 +63,133 @@ impl DocumentInput {
}
}

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

impl Parse for CustomScalarOverride {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let graphql_path_token = input.parse()?;
let graphql_path = Self::graphql_path(&graphql_path_token);
input.parse::<syn::Token![=>]>()?;
let type_token = input.parse()?;

let borrows = Self::type_borrows(&type_token)?;

Ok(Self {
graphql_path_token,
graphql_path,
type_token,
borrows,
})
}
}

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()
}

fn type_borrows(ty: &syn::Type) -> syn::Result<bool> {
let path = match ty {
syn::Type::Path(path) => path,
// allow the `()` type
syn::Type::Tuple(tuple) if tuple.elems.is_empty() => return Ok(false),
_ => {
return Err(syn::Error::new(
ty.span(),
"Unsupported type for custom scalar overrides",
));
}
};

let Some(last_segment) = path.path.segments.last() else {
return Err(syn::Error::new(
path.span(),
"Path must have at least one segment",
));
};

let path_arguments = match &last_segment.arguments {
syn::PathArguments::None => return Ok(false),
syn::PathArguments::AngleBracketed(bracketed) => bracketed,
syn::PathArguments::Parenthesized(parenthesized) => {
return Err(syn::Error::new(
parenthesized.span(),
"Paths for custom scalar overrides must not contain parenthesized generic arguments",
));
}
};

if path_arguments.args.len() != 1
|| !matches!(
path_arguments.args.first(),
Some(syn::GenericArgument::Lifetime(lifetime)) if lifetime.ident != "'a"
)
{
return Err(syn::Error::new(
ty.span(),
"Paths for custom scalar overrides with generic arguments must contain a single lifetime parameter 'a",
));
}

Ok(true)
}

pub(crate) fn r#type(&self) -> &syn::Type {
&self.type_token
}
}

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 +198,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 +211,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 +228,7 @@ impl Parse for Input {
schema,
borrow,
enums_as_str,
custom_scalar_overrides,
})
}
}
Expand Down
Loading
Loading