Skip to content

Commit

Permalink
feat: add support for external directives (#2894)
Browse files Browse the repository at this point in the history
Co-authored-by: Tushar Mathur <[email protected]>
  • Loading branch information
meskill and tusharmath authored Oct 5, 2024
1 parent 44777af commit 1511db7
Show file tree
Hide file tree
Showing 37 changed files with 723 additions and 351 deletions.
2 changes: 1 addition & 1 deletion examples/apollo_federation_subgraph_post.graphql
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
schema
@server(port: 8001)
@server(port: 8001, enableFederation: true)
@upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: 42, batch: {delay: 100}) {
query: Query
}
Expand Down
2 changes: 1 addition & 1 deletion examples/apollo_federation_subgraph_user.graphql
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
schema
@server(port: 8002)
@server(port: 8002, enableFederation: true)
@upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: 42, batch: {delay: 100}) {
query: Query
}
Expand Down
29 changes: 29 additions & 0 deletions generated/.tailcallrc.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,21 @@
"title": "DateTime",
"description": "Field whose value conforms to the standard datetime format as specified in RFC 3339 (https://datatracker.ietf.org/doc/html/rfc3339\")."
},
"Directive": {
"type": "object",
"required": [
"name"
],
"properties": {
"arguments": {
"type": "object",
"additionalProperties": true
},
"name": {
"type": "string"
}
}
},
"Email": {
"title": "Email",
"description": "Field whose value conforms to the standard internet email address format as specified in HTML Spec: https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address."
Expand Down Expand Up @@ -469,6 +484,13 @@
"default_value": {
"description": "Stores the default value for the field"
},
"directives": {
"description": "Any additional directives",
"type": "array",
"items": {
"$ref": "#/definitions/Directive"
}
},
"doc": {
"description": "Publicly visible documentation for the field.",
"type": [
Expand Down Expand Up @@ -1397,6 +1419,13 @@
}
]
},
"directives": {
"description": "Any additional directives",
"type": "array",
"items": {
"$ref": "#/definitions/Directive"
}
},
"doc": {
"description": "Documentation for the type that is publicly visible.",
"type": [
Expand Down
29 changes: 19 additions & 10 deletions src/core/blueprint/blueprint.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use std::collections::{BTreeSet, HashMap};
use std::collections::BTreeSet;
use std::sync::Arc;

use async_graphql::dynamic::{Schema, SchemaBuilder};
use async_graphql::extensions::ApolloTracing;
use async_graphql::ValidationMode;
use derive_setters::Setters;
use serde_json::Value;

use super::directive::Directive;
use super::telemetry::Telemetry;
use super::{GlobalTimeout, Index};
use crate::core::blueprint::{Server, Upstream};
Expand Down Expand Up @@ -48,13 +48,27 @@ impl Definition {
Definition::Union(def) => &def.name,
}
}

/// gets directives associated with definition
pub fn directives(&self) -> &[Directive] {
match self {
Definition::Interface(def) => &def.directives,
Definition::Object(def) => &def.directives,
Definition::InputObject(def) => &def.directives,
Definition::Scalar(def) => &def.directives,
Definition::Enum(def) => &def.directives,
Definition::Union(def) => &def.directives,
}
}
}

#[derive(Clone, Debug)]
pub struct InterfaceTypeDefinition {
pub name: String,
pub fields: Vec<FieldDefinition>,
pub description: Option<String>,
pub implements: BTreeSet<String>,
pub directives: Vec<Directive>,
}

#[derive(Clone, Debug)]
Expand All @@ -63,13 +77,15 @@ pub struct ObjectTypeDefinition {
pub fields: Vec<FieldDefinition>,
pub description: Option<String>,
pub implements: BTreeSet<String>,
pub directives: Vec<Directive>,
}

#[derive(Clone, Debug)]
pub struct InputObjectTypeDefinition {
pub name: String,
pub fields: Vec<InputFieldDefinition>,
pub description: Option<String>,
pub directives: Vec<Directive>,
}

#[derive(Clone, Debug)]
Expand Down Expand Up @@ -123,17 +139,10 @@ impl FieldDefinition {
}
}

#[derive(Clone, Debug)]
pub struct Directive {
pub name: String,
pub arguments: HashMap<String, Value>,
pub index: usize,
}

#[derive(Clone, Debug)]
pub struct ScalarTypeDefinition {
pub name: String,
pub directive: Vec<Directive>,
pub directives: Vec<Directive>,
pub description: Option<String>,
pub scalar: scalar::Scalar,
}
Expand Down
14 changes: 11 additions & 3 deletions src/core/blueprint/definitions.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::collections::HashSet;

use async_graphql_value::ConstValue;
use directive::Directive;
use regex::Regex;
use union_resolver::update_union_resolver;

