Skip to content

Create a schema with custom directives first change for #156 #1314

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion juniper/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down
140 changes: 137 additions & 3 deletions juniper/src/schema/model.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::{borrow::Cow, fmt};

use fnv::FnvHashMap;

#[cfg(feature = "schema-language")]
use graphql_parser::schema::Document;

Expand Down Expand Up @@ -53,7 +54,7 @@ pub struct SchemaType<'a, S> {
pub(crate) query_type_name: String,
pub(crate) mutation_type_name: Option<String>,
pub(crate) subscription_type_name: Option<String>,
directives: FnvHashMap<String, DirectiveType<'a, S>>,
pub(crate) directives: FnvHashMap<String, DirectiveType<'a, S>>,
}

impl<S> Context for SchemaType<'_, S> {}
Expand All @@ -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<String>,
/// required atleast a location
pub locations: Vec<DirectiveLocation>,
/// Optional arguments
pub arguments: Vec<Argument<'a, S>>,
/// 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,
}

Expand All @@ -104,7 +123,7 @@ where
SubscriptionT: GraphQLType<DefaultScalarValue, TypeInfo = ()>,
{
/// 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, (), (), ())
}
Expand All @@ -118,14 +137,67 @@ where
SubscriptionT: GraphQLType<S, TypeInfo = ()>,
{
/// 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,
subscription: SubscriptionT,
) -> 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<DirectiveType<'a, S>>,
) -> Self {
Self::new_with_directives_and_info(
query,
mutation,
subscription,
custom_directives,
(),
(),
(),
)
}
}

impl<'a, S, QueryT, MutationT, SubscriptionT> RootNode<'a, QueryT, MutationT, SubscriptionT, S>
Expand Down Expand Up @@ -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<DirectiveType<'a, S>>,
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::<QueryT, MutationT, SubscriptionT>(
&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.
///
Expand Down Expand Up @@ -257,12 +357,29 @@ where
}

impl<'a, S> SchemaType<'a, S> {

/// Create a new schema.
pub fn new<QueryT, MutationT, SubscriptionT>(
query_info: &QueryT::TypeInfo,
mutation_info: &MutationT::TypeInfo,
subscription_info: &SubscriptionT::TypeInfo,
) -> Self
where
S: ScalarValue + 'a,
QueryT: GraphQLType<S>,
MutationT: GraphQLType<S>,
SubscriptionT: GraphQLType<S>,
{
Self::new_with_directives::<QueryT,MutationT,SubscriptionT>(query_info, mutation_info, subscription_info, None)
}

/// Create a new schema with custom directives
pub fn new_with_directives<QueryT, MutationT, SubscriptionT>(
query_info: &QueryT::TypeInfo,
mutation_info: &MutationT::TypeInfo,
subscription_info: &SubscriptionT::TypeInfo,
custom_directives: Option<Vec<DirectiveType<'a, S>>>,
) -> Self
where
S: ScalarValue + 'a,
QueryT: GraphQLType<S>,
Expand Down Expand Up @@ -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::<SchemaType<S>>("__schema", &()),
registry
Expand Down Expand Up @@ -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],
Expand All @@ -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,
Expand Down Expand Up @@ -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
Expand Down
56 changes: 52 additions & 4 deletions juniper/src/schema/translate/graphql_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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::<T> {
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::<S,T>)
.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<S>) -> ExternalInputValue<'a, T>
where
S: ScalarValue,
Expand Down