Skip to content

Commit

Permalink
fix: directives name clashes with apollo federation for link
Browse files Browse the repository at this point in the history
  • Loading branch information
meskill committed Sep 23, 2024
1 parent 63bd4fe commit a9949f3
Show file tree
Hide file tree
Showing 12 changed files with 114 additions and 82 deletions.
2 changes: 1 addition & 1 deletion examples/federation/rover.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash

set -eumo pipefail

Expand Down
13 changes: 6 additions & 7 deletions src/core/blueprint/operators/apollo_federation.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
use std::collections::HashMap;
use std::fmt::Write;

use async_graphql::parser::types::ServiceDocument;

use super::{compile_call, compile_expr, compile_graphql, compile_grpc, compile_http, compile_js};
use crate::core::blueprint::FieldDefinition;
use crate::core::config::{
ApolloFederation, Config, ConfigModule, EntityResolver, Field, GraphQLOperationType, Resolver,
};
use crate::core::ir::model::IR;
use crate::core::sdl::SdlPrinter;
use crate::core::try_fold::TryFold;
use crate::core::valid::{Valid, Validator};
use crate::core::{config, Type};
Expand Down Expand Up @@ -78,16 +81,12 @@ pub fn compile_entity_resolver(inputs: CompileEntityResolver<'_>) -> Valid<IR, S
}

