From 4e58054595b9effc8970ade54e5378181823fbc7 Mon Sep 17 00:00:00 2001 From: Marco Fuijkschot Date: Fri, 9 May 2025 15:09:25 +0200 Subject: [PATCH] modified: juniper/src/lib.rs modified: juniper/src/schema/model.rs# modified: juniper/src/schema/translate/graphql_parser.rs Added new function to create a schema with custom directives Added doctest Added directives to be included in the schema output --- juniper/src/lib.rs | 2 +- juniper/src/schema/model.rs | 140 +++++++++++++++++- .../src/schema/translate/graphql_parser.rs | 56 ++++++- 3 files changed, 190 insertions(+), 8 deletions(-) diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index 15d9f3a53..7f0dd2be8 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -85,7 +85,7 @@ pub use crate::{ parser::{ParseError, ScalarToken, Span, Spanning}, schema::{ meta, - model::{RootNode, SchemaType}, + model::{RootNode, SchemaType, DirectiveType, DirectiveLocation}, }, types::{ async_await::{GraphQLTypeAsync, GraphQLValueAsync}, diff --git a/juniper/src/schema/model.rs b/juniper/src/schema/model.rs index 83c89c26f..da43b2be1 100644 --- a/juniper/src/schema/model.rs +++ b/juniper/src/schema/model.rs @@ -1,6 +1,7 @@ use std::{borrow::Cow, fmt}; use fnv::FnvHashMap; + #[cfg(feature = "schema-language")] use graphql_parser::schema::Document; @@ -53,7 +54,7 @@ pub struct SchemaType<'a, S> { pub(crate) query_type_name: String, pub(crate) mutation_type_name: Option, pub(crate) subscription_type_name: Option, - directives: FnvHashMap>, + pub(crate) directives: FnvHashMap>, } impl Context for SchemaType<'_, S> {} @@ -66,33 +67,51 @@ pub enum TypeType<'a, S: 'a> { } #[derive(Debug)] +/// Represents a graphql directive pub struct DirectiveType<'a, S> { + /// required a unique name per schema pub name: String, + /// optional a description pub description: Option, + /// required atleast a location pub locations: Vec, + /// Optional arguments pub arguments: Vec>, + /// Allow repeating a directive pub is_repeatable: bool, } #[derive(Clone, PartialEq, Eq, Debug, GraphQLEnum)] #[graphql(name = "__DirectiveLocation", internal)] +/// Describes the different allowed locations of a directive pub enum DirectiveLocation { + /// directive for query Query, + /// directive for Mutation Mutation, + /// directive for Subscription Subscription, + /// directive for Field Field, + /// directive for Scalar Scalar, #[graphql(name = "FRAGMENT_DEFINITION")] + /// directive for FragmentDefinition FragmentDefinition, #[graphql(name = "FIELD_DEFINITION")] + /// directive for FieldDefinition FieldDefinition, #[graphql(name = "VARIABLE_DEFINITION")] + /// directive for VariableDefinition VariableDefinition, #[graphql(name = "FRAGMENT_SPREAD")] + /// directive for FragmentSpread FragmentSpread, #[graphql(name = "INLINE_FRAGMENT")] + /// directive for InlineFragment InlineFragment, #[graphql(name = "ENUM_VALUE")] + /// directive for Enum EnumValue, } @@ -104,7 +123,7 @@ where SubscriptionT: GraphQLType, { /// Constructs a new [`RootNode`] from `query`, `mutation` and `subscription` nodes, - /// parametrizing it with a [`DefaultScalarValue`]. + /// parametrizing it with a [`DefaultScalarValue`] . pub fn new(query: QueryT, mutation: MutationT, subscription: SubscriptionT) -> Self { Self::new_with_info(query, mutation, subscription, (), (), ()) } @@ -118,7 +137,7 @@ where SubscriptionT: GraphQLType, { /// Constructs a new [`RootNode`] from `query`, `mutation` and `subscription` nodes, - /// parametrizing it with the provided [`ScalarValue`]. + /// parametrized it with the provided [`ScalarValue`]. pub fn new_with_scalar_value( query: QueryT, mutation: MutationT, @@ -126,6 +145,59 @@ where ) -> Self { RootNode::new_with_info(query, mutation, subscription, (), (), ()) } + + /// Constructs a new [`RootNode`] from `query`, `mutation` and `subscription` nodes, + /// parametrized it with a [`ScalarValue`] and directives + /// ```rust + /// use juniper::{ + /// graphql_object, graphql_vars, EmptyMutation, EmptySubscription, GraphQLError, + /// RootNode, DirectiveLocation , DirectiveType + /// }; + /// + /// struct Query{} + /// + /// #[graphql_object] + /// impl Query { + /// pub fn hello() -> String { + /// "Hello".to_string() + /// } + /// } + /// + /// type Schema = RootNode<'static, Query, EmptyMutation, EmptySubscription>; + /// + /// let schema = Schema::new_with_directives(Query {}, EmptyMutation::new(), EmptySubscription::new() + /// ,vec![ DirectiveType::new("my_directive", &[DirectiveLocation::Query] , &[] , false )]); + /// + /// let query = "query @my_directive { hello }"; + /// + /// match juniper::execute_sync(query, None, &schema, &graphql_vars! {}, &()) { + /// Err(GraphQLError::ValidationError(errs)) => { panic!("should not give an error"); } + /// res => {} + /// } + /// + /// let query = "query @non_existing_directive { hello }"; + /// + /// match juniper::execute_sync(query, None, &schema, &graphql_vars! {}, &()) { + /// Err(GraphQLError::ValidationError(errs)) => { } + /// res => { panic!("should give an error"); } + /// } + /// ``` + pub fn new_with_directives( + query: QueryT, + mutation: MutationT, + subscription: SubscriptionT, + custom_directives: Vec>, + ) -> Self { + Self::new_with_directives_and_info( + query, + mutation, + subscription, + custom_directives, + (), + (), + (), + ) + } } impl<'a, S, QueryT, MutationT, SubscriptionT> RootNode<'a, QueryT, MutationT, SubscriptionT, S> @@ -162,6 +234,34 @@ where } } + /// Construct a new root node with default meta types + /// and with custom directives + pub fn new_with_directives_and_info( + query_obj: QueryT, + mutation_obj: MutationT, + subscription_obj: SubscriptionT, + custom_directives: Vec>, + query_info: QueryT::TypeInfo, + mutation_info: MutationT::TypeInfo, + subscription_info: SubscriptionT::TypeInfo, + ) -> Self { + Self { + query_type: query_obj, + mutation_type: mutation_obj, + subscription_type: subscription_obj, + schema: SchemaType::new_with_directives::( + &query_info, + &mutation_info, + &subscription_info, + custom_directives.into(), + ), + query_info, + mutation_info, + subscription_info, + introspection_disabled: false, + } + } + /// Disables introspection for this [`RootNode`], making it to return a [`FieldError`] whenever /// its `__schema` or `__type` field is resolved. /// @@ -257,12 +357,29 @@ where } impl<'a, S> SchemaType<'a, S> { + /// Create a new schema. pub fn new( query_info: &QueryT::TypeInfo, mutation_info: &MutationT::TypeInfo, subscription_info: &SubscriptionT::TypeInfo, ) -> Self + where + S: ScalarValue + 'a, + QueryT: GraphQLType, + MutationT: GraphQLType, + SubscriptionT: GraphQLType, + { + Self::new_with_directives::(query_info, mutation_info, subscription_info, None) + } + + /// Create a new schema with custom directives + pub fn new_with_directives( + query_info: &QueryT::TypeInfo, + mutation_info: &MutationT::TypeInfo, + subscription_info: &SubscriptionT::TypeInfo, + custom_directives: Option>>, + ) -> Self where S: ScalarValue + 'a, QueryT: GraphQLType, @@ -298,6 +415,12 @@ impl<'a, S> SchemaType<'a, S> { DirectiveType::new_specified_by(&mut registry), ); + if let Some(custom_directives) = custom_directives { + for custom_directive in custom_directives.into_iter() { + directives.insert(custom_directive.name.clone(), custom_directive); + } + } + let mut meta_fields = vec![ registry.field::>("__schema", &()), registry @@ -585,6 +708,7 @@ impl<'a, S> DirectiveType<'a, S> where S: ScalarValue + 'a, { + /// Create a new default directive pub fn new( name: &str, locations: &[DirectiveLocation], @@ -600,6 +724,15 @@ where } } + /// skip,include,deprecated,specifiedBy are standard graphQL directive + /// wiil not show up in generated scheme + pub fn is_builtin(&self) -> bool { + match self.name.as_str() { + "skip" | "include" | "deprecated" | "specifiedBy" => true, + _ => false, + } + } + fn new_skip(registry: &mut Registry<'a, S>) -> DirectiveType<'a, S> where S: ScalarValue, @@ -659,6 +792,7 @@ where ) } + /// Set description of directive pub fn description(mut self, description: &str) -> DirectiveType<'a, S> { self.description = Some(description.into()); self diff --git a/juniper/src/schema/translate/graphql_parser.rs b/juniper/src/schema/translate/graphql_parser.rs index 0bf8a589a..6fafb8db6 100644 --- a/juniper/src/schema/translate/graphql_parser.rs +++ b/juniper/src/schema/translate/graphql_parser.rs @@ -4,16 +4,19 @@ use graphql_parser::{ Pos, query::{Directive as ExternalDirective, Number as ExternalNumber, Type as ExternalType}, schema::{ - Definition, Document, EnumType as ExternalEnum, EnumValue as ExternalEnumValue, - Field as ExternalField, InputObjectType as ExternalInputObjectType, - InputValue as ExternalInputValue, InterfaceType as ExternalInterfaceType, - ObjectType as ExternalObjectType, ScalarType as ExternalScalarType, SchemaDefinition, Text, + Definition, DirectiveDefinition as ExternalDirectiveDefinition, + DirectiveLocation as ExternalDirectiveLocation, Document, EnumType as ExternalEnum, + EnumValue as ExternalEnumValue, Field as ExternalField, + InputObjectType as ExternalInputObjectType, InputValue as ExternalInputValue, + InterfaceType as ExternalInterfaceType, ObjectType as ExternalObjectType, + ScalarType as ExternalScalarType, SchemaDefinition, Text, TypeDefinition as ExternalTypeDefinition, UnionType as ExternalUnionType, Value as ExternalValue, }, }; use crate::{ + DirectiveLocation, ast::{InputValue, Type}, schema::{ meta::{Argument, DeprecationStatus, EnumValue, Field, MetaType}, @@ -76,11 +79,56 @@ where .map(|s| From::from(s.as_str())), })); + let mut directives = input + .directives + .iter() + .filter(|(_, directive)| !directive.is_builtin()) + .map(|(_, directive)| ExternalDirectiveDefinition:: { + position: Pos::default(), + description: directive.description.clone(), + name: From::from(directive.name.as_str()), + arguments: directive + .arguments + .iter() + .map(GraphQLParserTranslator::translate_argument) + .collect(), + repeatable: directive.is_repeatable, + locations: directive + .locations + .iter() + .map(GraphQLParserTranslator::translate_location::) + .collect(), + }) + .map(Definition::DirectiveDefinition) + .collect(); + + doc.definitions.append(&mut directives); + doc } } impl GraphQLParserTranslator { + fn translate_location<'a, S, T>(location: &DirectiveLocation) -> ExternalDirectiveLocation + where + S: ScalarValue, + T: Text<'a>, + { + match location { + DirectiveLocation::Query => ExternalDirectiveLocation::Query, + DirectiveLocation::Mutation => ExternalDirectiveLocation::Mutation, + DirectiveLocation::Subscription => ExternalDirectiveLocation::Subscription, + DirectiveLocation::Field => ExternalDirectiveLocation::Field, + DirectiveLocation::Scalar => ExternalDirectiveLocation::Scalar, + DirectiveLocation::FragmentDefinition => ExternalDirectiveLocation::FragmentDefinition, + DirectiveLocation::FieldDefinition => ExternalDirectiveLocation::FieldDefinition, + DirectiveLocation::VariableDefinition => ExternalDirectiveLocation::VariableDefinition, + DirectiveLocation::FragmentSpread => ExternalDirectiveLocation::FragmentSpread, + DirectiveLocation::InlineFragment => ExternalDirectiveLocation::InlineFragment, + DirectiveLocation::EnumValue => ExternalDirectiveLocation::EnumValue, + } + } + fn translate_argument<'a, S, T>(input: &'a Argument) -> ExternalInputValue<'a, T> where S: ScalarValue,