diff --git a/bluejay-typegen-codegen/src/executable_definition.rs b/bluejay-typegen-codegen/src/executable_definition.rs index b1aaa50..a5370e7 100644 --- a/bluejay-typegen-codegen/src/executable_definition.rs +++ b/bluejay-typegen-codegen/src/executable_definition.rs @@ -1,6 +1,6 @@ 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 _}; @@ -8,8 +8,7 @@ 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; @@ -29,87 +28,6 @@ mod kw { syn::custom_keyword!(custom_scalar_overrides); } -pub(crate) struct CustomScalarOverride { - graphql_path_token: syn::LitStr, - graphql_path: Vec, - type_token: syn::Type, - borrows: bool, -} - -impl Parse for CustomScalarOverride { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - let graphql_path_token = input.parse()?; - let graphql_path = Self::graphql_path(&graphql_path_token); - input.parse::]>()?; - 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 { - lit_str.value().split('.').map(|s| s.to_string()).collect() - } - - fn type_borrows(ty: &syn::Type) -> syn::Result { - 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: @@ -126,11 +44,11 @@ impl Parse for Input { input.parse::()?; 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()); } @@ -196,46 +114,17 @@ pub(crate) fn generate_executable_definition = custom_scalar_overrides - .map(|c| c.into_iter().collect()) - .unwrap_or_default(); - - let (valid_custom_scalar_overrides, custom_scalar_override_errors): (Vec<_>, Vec) = - 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 diff --git a/bluejay-typegen-codegen/src/executable_definition/intermediate_representation.rs b/bluejay-typegen-codegen/src/executable_definition/intermediate_representation.rs index d19c9d6..5856a2c 100644 --- a/bluejay-typegen-codegen/src/executable_definition/intermediate_representation.rs +++ b/bluejay-typegen-codegen/src/executable_definition/intermediate_representation.rs @@ -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, }; diff --git a/bluejay-typegen-codegen/src/input.rs b/bluejay-typegen-codegen/src/input.rs index d368cb7..8806a93 100644 --- a/bluejay-typegen-codegen/src/input.rs +++ b/bluejay-typegen-codegen/src/input.rs @@ -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 { @@ -61,10 +63,133 @@ impl DocumentInput { } } +pub(crate) struct CustomScalarOverride { + graphql_path_token: syn::LitStr, + pub(crate) graphql_path: Vec, + type_token: syn::Type, + pub(crate) borrows: bool, +} + +impl Parse for CustomScalarOverride { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let graphql_path_token = input.parse()?; + let graphql_path = Self::graphql_path(&graphql_path_token); + input.parse::]>()?; + 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> { + 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>, + borrow: bool, + path_error: &str, + is_custom_scalar_path: impl Fn(&[String]) -> bool, + ) -> syn::Result> { + let (valid, errors): (Vec<_>, Vec) = + 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 { + lit_str.value().split('.').map(|s| s.to_string()).collect() + } + + fn type_borrows(ty: &syn::Type) -> syn::Result { + 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, pub enums_as_str: syn::punctuated::Punctuated, + pub(crate) custom_scalar_overrides: + Option>, } impl Parse for Input { @@ -73,6 +198,7 @@ impl Parse for Input { let mut borrow: Option = None; let mut enums_as_str = None; + let mut custom_scalar_overrides = None; while !input.is_empty() { input.parse::()?; @@ -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()); } @@ -96,6 +228,7 @@ impl Parse for Input { schema, borrow, enums_as_str, + custom_scalar_overrides, }) } } diff --git a/bluejay-typegen-codegen/src/input_object_type_definition.rs b/bluejay-typegen-codegen/src/input_object_type_definition.rs index 3949dfe..2439a74 100644 --- a/bluejay-typegen-codegen/src/input_object_type_definition.rs +++ b/bluejay-typegen-codegen/src/input_object_type_definition.rs @@ -97,15 +97,18 @@ impl<'a, S: SchemaDefinition, C: CodeGenerator> InputObjectTypeDefinitionBuilder .iter() .map(|ivd| { let field_ident = field_ident(ivd.name()); - let field_type = self - .type_for_input_value_definition(self.input_object_type_definition.name(), ivd); + let field_type = self.type_for_input_value_definition(ivd); let description_attribute = ivd.description().map(doc_string); let field_attributes = self .config .code_generator() .attributes_for_input_object_field( ivd, - self.contains_reference_types(ivd.r#type(), &mut HashSet::new()), + self.input_field_contains_reference_types( + self.input_object_type_definition, + ivd, + &mut HashSet::new(), + ), ); parse_quote! { @@ -227,7 +230,20 @@ impl<'a, S: SchemaDefinition, C: CodeGenerator> InputObjectTypeDefinitionBuilder ) -> bool { iotd.input_field_definitions() .iter() - .any(|ivd| self.contains_reference_types(ivd.r#type(), visited)) + .any(|ivd| self.input_field_contains_reference_types(iotd, ivd, visited)) + } + + /// Whether the type for `ivd`, a field of `iotd`, borrows, skipping types already in `visited`. + fn input_field_contains_reference_types( + &self, + iotd: &'a S::InputObjectTypeDefinition, + ivd: &'a S::InputValueDefinition, + visited: &mut HashSet<&'a str>, + ) -> bool { + self.config.custom_scalar_override(iotd, ivd).map_or_else( + || self.contains_reference_types(ivd.r#type(), visited), + |custom_scalar_override| custom_scalar_override.borrows, + ) } fn type_for_base_input_type(&self, base: BaseInputTypeReference) -> syn::Type { @@ -259,11 +275,13 @@ impl<'a, S: SchemaDefinition, C: CodeGenerator> InputObjectTypeDefinitionBuilder } } + /// The type for `ty`, with `custom_scalar_override` in place of its base type if given. fn type_for_input_type( &self, ty: InputTypeReference, parent_type_name: Option<&str>, has_default_value: Option, + custom_scalar_override: Option<&syn::Type>, ) -> syn::Type { let required = has_default_value.map_or_else( || ty.is_required(), @@ -271,7 +289,9 @@ impl<'a, S: SchemaDefinition, C: CodeGenerator> InputObjectTypeDefinitionBuilder ); match ty { InputTypeReference::Base(base, _) => { - let mut inner = self.type_for_base_input_type(base); + let mut inner = custom_scalar_override + .cloned() + .unwrap_or_else(|| self.type_for_base_input_type(base)); if let Some(parent_type_name) = parent_type_name { if self.contains_non_list_reference(parent_type_name, ty, &mut HashSet::new()) { inner = parse_quote! { ::std::boxed::Box<#inner> }; @@ -288,6 +308,7 @@ impl<'a, S: SchemaDefinition, C: CodeGenerator> InputObjectTypeDefinitionBuilder inner.as_ref(self.config.schema_definition()), None, None, + custom_scalar_override, )); if required { inner_ty @@ -298,18 +319,21 @@ impl<'a, S: SchemaDefinition, C: CodeGenerator> InputObjectTypeDefinitionBuilder } } - fn type_for_input_value_definition( - &self, - parent_type_name: &str, - ivd: &S::InputValueDefinition, - ) -> syn::Type { + fn type_for_input_value_definition(&self, ivd: &S::InputValueDefinition) -> syn::Type { self.type_for_input_type( ivd.r#type().as_ref(self.config.schema_definition()), - Some(parent_type_name), + Some(self.input_object_type_definition.name()), Some(ivd.default_value().is_some()), + self.custom_scalar_override(ivd), ) } + fn custom_scalar_override(&self, ivd: &S::InputValueDefinition) -> Option<&syn::Type> { + self.config + .custom_scalar_override(self.input_object_type_definition, ivd) + .map(|custom_scalar_override| custom_scalar_override.r#type()) + } + fn variant_type(&self, ivd: &S::InputValueDefinition) -> syn::Type { // since we're building a oneOf enum, all types are optional, but we need to make // them required for the enum variant @@ -321,6 +345,7 @@ impl<'a, S: SchemaDefinition, C: CodeGenerator> InputObjectTypeDefinitionBuilder required_type, Some(self.input_object_type_definition.name()), None, + self.custom_scalar_override(ivd), ) } } diff --git a/bluejay-typegen-codegen/src/lib.rs b/bluejay-typegen-codegen/src/lib.rs index a9d4695..cdb69d5 100644 --- a/bluejay-typegen-codegen/src/lib.rs +++ b/bluejay-typegen-codegen/src/lib.rs @@ -1,5 +1,5 @@ use bluejay_core::{ - definition::{prelude::*, SchemaDefinition, TypeDefinitionReference}, + definition::{prelude::*, BaseInputTypeReference, SchemaDefinition, TypeDefinitionReference}, BuiltinScalarDefinition, }; use bluejay_parser::{ @@ -31,8 +31,8 @@ use executable_definition::generate_executable_definition; pub use executable_definition::{ ExecutableEnum, ExecutableField, ExecutableStruct, ExecutableType, WrappedExecutableType, }; -use input::DocumentInput; pub use input::Input; +use input::{CustomScalarOverride, DocumentInput}; use input_object_type_definition::InputObjectTypeDefinitionBuilder; pub(crate) struct Config<'a, S: SchemaDefinition, C: CodeGenerator> { @@ -40,6 +40,7 @@ pub(crate) struct Config<'a, S: SchemaDefinition, C: CodeGenerator> { schema_definition: &'a S, custom_scalar_borrows: HashMap, enums_as_str: HashSet, + custom_scalar_overrides: Vec, code_generator: &'a C, } @@ -67,6 +68,17 @@ impl<'a, S: SchemaDefinition, C: CodeGenerator> Config<'a, S, C> { self.enums_as_str.contains(etd.name()) } + /// The override for the type of `ivd`, a field of `iotd`. + pub(crate) fn custom_scalar_override( + &self, + iotd: &S::InputObjectTypeDefinition, + ivd: &S::InputValueDefinition, + ) -> Option<&CustomScalarOverride> { + self.custom_scalar_overrides + .iter() + .find(|c| c.graphql_path == [iotd.name(), ivd.name()]) + } + pub(crate) fn code_generator(&self) -> &C { self.code_generator } @@ -82,6 +94,7 @@ pub fn generate_schema( ref schema, borrow, enums_as_str, + custom_scalar_overrides, } = input; let borrow = borrow.is_some_and(|lit| lit.value()); @@ -116,11 +129,19 @@ pub fn generate_schema( let enums_as_str = validate_enums_as_str(enums_as_str, &schema_definition)?; + let custom_scalar_overrides = CustomScalarOverride::validate_all( + custom_scalar_overrides, + borrow, + "Custom scalar overrides must correspond to an input object field that is a custom scalar type", + |path| is_custom_scalar_input_field(&schema_definition, path), + )?; + let config = Config { schema_definition: &schema_definition, borrow, custom_scalar_borrows, enums_as_str, + custom_scalar_overrides, code_generator: &code_generator, }; @@ -283,6 +304,29 @@ fn validate_enums_as_str( Ok(enum_names) } +/// Whether `path` is `[input object name, field name]` for a field whose base type is a custom scalar. +fn is_custom_scalar_input_field( + schema_definition: &impl SchemaDefinition, + path: &[String], +) -> bool { + let [type_name, field_name] = path else { + return false; + }; + let Some(TypeDefinitionReference::InputObject(iotd)) = + schema_definition.get_type_definition(type_name) + else { + return false; + }; + iotd.input_field_definitions() + .get(field_name) + .is_some_and(|ivd| { + matches!( + ivd.r#type().base(schema_definition), + BaseInputTypeReference::CustomScalar(_) + ) + }) +} + fn process_module_items( config: &Config, items: Vec, diff --git a/bluejay-typegen-macro/src/lib.rs b/bluejay-typegen-macro/src/lib.rs index a2897c1..baad852 100644 --- a/bluejay-typegen-macro/src/lib.rs +++ b/bluejay-typegen-macro/src/lib.rs @@ -22,6 +22,9 @@ use syn::{parse_macro_input, parse_quote}; /// _borrow_: Boolean literal indicating whether the generated types should borrow where possible. Defaults to `false`. /// When `true`, deserializing must be done from a string as a opposed to `serde_json::Value` or a reader. /// +/// _custom_scalar_overrides_: Map from `"InputObject.field"` to the type to use for that input object field instead +/// of its custom scalar. See [custom scalar overrides](#custom-scalar-overrides). +/// /// ### Trait implementations /// /// By default, will implement `PartialEq`, `Eq`, `Clone`, and `Debug` for all types. Will implement `Copy` for enums. @@ -56,6 +59,10 @@ use syn::{parse_macro_input, parse_quote}; /// "MyQuery.myField" => ::std::primitive::i32, /// })] /// ``` +/// The same argument on the `#[typegen(...)]` attribute overrides the type of a custom scalar field of an input object, +/// with paths of the form `"MyInput.myField"`. Lists and nullability are kept, so a nullable field is an `Option` of the +/// type. +/// /// Any type path that does not start with `::` is assumed to be relative to the schema definition module. /// Types may have a single lifetime parameter, which must be named `a`. Generic types are not supported. /// If you need to use a generic type, use an alias for the type to remove the generic parameters. diff --git a/bluejay-typegen/src/lib.rs b/bluejay-typegen/src/lib.rs index e9b2256..0c853a4 100644 --- a/bluejay-typegen/src/lib.rs +++ b/bluejay-typegen/src/lib.rs @@ -13,6 +13,8 @@ //! - `borrow`: A boolean indicating whether the generated types should borrow strings from the input JSON value instead of owning them. Defaults to `false`. //! - `enums_as_str`: An array of string literals containing the names of enum types from the GraphQL schema that should be represented as strings. Defaults to `[]`. //! When `borrow` is true, the values are `std::borrow::Cow`, otherwise they are `String`. +//! - `custom_scalar_overrides`: A map from `"InputObject.field"` to the type to use for that input object field instead of its custom scalar, e.g. `{ "MyInput.myField" => ::std::primitive::i32 }`. Lists and nullability are kept. +//! Type paths that do not start with `::` are relative to the module. Defaults to `{}`. //! //! #### Queries //! Within the module defining the schema definition, a submodule can be defined for any number of executable documents. diff --git a/bluejay-typegen/tests/validation_cases/error/invalid_custom_scalar_overrides.rs b/bluejay-typegen/tests/validation_cases/error/invalid_query_custom_scalar_overrides.rs similarity index 100% rename from bluejay-typegen/tests/validation_cases/error/invalid_custom_scalar_overrides.rs rename to bluejay-typegen/tests/validation_cases/error/invalid_query_custom_scalar_overrides.rs diff --git a/bluejay-typegen/tests/validation_cases/error/invalid_custom_scalar_overrides.stderr b/bluejay-typegen/tests/validation_cases/error/invalid_query_custom_scalar_overrides.stderr similarity index 70% rename from bluejay-typegen/tests/validation_cases/error/invalid_custom_scalar_overrides.stderr rename to bluejay-typegen/tests/validation_cases/error/invalid_query_custom_scalar_overrides.stderr index 6fe069e..52b191f 100644 --- a/bluejay-typegen/tests/validation_cases/error/invalid_custom_scalar_overrides.stderr +++ b/bluejay-typegen/tests/validation_cases/error/invalid_query_custom_scalar_overrides.stderr @@ -1,17 +1,17 @@ error: Custom scalar overrides must correspond to a path in the query that is a custom scalar type - --> tests/validation_cases/error/invalid_custom_scalar_overrides.rs:22:9 + --> tests/validation_cases/error/invalid_query_custom_scalar_overrides.rs:22:9 | 22 | "MyQuery.myField" => super::MyScalarOverride, | ^^^^^^^^^^^^^^^^^ error: Custom scalar overrides must correspond to a path in the query that is a custom scalar type - --> tests/validation_cases/error/invalid_custom_scalar_overrides.rs:23:9 + --> tests/validation_cases/error/invalid_query_custom_scalar_overrides.rs:23:9 | 23 | "MyQuery.myOtherField" => ::std::primitive::i32, | ^^^^^^^^^^^^^^^^^^^^^^ error: Custom scalar overrides must not borrow if the `borrow` option is not enabled - --> tests/validation_cases/error/invalid_custom_scalar_overrides.rs:24:36 + --> tests/validation_cases/error/invalid_query_custom_scalar_overrides.rs:24:36 | 24 | "MyQuery.myScalarField" => super::StringCow<'a>, | ^^^^^ diff --git a/bluejay-typegen/tests/validation_cases/error/invalid_schema_custom_scalar_overrides.rs b/bluejay-typegen/tests/validation_cases/error/invalid_schema_custom_scalar_overrides.rs new file mode 100644 index 0000000..8be7c30 --- /dev/null +++ b/bluejay-typegen/tests/validation_cases/error/invalid_schema_custom_scalar_overrides.rs @@ -0,0 +1,24 @@ +type StringCow<'a> = std::borrow::Cow<'a, str>; + +#[bluejay_typegen::typegen([ + scalar MyScalar + + type Query { + myField(input: MyInput): MyScalar + } + + input MyInput { + myField: Int + myScalarField: MyScalar + } +], custom_scalar_overrides = { + "MyInput.myField" => ::std::primitive::i32, + "MyInput.myMissingField" => ::std::primitive::i32, + "Query.myField" => ::std::primitive::i32, + "MyInput.myScalarField" => super::StringCow<'a>, +})] +mod schema { + type MyScalar = String; +} + +fn main() {} diff --git a/bluejay-typegen/tests/validation_cases/error/invalid_schema_custom_scalar_overrides.stderr b/bluejay-typegen/tests/validation_cases/error/invalid_schema_custom_scalar_overrides.stderr new file mode 100644 index 0000000..393662a --- /dev/null +++ b/bluejay-typegen/tests/validation_cases/error/invalid_schema_custom_scalar_overrides.stderr @@ -0,0 +1,23 @@ +error: Custom scalar overrides must correspond to an input object field that is a custom scalar type + --> tests/validation_cases/error/invalid_schema_custom_scalar_overrides.rs:15:5 + | +15 | "MyInput.myField" => ::std::primitive::i32, + | ^^^^^^^^^^^^^^^^^ + +error: Custom scalar overrides must correspond to an input object field that is a custom scalar type + --> tests/validation_cases/error/invalid_schema_custom_scalar_overrides.rs:16:5 + | +16 | "MyInput.myMissingField" => ::std::primitive::i32, + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: Custom scalar overrides must correspond to an input object field that is a custom scalar type + --> tests/validation_cases/error/invalid_schema_custom_scalar_overrides.rs:17:5 + | +17 | "Query.myField" => ::std::primitive::i32, + | ^^^^^^^^^^^^^^^ + +error: Custom scalar overrides must not borrow if the `borrow` option is not enabled + --> tests/validation_cases/error/invalid_schema_custom_scalar_overrides.rs:18:32 + | +18 | "MyInput.myScalarField" => super::StringCow<'a>, + | ^^^^^ diff --git a/bluejay-typegen/tests/validation_cases/valid/valid_custom_scalar_override.rs b/bluejay-typegen/tests/validation_cases/valid/valid_query_custom_scalar_override.rs similarity index 100% rename from bluejay-typegen/tests/validation_cases/valid/valid_custom_scalar_override.rs rename to bluejay-typegen/tests/validation_cases/valid/valid_query_custom_scalar_override.rs diff --git a/bluejay-typegen/tests/validation_cases/valid/valid_schema_custom_scalar_override.rs b/bluejay-typegen/tests/validation_cases/valid/valid_schema_custom_scalar_override.rs new file mode 100644 index 0000000..9588db3 --- /dev/null +++ b/bluejay-typegen/tests/validation_cases/valid/valid_schema_custom_scalar_override.rs @@ -0,0 +1,36 @@ +type StringCow<'a> = std::borrow::Cow<'a, str>; + +#[bluejay_typegen::typegen([ + scalar MyScalar + + type Query { + myField(input: MyInput, oneOfInput: MyOneOfInput): Int + } + + input MyInput { + myScalar: MyScalar + myScalarList: [MyScalar!]! + myBorrowedScalar: MyScalar! + } + + input MyOneOfInput @oneOf { + myScalar: MyScalar + } +], borrow = true, custom_scalar_overrides = { + "MyInput.myScalar" => ::std::primitive::i32, + "MyInput.myScalarList" => ::std::primitive::i32, + "MyInput.myBorrowedScalar" => super::StringCow<'a>, + "MyOneOfInput.myScalar" => ::std::primitive::i32, +})] +pub mod schema { + type MyScalar = String; +} + +fn main() { + let _ = schema::MyInput { + my_scalar: Some(1), + my_scalar_list: vec![1], + my_borrowed_scalar: "hello".into(), + }; + let _ = schema::MyOneOfInput::MyScalar(1); +}