diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index e79c5290a..1dfa2fcbe 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -88,7 +88,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 95e973356..bd35025c7 100644 --- a/juniper/src/schema/model.rs +++ b/juniper/src/schema/model.rs @@ -3,6 +3,7 @@ use std::ptr; use arcstr::ArcStr; use derive_more::with_trait::Display; use fnv::FnvHashMap; + #[cfg(feature = "schema-language")] use graphql_parser::schema::Document; @@ -54,7 +55,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, (), (), ()) } @@ -68,7 +69,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, @@ -76,6 +77,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 RootNode @@ -112,6 +166,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. /// @@ -214,7 +296,7 @@ pub struct SchemaType { 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 {} @@ -226,6 +308,22 @@ impl SchemaType { mutation_info: &MutationT::TypeInfo, subscription_info: &SubscriptionT::TypeInfo, ) -> Self + where + S: ScalarValue, + 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, QueryT: GraphQLType, @@ -259,6 +357,12 @@ impl SchemaType { directives.insert(deprecated_directive.name.clone(), deprecated_directive); directives.insert(specified_by_directive.name.clone(), specified_by_directive); + 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::>(arcstr::literal!("__schema"), &()), registry @@ -558,15 +662,29 @@ impl<'a, S> TypeType<'a, S> { } #[derive(Debug)] +/// Represents a GraphQL directive type, including its name, description, locations, arguments, and repeatability. pub struct DirectiveType { + /// The name of the directive. pub name: ArcStr, + /// The description of the directive. pub description: Option, + /// The locations where the directive can be used. pub locations: Vec, + /// The arguments accepted by the directive. pub arguments: Vec>, + /// Whether the directive is repeatable. pub is_repeatable: bool, } impl DirectiveType { + /// Creates a new custom directive type with the given name, locations, arguments, and repeatability. + /// + /// # Arguments + /// + /// * `name` - The name of the directive. + /// * `locations` - The locations where the directive can be used. + /// * `arguments` - The arguments accepted by the directive. + /// * `is_repeatable` - Whether the directive is repeatable. pub fn new( name: impl Into, locations: &[DirectiveLocation], @@ -601,6 +719,15 @@ impl DirectiveType { ) } + /// 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) -> Self where S: ScalarValue, @@ -650,51 +777,76 @@ impl DirectiveType { ) } + /// Sets the description for the directive type and returns the updated instance. + /// + /// # Arguments + /// + /// * `description` - The description to set for the directive. pub fn description(mut self, description: impl Into) -> Self { self.description = Some(description.into()); self } } +/// Represents the valid locations where a GraphQL directive can be used. #[derive(Clone, Debug, Display, Eq, GraphQLEnum, PartialEq)] #[graphql(name = "__DirectiveLocation", internal)] pub enum DirectiveLocation { + /// Location adjacent to a query operation. #[display("query")] Query, + /// Location adjacent to a mutation operation. #[display("mutation")] Mutation, + /// Location adjacent to a subscription operation. #[display("subscription")] Subscription, + /// Location adjacent to a field. #[display("field")] Field, + /// Location adjacent to a fragment definition. #[display("fragment definition")] FragmentDefinition, + /// Location adjacent to a fragment spread. #[display("fragment spread")] FragmentSpread, + /// Location adjacent to an inline fragment. #[display("inline fragment")] InlineFragment, + /// Location adjacent to a variable definition. #[display("variable definition")] VariableDefinition, + /// Location adjacent to a schema definition. #[display("schema")] Schema, + /// Location adjacent to a scalar definition. #[display("scalar")] Scalar, + /// Location adjacent to an object type definition. #[display("object")] Object, + /// Location adjacent to a field definition. #[display("field definition")] FieldDefinition, + /// Location adjacent to an argument definition. #[display("argument definition")] ArgumentDefinition, + /// Location adjacent to an interface definition. #[display("interface")] Interface, + /// Location adjacent to a union definition. #[display("union")] Union, + /// Location adjacent to an enum definition. #[display("enum")] Enum, + /// Location adjacent to an enum value definition. #[display("enum value")] EnumValue, + /// Location adjacent to an input object definition. #[display("input object")] InputObject, + /// Location adjacent to an input field definition. #[display("input field definition")] InputFieldDefinition, } diff --git a/juniper/src/schema/translate/graphql_parser.rs b/juniper/src/schema/translate/graphql_parser.rs index c687d029a..3033099a8 100644 --- a/juniper/src/schema/translate/graphql_parser.rs +++ b/juniper/src/schema/translate/graphql_parser.rs @@ -5,6 +5,17 @@ use graphql_parser::{Pos, schema}; use crate::{ ast, schema::{meta, model::SchemaType, translate::SchemaTranslator}, +}; + +use graphql_parser::{ + schema::{ + Definition, DirectiveDefinition as ExternalDirectiveDefinition, + DirectiveLocation as ExternalDirectiveLocation, + }, +}; + +use crate::{ + DirectiveLocation, value::ScalarValue, }; @@ -58,11 +69,65 @@ where }, )); + let mut directives = input + .directives + .iter() + .filter(|(_, directive)| !directive.is_builtin()) + .map(|(_, directive)| ExternalDirectiveDefinition:: { + position: Pos::default(), + description: directive.description.as_deref().map(Into::into), + 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: schema::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, + DirectiveLocation::Schema => ExternalDirectiveLocation::Schema, + DirectiveLocation::Object => ExternalDirectiveLocation::Object, + DirectiveLocation::ArgumentDefinition => ExternalDirectiveLocation::ArgumentDefinition, + DirectiveLocation::Interface => ExternalDirectiveLocation::Interface, + DirectiveLocation::Union => ExternalDirectiveLocation::Union, + DirectiveLocation::Enum => ExternalDirectiveLocation::Enum, + DirectiveLocation::InputObject => ExternalDirectiveLocation::InputObject, + DirectiveLocation::InputFieldDefinition => ExternalDirectiveLocation::InputFieldDefinition, + } + } + fn translate_argument<'a, S, T>(input: &'a meta::Argument) -> schema::InputValue<'a, T> where S: ScalarValue, diff --git a/juniper/src/tests/schema_introspection.rs b/juniper/src/tests/schema_introspection.rs index e8dc19edc..323171fdc 100644 --- a/juniper/src/tests/schema_introspection.rs +++ b/juniper/src/tests/schema_introspection.rs @@ -42,7 +42,7 @@ pub(crate) fn schema_introspection_result() -> Value { { "kind": "ENUM", "name": "__DirectiveLocation", - "description": null, + "description": "Represents the valid locations where a GraphQL directive can be used.", "specifiedByURL": null, "isOneOf": null, "fields": null, @@ -51,115 +51,115 @@ pub(crate) fn schema_introspection_result() -> Value { "enumValues": [ { "name": "QUERY", - "description": null, + "description": "Location adjacent to a query operation.", "isDeprecated": false, "deprecationReason": null }, { "name": "MUTATION", - "description": null, + "description": "Location adjacent to a mutation operation.", "isDeprecated": false, "deprecationReason": null }, { "name": "SUBSCRIPTION", - "description": null, + "description": "Location adjacent to a subscription operation.", "isDeprecated": false, "deprecationReason": null }, { "name": "FIELD", - "description": null, + "description": "Location adjacent to a field.", "isDeprecated": false, "deprecationReason": null }, { "name": "FRAGMENT_DEFINITION", - "description": null, + "description": "Location adjacent to a fragment definition.", "isDeprecated": false, "deprecationReason": null }, { "name": "FRAGMENT_SPREAD", - "description": null, + "description": "Location adjacent to a fragment spread.", "isDeprecated": false, "deprecationReason": null }, { "name": "INLINE_FRAGMENT", - "description": null, + "description": "Location adjacent to an inline fragment.", "isDeprecated": false, "deprecationReason": null }, { "name": "VARIABLE_DEFINITION", - "description": null, + "description": "Location adjacent to a variable definition.", "isDeprecated": false, "deprecationReason": null }, { "name": "SCHEMA", - "description": null, + "description": "Location adjacent to a schema definition.", "isDeprecated": false, "deprecationReason": null }, { "name": "SCALAR", - "description": null, + "description": "Location adjacent to a scalar definition.", "isDeprecated": false, "deprecationReason": null }, { "name": "OBJECT", - "description": null, + "description": "Location adjacent to an object type definition.", "isDeprecated": false, "deprecationReason": null }, { "name": "FIELD_DEFINITION", - "description": null, + "description": "Location adjacent to a field definition.", "isDeprecated": false, "deprecationReason": null }, { "name": "ARGUMENT_DEFINITION", - "description": null, + "description": "Location adjacent to an argument definition.", "isDeprecated": false, "deprecationReason": null }, { "name": "INTERFACE", - "description": null, + "description": "Location adjacent to an interface definition.", "isDeprecated": false, "deprecationReason": null }, { "name": "UNION", - "description": null, + "description": "Location adjacent to a union definition.", "isDeprecated": false, "deprecationReason": null }, { "name": "ENUM", - "description": null, + "description": "Location adjacent to an enum definition.", "isDeprecated": false, "deprecationReason": null }, { "name": "ENUM_VALUE", - "description": null, + "description": "Location adjacent to an enum value definition.", "isDeprecated": false, "deprecationReason": null }, { "name": "INPUT_OBJECT", - "description": null, + "description": "Location adjacent to an input object definition.", "isDeprecated": false, "deprecationReason": null }, { "name": "INPUT_FIELD_DEFINITION", - "description": null, + "description": "Location adjacent to an input field definition.", "isDeprecated": false, "deprecationReason": null }