Expand All @@ -18,7 +19,7 @@ pub fn to_scalar_type_definition(name: &str) -> Valid<Definition, String> {
} else {
Valid::succeed(Definition::Scalar(ScalarTypeDefinition {
name: name.to_string(),
directive: Vec::new(),
directives: Vec::new(),
description: None,
scalar: scalar::Scalar::find(name)
.unwrap_or(&scalar::Scalar::Empty)
Expand Down Expand Up @@ -52,6 +53,7 @@ pub fn to_input_object_type_definition(
})
.collect(),
description: definition.description,
directives: Vec::new(),
}))
}

Expand All @@ -60,6 +62,8 @@ pub fn to_interface_type_definition(definition: ObjectTypeDefinition) -> Valid<D
name: definition.name,
fields: definition.fields,
description: definition.description,
implements: definition.implements,
directives: Vec::new(),
}))
}

Expand Down Expand Up @@ -255,6 +259,7 @@ fn to_object_type_definition(
description: type_of.doc.clone(),
fields,
implements: type_of.implements.clone(),
directives: to_directives(&type_of.directives),
})
})
}
Expand All @@ -278,7 +283,7 @@ fn update_args<'a>(
description: field.doc.clone(),
args,
of_type: field.type_of.clone(),
directives: Vec::new(),
directives: to_directives(&field.directives),
resolver: None,
default_value: field.default_value.clone(),
})
Expand Down Expand Up @@ -492,7 +497,6 @@ pub fn to_field_definition(
name: &String,
) -> Valid<FieldDefinition, String> {
update_args()
.and(update_apollo_federation(operation_type).trace("_entities"))
.and(update_http().trace(config::Http::trace_name().as_str()))
.and(update_grpc(operation_type).trace(config::Grpc::trace_name().as_str()))
.and(update_const_field().trace(config::Expr::trace_name().as_str()))
Expand Down Expand Up @@ -554,3 +558,7 @@ pub fn to_definitions<'a>() -> TryFold<'a, ConfigModule, Vec<Definition>, String
})
})
}

fn to_directives(directives: &[config::Directive]) -> Vec<Directive> {
directives.iter().cloned().map(Directive::from).collect()
}
49 changes: 49 additions & 0 deletions src/core/blueprint/directive.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use std::collections::HashMap;

use async_graphql::parser::types::ConstDirective;
use async_graphql::Name;
use serde_json::Value;

use crate::core::valid::{Valid, ValidationError, Validator};
use crate::core::{config, pos};

#[derive(Clone, Debug)]
pub struct Directive {
pub name: String,
pub arguments: HashMap<String, Value>,
}

pub fn to_directive(const_directive: ConstDirective) -> Valid<Directive, String> {
const_directive
.arguments
.into_iter()
.map(|(k, v)| {
let value = v.node.into_json();

value.map(|value| (k.node.to_string(), value))
})
.collect::<Result<_, _>>()
.map_err(|e| ValidationError::new(e.to_string()))
.map(|arguments| Directive { name: const_directive.name.node.to_string(), arguments })
.into()
}

pub fn to_const_directive(directive: &Directive) -> Valid<ConstDirective, String> {
Valid::from_iter(directive.arguments.iter(), |(k, v)| {
let name = pos(Name::new(k));
Valid::from(serde_json::from_value(v.clone()).map(pos).map_err(|e| {
ValidationError::new(e.to_string()).trace(format!("@{}", directive.name).as_str())
}))
.map(|value| (name, value))
})
.map(|arguments| ConstDirective { name: pos(Name::new(&directive.name)), arguments })
}

impl From<config::Directive> for Directive {
fn from(value: config::Directive) -> Self {
Self {
name: value.name,
arguments: value.arguments.into_iter().collect(),
}
}
}
3 changes: 3 additions & 0 deletions src/core/blueprint/from_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ pub fn config_blueprint<'a>() -> TryFold<'a, ConfigModule, Blueprint, String> {
.and(upstream)
.and(links)
.and(opentelemetry)
// set the federation config only after setting other properties to be able
// to use blueprint inside the handler and to avoid recursion overflow
.and(update_federation().trace("federation"))
.update(apply_batching)
.update(compress)
}
Expand Down
Loading

1 comment on commit 1511db7

@github-actions
Copy link

Choose a reason for hiding this comment

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

Running 30s test @ http://localhost:8000/graphql

4 threads and 100 connections

Thread Stats Avg Stdev Max +/- Stdev
Latency 7.14ms 3.08ms 38.83ms 71.83%
Req/Sec 3.55k 383.75 4.02k 94.42%

424132 requests in 30.04s, 792.84MB read

Requests/sec: 14120.11

Transfer/sec: 26.39MB

Please sign in to comment.