pub fn compile_service(config: &ConfigModule) -> Valid<IR, String> {
let mut sdl = config.to_sdl();
let sdl_printer = SdlPrinter { federation_compatibility: true };
let mut sdl = sdl_printer.print(ServiceDocument::from(config.config()));

writeln!(sdl).ok();
// Add tailcall specific definitions to the sdl output
writeln!(
sdl,
"{}",
crate::core::document::print(Config::graphql_schema())
)
.ok();
writeln!(sdl, "{}", sdl_printer.print(Config::graphql_schema())).ok();
writeln!(sdl).ok();
// Mark subgraph as Apollo federation v2 compatible according to [docs](https://www.apollographql.com/docs/apollo-server/using-federation/apollo-subgraph-setup/#2-opt-in-to-federation-2)
// (borrowed from async_graphql)
Expand Down
4 changes: 3 additions & 1 deletion src/core/config/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use crate::core::is_default;
use crate::core::macros::MergeRight;
use crate::core::merge_right::MergeRight;
use crate::core::scalar::Scalar;
use crate::core::sdl::SdlPrinter;
use crate::core::valid::{Valid, Validator};

#[derive(
Expand Down Expand Up @@ -534,7 +535,8 @@ impl Config {

/// Renders current config to graphQL string
pub fn to_sdl(&self) -> String {
crate::core::document::print(self.into())
let sdl_printer = SdlPrinter::default();
sdl_printer.print(self.into())
}

pub fn query(mut self, query: &str) -> Self {
Expand Down
2 changes: 1 addition & 1 deletion src/core/ir/eval_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ use async_graphql::{ServerError, Value};
use reqwest::header::HeaderMap;

use super::{GraphQLOperationContext, RelatedFields, ResolverContextLike, SelectionField};
use crate::core::document::print_directives;
use crate::core::http::RequestContext;
use crate::core::sdl::print_directives;

// TODO: rename to ResolverContext
#[derive(Clone)]
Expand Down
2 changes: 1 addition & 1 deletion src/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ pub mod config;
mod counter;
pub mod data_loader;
pub mod directive;
pub mod document;
pub mod endpoint;
mod errata;
pub mod error;
Expand All @@ -35,6 +34,7 @@ pub mod rest;
pub mod runtime;
pub mod scalar;
pub mod schema_extension;
pub mod sdl;
mod serde_value_ext;
pub mod tracing;
mod transform;
Expand Down
153 changes: 89 additions & 64 deletions src/core/document.rs → src/core/sdl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,30 +78,6 @@ fn print_pos_directives(directives: &[Positioned<ConstDirective>]) -> String {
output
}

fn print_schema(schema: &SchemaDefinition) -> String {
let directives = print_pos_directives(&schema.directives);

let query = schema
.query
.as_ref()
.map_or(String::new(), |q| format!(" query: {}\n", q.node));
let mutation = schema
.mutation
.as_ref()
.map_or(String::new(), |m| format!(" mutation: {}\n", m.node));
let subscription = schema
.subscription
.as_ref()
.map_or(String::new(), |s| format!(" subscription: {}\n", s.node));
if mutation.is_empty() && query.is_empty() {
return String::new();
}
format!(
"schema {}{{\n{}{}{}}}\n",
directives, query, mutation, subscription
)
}

fn const_directive_to_sdl(directive: &ConstDirective) -> DirectiveDefinition {
DirectiveDefinition {
description: None,
Expand Down Expand Up @@ -363,48 +339,97 @@ fn print_directive_type_def(directive: &DirectiveDefinition) -> String {
}
}

pub fn print(sd: ServiceDocument) -> String {
// Separate the definitions by type
let definitions_len = sd.definitions.len();
let mut schemas = Vec::with_capacity(definitions_len);
let mut scalars = Vec::with_capacity(definitions_len);
let mut interfaces = Vec::with_capacity(definitions_len);
let mut objects = Vec::with_capacity(definitions_len);
let mut enums = Vec::with_capacity(definitions_len);
let mut unions = Vec::with_capacity(definitions_len);
let mut inputs = Vec::with_capacity(definitions_len);
let mut directives = Vec::with_capacity(definitions_len);

for def in sd.definitions.iter() {
match def {
TypeSystemDefinition::Schema(schema) => schemas.push(print_schema(&schema.node)),
TypeSystemDefinition::Type(type_def) => match &type_def.node.kind {
TypeKind::Scalar => scalars.push(print_type_def(&type_def.node)),
TypeKind::Interface(_) => interfaces.push(print_type_def(&type_def.node)),
TypeKind::Enum(_) => enums.push(print_type_def(&type_def.node)),
TypeKind::Object(_) => objects.push(print_type_def(&type_def.node)),
TypeKind::Union(_) => unions.push(print_type_def(&type_def.node)),
TypeKind::InputObject(_) => inputs.push(print_type_def(&type_def.node)),
},
TypeSystemDefinition::Directive(type_def) => {
directives.push(print_directive_type_def(&type_def.node))
}
#[derive(Default)]
pub struct SdlPrinter {
pub federation_compatibility: bool,
}

impl SdlPrinter {
fn should_print_directive(&self, directive_name: &str) -> bool {
if self.federation_compatibility {
return directive_name != "link";
}

true
}

// Concatenate the definitions in the desired order
let sdl_string = schemas
.into_iter()
.chain(directives)
.chain(scalars)
.chain(inputs)
.chain(interfaces)
.chain(unions)
.chain(enums)
.chain(objects)
// Chain other types as needed...
.collect::<Vec<String>>()
.join("\n");
fn print_schema(&self, schema: &SchemaDefinition) -> String {
let directives = print_directives(
schema
.directives
.iter()
.map(|d| &d.node)
.filter(|&d| self.should_print_directive(&d.name.node)),
);

sdl_string.trim_end_matches('\n').to_string()
let query = schema
.query
.as_ref()
.map_or(String::new(), |q| format!(" query: {}\n", q.node));
let mutation = schema
.mutation
.as_ref()
.map_or(String::new(), |m| format!(" mutation: {}\n", m.node));
let subscription = schema
.subscription
.as_ref()
.map_or(String::new(), |s| format!(" subscription: {}\n", s.node));
if mutation.is_empty() && query.is_empty() {
return String::new();
}
format!(
"schema {} {{\n{}{}{}}}\n",
directives, query, mutation, subscription
)
}

pub fn print(&self, sd: ServiceDocument) -> String {
// Separate the definitions by type
let definitions_len = sd.definitions.len();
let mut schemas = Vec::with_capacity(definitions_len);
let mut scalars = Vec::with_capacity(definitions_len);
let mut interfaces = Vec::with_capacity(definitions_len);
let mut objects = Vec::with_capacity(definitions_len);
let mut enums = Vec::with_capacity(definitions_len);
let mut unions = Vec::with_capacity(definitions_len);
let mut inputs = Vec::with_capacity(definitions_len);
let mut directives = Vec::with_capacity(definitions_len);

for def in sd.definitions.iter() {
match def {
TypeSystemDefinition::Schema(schema) => {
schemas.push(self.print_schema(&schema.node))
}
TypeSystemDefinition::Type(type_def) => match &type_def.node.kind {
TypeKind::Scalar => scalars.push(print_type_def(&type_def.node)),
TypeKind::Interface(_) => interfaces.push(print_type_def(&type_def.node)),
TypeKind::Enum(_) => enums.push(print_type_def(&type_def.node)),
TypeKind::Object(_) => objects.push(print_type_def(&type_def.node)),
TypeKind::Union(_) => unions.push(print_type_def(&type_def.node)),
TypeKind::InputObject(_) => inputs.push(print_type_def(&type_def.node)),
},
TypeSystemDefinition::Directive(type_def) => {
if self.should_print_directive(&type_def.node.name.node) {
directives.push(print_directive_type_def(&type_def.node))
}
}
}
}

// Concatenate the definitions in the desired order
let sdl_string = schemas
.into_iter()
.chain(directives)
.chain(scalars)
.chain(inputs)
.chain(interfaces)
.chain(unions)
.chain(enums)
.chain(objects)
// Chain other types as needed...
.collect::<Vec<String>>()
.join("\n");

sdl_string.trim_end_matches('\n').to_string()
}
}
5 changes: 3 additions & 2 deletions tailcall-typedefs/src/gen_gql_schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ mod tests {

use schemars::schema::Schema;
use schemars::JsonSchema;
use tailcall::core::sdl::SdlPrinter;
use tailcall_typedefs_common::directive_definition::{
into_directive_definition, Attrs, DirectiveDefinition,
};
Expand Down Expand Up @@ -83,7 +84,7 @@ mod tests {
fn it_works_for_into_scalar() {
let builder = ServiceDocumentBuilder::new();
let doc = builder.add_scalar(FooScalar::scalar_definition()).build();
let actual = tailcall::core::document::print(doc);
let actual = SdlPrinter::default().print(doc);
let expected = "scalar FooScalar".to_string();
assert_eq!(actual, expected);
}
Expand All @@ -94,7 +95,7 @@ mod tests {
let doc = builder
.add_directive(ComplexDirective::directive_definition(&mut HashSet::new()))
.build();
let actual = tailcall::core::document::print(doc);
let actual = SdlPrinter::default().print(doc);
let expected = "directive @complexDirective(\n custom_type: FooType\n enum_field: FooEnum\n field1: Int!\n) repeatable on SCHEMA\n\ninput BarType {\n field2: BazType\n}\n\ninput BazType {\n field: Int!\n}\n\ninput FooType {\n field1: Int!\n field2: Int\n field3: [String!]\n inner_type: BarType\n}\n\nenum FooEnum {\n Variant\n Variant2\n Variat3\n}".to_string();

assert_eq!(actual, expected);
Expand Down
3 changes: 2 additions & 1 deletion tailcall-typedefs/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use serde_json::{json, Value};
use strum::IntoEnumIterator;
use tailcall::cli;
use tailcall::core::config::Config;
use tailcall::core::sdl::SdlPrinter;
use tailcall::core::tracing::default_tracing_for_name;
use tailcall::core::{scalar, FileIO};

Expand Down Expand Up @@ -85,7 +86,7 @@ async fn update_gql(file_io: Arc<dyn FileIO>) -> Result<()> {
file_io
.write(
path.to_str().ok_or(anyhow!("Unable to determine path"))?,
tailcall::core::document::print(doc).as_bytes(),
SdlPrinter::default().print(doc).as_bytes(),
)
.await?;
Ok(())
Expand Down

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion tests/core/snapshots/apollo-federation-entities.md_1.snap

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ expression: formatter
---
schema
@server(port: 8000)
@upstream(baseURL: "http://jsonplaceholder.typicode.com", batch: {delay: 100, headers: []}, httpCache: 42) {
@upstream(baseURL: "http://jsonplaceholder.typicode.com", batch: {delay: 100, headers: []}, httpCache: 42)
@link(src: "./posts.graphql", type: Config) {
query: Query
}

Expand Down
5 changes: 4 additions & 1 deletion tests/execution/apollo-federation-entities.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
```graphql @config
schema
@server(port: 8000)
@upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: 42, batch: {delay: 100}) {
@upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: 42, batch: {delay: 100})
@link(src: "./posts.graphql") {
query: Query
}

Expand All @@ -15,7 +16,9 @@ type User @call(steps: [{query: "user", args: {id: "{{.value.user.id}}"}}]) {
id: Int!
name: String!
}
```

```graphql @file:posts.graphql
type Post @expr(body: {id: "{{.value.id}}", title: "post-title-{{.value.id}}"}) {
id: Int!
title: String!
Expand Down

0 comments on commit a9949f3

Please sign in to comment.