use graphql_tools::parser::query::Text;
use graphql_tools::parser::schema::*;
use crate::query_planner::{
federation_spec::{
definitions::{
CorePurposesEnum, JoinDirectiveArgumentsScalar, JoinFieldSetScalar, JoinGraphEnum,
LinkImportScalar, LinkPurposeEnum, RequiresScopesScopeScalar,
},
demand_control::{CostDirective, ListSizeDirective},
directives::{
AuthenticatedDirective, CoreDirective, InaccessibleDirective, JoinEnumValueDirective,
JoinFieldDirective, JoinGraphDirective, JoinImplementsDirective, JoinTypeDirective,
JoinUnionMemberDirective, LinkDirective, RequiresScopesDirective, TagDirective,
},
join_directive::JoinDirectiveDirective,
join_owner::JoinOwnerDirective,
},
utils::schema_transformer::{SchemaTransformer, TransformedValue},
};
pub(crate) struct StripSchemaInternals;
static DIRECTIVES_TO_STRIP: [&str; 16] = [
JoinTypeDirective::NAME,
JoinEnumValueDirective::NAME,
JoinFieldDirective::NAME,
JoinImplementsDirective::NAME,
JoinUnionMemberDirective::NAME,
JoinGraphDirective::NAME,
JoinOwnerDirective::NAME,
JoinDirectiveDirective::NAME,
LinkDirective::NAME,
TagDirective::NAME,
InaccessibleDirective::NAME,
CoreDirective::NAME,
AuthenticatedDirective::NAME,
RequiresScopesDirective::NAME,
CostDirective::NAME,
ListSizeDirective::NAME,
];
static DEFINITIONS_TO_STRIP: [&str; 7] = [
LinkPurposeEnum::NAME,
LinkImportScalar::NAME,
JoinGraphEnum::NAME,
JoinFieldSetScalar::NAME,
JoinDirectiveArgumentsScalar::NAME,
CorePurposesEnum::NAME,
RequiresScopesScopeScalar::NAME,
];
impl StripSchemaInternals {
pub fn strip_schema_internals(schema: &Document<'static, String>) -> Document<'static, String> {
let mut transformer = StripSchemaInternals {};
let result = transformer
.transform_document(schema)
.replace_or_else(|| schema.clone());
result
}
pub(crate) fn filter_directives<'a, T: Text<'a> + Clone>(
directives: &Vec<Directive<'a, T>>,
) -> Vec<Directive<'a, T>> {
directives
.iter()
.filter(|d| !DIRECTIVES_TO_STRIP.contains(&d.name.as_ref()))
.cloned()
.collect()
}
}
impl<'a, T: Text<'a> + Clone> SchemaTransformer<'a, T> for StripSchemaInternals {
fn transform_directives(
&mut self,
directives: &Vec<Directive<'a, T>>,
) -> TransformedValue<Vec<Directive<'a, T>>> {
let new_directives = StripSchemaInternals::filter_directives(directives);
TransformedValue::Replace(new_directives)
}
fn transform_document(
&mut self,
document: &Document<'a, T>,
) -> TransformedValue<Document<'a, T>> {
let new_doc = Document::new(
document
.definitions
.iter()
.filter(|def| match def {
Definition::DirectiveDefinition(directive) => {
!DIRECTIVES_TO_STRIP.contains(&directive.name.as_ref())
}
Definition::TypeDefinition(TypeDefinition::Object(obj)) => {
!DEFINITIONS_TO_STRIP.contains(&obj.name.as_ref())
}
Definition::TypeDefinition(TypeDefinition::Interface(interface)) => {
!DEFINITIONS_TO_STRIP.contains(&interface.name.as_ref())
}
Definition::TypeDefinition(TypeDefinition::Union(union)) => {
!DEFINITIONS_TO_STRIP.contains(&union.name.as_ref())
}
Definition::TypeDefinition(TypeDefinition::Enum(enm)) => {
!DEFINITIONS_TO_STRIP.contains(&enm.name.as_ref())
}
Definition::TypeDefinition(TypeDefinition::Scalar(scalar)) => {
!DEFINITIONS_TO_STRIP.contains(&scalar.name.as_ref())
}
Definition::TypeDefinition(TypeDefinition::InputObject(input)) => {
!DEFINITIONS_TO_STRIP.contains(&input.name.as_ref())
}
Definition::TypeExtension(_) => todo!("TypeExtension not implemented"),
Definition::SchemaDefinition(_) => true,
})
.cloned()
.collect(),
);
self.default_transform_document(&new_doc)
}
}
#[cfg(test)]
mod test {
use insta::assert_debug_snapshot;
use crate::query_planner::utils::parsing::parse_schema;
#[test]
fn test_strip_schema_internals() {
use crate::query_planner::consumer_schema::strip_schema_internals::StripSchemaInternals;
let schema = r#"
schema
@link(url: "https://specs.apollo.dev/link/v1.0")
@link(url: "https://specs.apollo.dev/join/v0.3", for: EXECUTION)
@link(url: "https://specs.apollo.dev/tag/v0.3")
@link(url: "https://specs.apollo.dev/inaccessible/v0.2", for: SECURITY)
@link(
url: "https://myspecs.dev/myDirective/v1.0"
import: ["@myDirective", { name: "@anotherDirective", as: "@hello" }]
) {
query: Query
}
directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE
directive @join__field(
graph: join__Graph
requires: join__FieldSet
provides: join__FieldSet
type: String
external: Boolean
override: String
usedOverridden: Boolean
) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION
directive @join__graph(name: String!, url: String!) on ENUM_VALUE
directive @join__implements(
graph: join__Graph!
interface: String!
) repeatable on OBJECT | INTERFACE
directive @join__type(
graph: join__Graph!
key: join__FieldSet
extension: Boolean! = false
resolvable: Boolean! = true
isInterfaceObject: Boolean! = false
) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR
directive @join__unionMember(
graph: join__Graph!
member: String!
) repeatable on UNION
directive @join__directive(graphs: [join__Graph!], name: String!, args: join__DirectiveArguments) repeatable on SCHEMA | OBJECT | INTERFACE | FIELD_DEFINITION
scalar join__DirectiveArguments
scalar join__FieldSet
directive @link(
url: String
as: String
for: link__Purpose
import: [link__Import]
) repeatable on SCHEMA
scalar link__Import
enum link__Purpose {
"""
`SECURITY` features provide metadata necessary to securely resolve fields.
"""
SECURITY
"""
`EXECUTION` features provide metadata necessary for operation execution.
"""
EXECUTION
}
directive @tag(
name: String!
) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA
directive @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ENUM | ENUM_VALUE | SCALAR | INPUT_OBJECT | INPUT_FIELD_DEFINITION | ARGUMENT_DEFINITION
directive @authenticated on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM
scalar requiresScopes__Scope
directive @requiresScopes(
scopes: [[requiresScopes__Scope!]!]!
) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM
enum join__Graph {
INVENTORY @join__graph(name: "inventory", url: "")
PANDAS @join__graph(name: "pandas", url: "")
PRODUCTS @join__graph(name: "products", url: "")
REVIEWS @join__graph(name: "reviews", url: "")
USERS @join__graph(name: "users", url: "")
}
directive @myDirective(a: String!) on FIELD_DEFINITION
directive @hello on FIELD_DEFINITION
type Product implements ProductItf & SkuItf
@join__type(graph: INVENTORY, key: "id")
@join__type(graph: PRODUCTS, key: "id")
@join__type(graph: PRODUCTS, key: "sku package")
@join__type(graph: PRODUCTS, key: "sku variation { id }")
@join__type(graph: REVIEWS, key: "id")
@join__implements(graph: INVENTORY, interface: "ProductItf")
@join__implements(graph: PRODUCTS, interface: "ProductItf")
@join__implements(graph: PRODUCTS, interface: "SkuItf")
@join__implements(graph: REVIEWS, interface: "ProductItf") {
id: ID! @tag(name: "hi-from-products")
dimensions: ProductDimension
@join__field(graph: INVENTORY, external: true)
@join__field(graph: PRODUCTS)
delivery(zip: String): DeliveryEstimates
@join__field(
graph: INVENTORY
requires: "dimensions{...on ProductDimension{size weight}}"
)
sku: String @join__field(graph: PRODUCTS)
package: String @join__field(graph: PRODUCTS)
variation: ProductVariation @join__field(graph: PRODUCTS)
name: String @hello @join__field(graph: PRODUCTS)
createdBy: User @join__field(graph: PRODUCTS)
hidden: String @join__field(graph: PRODUCTS) @requiresScopes(scopes: ["read", "admin"])
reviewsScore: Float! @join__field(graph: REVIEWS, override: "products")
oldField: String @join__field(graph: PRODUCTS)
reviewsCount: Int! @join__field(graph: REVIEWS)
reviews: [Review!]! @join__field(graph: REVIEWS)
}
type ProductDimension
@join__type(graph: INVENTORY)
@join__type(graph: PRODUCTS) {
size: String
weight: Float
}
type DeliveryEstimates @join__type(graph: INVENTORY) {
estimatedDelivery: String
fastestDelivery: String
}
type Query
@join__type(graph: INVENTORY)
@join__type(graph: PANDAS)
@join__type(graph: PRODUCTS)
@join__type(graph: REVIEWS)
@join__type(graph: USERS) {
allPandas: [Panda] @join__field(graph: PANDAS) @authenticated
panda(name: ID!): Panda @join__field(graph: PANDAS)
allProducts: [ProductItf] @join__field(graph: PRODUCTS)
product(id: ID!): ProductItf @join__field(graph: PRODUCTS)
review(id: Int!): Review @join__field(graph: REVIEWS)
}
type Mutation
@join__type(graph: REVIEWS)
{
newRandomReview: Review
}
type Panda @join__type(graph: PANDAS) {
name: ID!
favoriteFood: String @tag(name: "nom-nom-nom")
}
type ProductVariation @join__type(graph: PRODUCTS) {
id: ID!
name: String
}
type User
@join__type(graph: PRODUCTS, key: "email")
@join__type(graph: USERS, key: "email") {
email: ID! @tag(name: "test-from-users")
totalProductsCreated: Int
name: String @join__field(graph: USERS)
}
type Review @join__type(graph: REVIEWS) {
id: Int!
body: String!
}
interface ProductItf implements SkuItf
@join__type(graph: INVENTORY)
@join__type(graph: PRODUCTS)
@join__type(graph: REVIEWS)
@join__implements(graph: PRODUCTS, interface: "SkuItf") {
id: ID!
dimensions: ProductDimension
@join__field(graph: INVENTORY)
@join__field(graph: PRODUCTS)
delivery(zip: String): DeliveryEstimates @join__field(graph: INVENTORY)
sku: String @join__field(graph: PRODUCTS)
name: String @join__field(graph: PRODUCTS)
package: String @join__field(graph: PRODUCTS)
variation: ProductVariation @join__field(graph: PRODUCTS)
createdBy: User @join__field(graph: PRODUCTS)
hidden: String @join__field(graph: PRODUCTS) @inaccessible
oldField: String
@join__field(graph: PRODUCTS)
@deprecated(reason: "refactored out")
reviewsCount: Int! @join__field(graph: REVIEWS)
reviewsScore: Float! @join__field(graph: REVIEWS)
reviews: [Review!]! @join__field(graph: REVIEWS)
}
interface SkuItf @join__type(graph: PRODUCTS) {
sku: String
}
enum ShippingClass @join__type(graph: INVENTORY) @join__type(graph: PRODUCTS) {
STANDARD @join__enumValue(graph: INVENTORY) @join__enumValue(graph: PRODUCTS)
EXPRESS @join__enumValue(graph: INVENTORY) @join__enumValue(graph: PRODUCTS)
OVERNIGHT @join__enumValue(graph: INVENTORY)
}
"#;
let schema = graphql_tools::parser::parse_schema(schema).unwrap();
let schema = StripSchemaInternals::strip_schema_internals(&schema);
let schema_str = format!("{}", schema);
assert_debug_snapshot!(schema_str);
parse_schema(&schema_str);
}
}