Skip to content

Commit

Permalink
fix: check if graphql schema should be updated (#3173)
Browse files Browse the repository at this point in the history
  • Loading branch information
meskill authored Nov 28, 2024
1 parent ceefed9 commit 2375475
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 54 deletions.
36 changes: 9 additions & 27 deletions generated/.tailcallrc.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -1027,57 +1027,39 @@ enum Method {

enum LinkType {
"""
Points to another Tailcall Configuration file. The imported
configuration will be merged into the importing configuration.
Points to another Tailcall Configuration file. The imported configuration will be merged into the importing configuration.
"""
Config

"""
Points to a Protobuf file. The imported Protobuf file will be used by
the `@grpc` directive. If your API exposes a reflection endpoint, you
should set the type to `Grpc` instead.
Points to a Protobuf file. The imported Protobuf file will be used by the `@grpc` directive. If your API exposes a reflection endpoint, you should set the type to `Grpc` instead.
"""
Protobuf

"""
Points to a JS file. The imported JS file will be used by the `@js`
directive.
Points to a JS file. The imported JS file will be used by the `@js` directive.
"""
Script

"""
Points to a Cert file. The imported Cert file will be used by the server
to serve over HTTPS.
Points to a Cert file. The imported Cert file will be used by the server to serve over HTTPS.
"""
Cert

"""
Points to a Key file. The imported Key file will be used by the server
to serve over HTTPS.
Points to a Key file. The imported Key file will be used by the server to serve over HTTPS.
"""
Key

"""
A trusted document that contains GraphQL operations (queries, mutations)
that can be exposed as a REST API using the `@rest` directive.
A trusted document that contains GraphQL operations (queries, mutations) that can be exposed a REST API using the `@rest` directive.
"""
Operation

"""
Points to a Htpasswd file. The imported Htpasswd file will be used by
the server to authenticate users.
Points to a Htpasswd file. The imported Htpasswd file will be used by the server to authenticate users.
"""
Htpasswd

"""
Points to a Jwks file. The imported Jwks file will be used by the server
to authenticate users.
Points to a Jwks file. The imported Jwks file will be used by the server to authenticate users.
"""
Jwks

"""
Points to a reflection endpoint. The imported reflection endpoint will
be used by the `@grpc` directive to resolve data from gRPC services.
Points to a reflection endpoint. The imported reflection endpoint will be used by the `@grpc` directive to resolve data from gRPC services.
"""
Grpc
}
Expand Down
12 changes: 11 additions & 1 deletion src/core/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,10 +252,20 @@ fn print_type_def(type_def: &TypeDefinition) -> String {

fn print_enum_value(value: &async_graphql::parser::types::EnumValueDefinition) -> String {
let directives_str = print_pos_directives(&value.directives);
if directives_str.is_empty() {
let variant_def = if directives_str.is_empty() {
format!(" {}", value.value)
} else {
format!(" {} {}", value.value, directives_str)
};

if let Some(desc) = &value.description {
format!(
" \"\"\"\n {}\n \"\"\"\n{}",
desc.node.as_str(),
variant_def
)
} else {
variant_def
}
}

Expand Down
62 changes: 54 additions & 8 deletions tailcall-typedefs-common/src/enum_definition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,38 @@ use async_graphql::parser::types::{
use async_graphql::{Name, Positioned};
use schemars::schema::Schema;

#[derive(Debug)]
pub struct EnumVariant {
pub value: String,
pub description: Option<Positioned<String>>,
}

impl EnumVariant {
pub fn new(value: String) -> Self {
Self { value, description: None }
}
}

#[derive(Debug)]
pub struct EnumValue {
pub variants: Vec<String>,
pub variants: Vec<EnumVariant>,
pub description: Option<Positioned<String>>,
}

use crate::common::{get_description, pos};

pub fn into_enum_definition(enum_value: EnumValue, name: &str) -> TypeSystemDefinition {
let mut enum_value_definition = vec![];
for enum_value in enum_value.variants {
let formatted_value: String = enum_value
for enum_variant in enum_value.variants {
let formatted_value: String = enum_variant
.value
.to_string()
.chars()
.filter(|ch| ch != &'"')
.collect();
enum_value_definition.push(pos(EnumValueDefinition {
value: pos(Name::new(formatted_value)),
description: None,
description: enum_variant.description,
directives: vec![],
}));
}
Expand All @@ -39,16 +52,49 @@ pub fn into_enum_definition(enum_value: EnumValue, name: &str) -> TypeSystemDefi
pub fn into_enum_value(obj: &Schema) -> Option<EnumValue> {
match obj {
Schema::Object(schema_object) => {
let description = get_description(schema_object);
let description =
get_description(schema_object).map(|description| pos(description.to_owned()));

// if it has enum_values then it's raw enum
if let Some(enum_values) = &schema_object.enum_values {
return Some(EnumValue {
variants: enum_values
.iter()
.map(|val| val.to_string())
.collect::<Vec<String>>(),
description: description.map(|description| pos(description.to_owned())),
.map(|val| EnumVariant::new(val.to_string()))
.collect::<Vec<_>>(),
description,
});
}

// in case enum has description docs for the variants they will be generated
// as schema with `one_of` entry, where every enum variant is separate enum
// entry
if let Some(subschema) = &schema_object.subschemas {
if let Some(one_ofs) = &subschema.one_of {
let variants = one_ofs
.iter()
.filter_map(|one_of| {
// try to parse one_of value as enum
into_enum_value(one_of).and_then(|mut en| {
// if it has only single variant it's our high-level enum
if en.variants.len() == 1 {
Some(EnumVariant {
value: en.variants.pop().unwrap().value,
description: en.description,
})
} else {
None
}
})
})
.collect::<Vec<_>>();

if !variants.is_empty() {
return Some(EnumValue { variants, description });
}
}
}

None
}
_ => None,
Expand Down
76 changes: 58 additions & 18 deletions tailcall-typedefs/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
mod gen_gql_schema;

use std::env;
use std::path::PathBuf;
use std::ops::Deref;
use std::path::{Path, PathBuf};
use std::process::exit;
use std::sync::Arc;

Expand All @@ -15,8 +16,8 @@ use tailcall::core::config::Config;
use tailcall::core::tracing::default_tracing_for_name;
use tailcall::core::{scalar, FileIO};

static JSON_SCHEMA_FILE: &str = "../generated/.tailcallrc.schema.json";
static GRAPHQL_SCHEMA_FILE: &str = "../generated/.tailcallrc.graphql";
static JSON_SCHEMA_FILE: &str = "generated/.tailcallrc.schema.json";
static GRAPHQL_SCHEMA_FILE: &str = "generated/.tailcallrc.graphql";

#[tokio::main]
async fn main() {
Expand Down Expand Up @@ -51,9 +52,17 @@ async fn main() {
}

async fn mode_check() -> Result<()> {
let json_schema = get_file_path();
let rt = cli::runtime::init(&Default::default());
let file_io = rt.file;
let file_io = rt.file.deref();

check_json(file_io).await?;
check_graphql(file_io).await?;

Ok(())
}

async fn check_json(file_io: &dyn FileIO) -> Result<()> {
let json_schema = get_json_path();
let content = file_io
.read(
json_schema
Expand All @@ -62,10 +71,26 @@ async fn mode_check() -> Result<()> {
)
.await?;
let content = serde_json::from_str::<Value>(&content)?;
let schema = get_updated_json().await?;
let schema = get_updated_json()?;
match content.eq(&schema) {
true => Ok(()),
false => Err(anyhow!("Schema mismatch")),
false => Err(anyhow!("Schema file '{}' mismatch", JSON_SCHEMA_FILE)),
}
}

async fn check_graphql(file_io: &dyn FileIO) -> Result<()> {
let graphql_schema = get_graphql_path();
let content = file_io
.read(
graphql_schema
.to_str()
.ok_or(anyhow!("Unable to determine path"))?,
)
.await?;
let schema = get_updated_graphql();
match content.eq(&schema) {
true => Ok(()),
false => Err(anyhow!("Schema file '{}' mismatch", GRAPHQL_SCHEMA_FILE)),
}
}

Expand All @@ -74,27 +99,28 @@ async fn mode_fix() -> Result<()> {
let file_io = rt.file;

update_json(file_io.clone()).await?;
update_gql(file_io.clone()).await?;
update_graphql(file_io.clone()).await?;
Ok(())
}

async fn update_gql(file_io: Arc<dyn FileIO>) -> Result<()> {
let doc = gen_gql_schema::build_service_document();
async fn update_graphql(file_io: Arc<dyn FileIO>) -> Result<()> {
let schema = get_updated_graphql();

let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(GRAPHQL_SCHEMA_FILE);
let path = get_graphql_path();
tracing::info!("Updating Graphql Schema: {}", GRAPHQL_SCHEMA_FILE);
file_io
.write(
path.to_str().ok_or(anyhow!("Unable to determine path"))?,
tailcall::core::document::print(doc).as_bytes(),
schema.as_bytes(),
)
.await?;
Ok(())
}

async fn update_json(file_io: Arc<dyn FileIO>) -> Result<()> {
let path = get_file_path();
let schema = serde_json::to_string_pretty(&get_updated_json().await?)?;
tracing::info!("Updating JSON Schema: {}", path.to_str().unwrap());
let path = get_json_path();
let schema = serde_json::to_string_pretty(&get_updated_json()?)?;
tracing::info!("Updating JSON Schema: {}", JSON_SCHEMA_FILE);
file_io
.write(
path.to_str().ok_or(anyhow!("Unable to determine path"))?,
Expand All @@ -104,11 +130,19 @@ async fn update_json(file_io: Arc<dyn FileIO>) -> Result<()> {
Ok(())
}

fn get_file_path() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(JSON_SCHEMA_FILE)
fn get_root_path() -> &'static Path {
Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap()
}

fn get_json_path() -> PathBuf {
get_root_path().join(JSON_SCHEMA_FILE)
}

async fn get_updated_json() -> Result<Value> {
fn get_graphql_path() -> PathBuf {
get_root_path().join(GRAPHQL_SCHEMA_FILE)
}

fn get_updated_json() -> Result<Value> {
let mut schema: RootSchema = schemars::schema_for!(Config);
let scalar = scalar::Scalar::iter()
.map(|scalar| (scalar.name(), scalar.schema()))
Expand All @@ -118,3 +152,9 @@ async fn get_updated_json() -> Result<Value> {
let schema = json!(schema);
Ok(schema)
}

fn get_updated_graphql() -> String {
let doc = gen_gql_schema::build_service_document();

tailcall::core::document::print(doc)
}

1 comment on commit 2375475

@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 4.03ms 1.99ms 29.07ms 80.86%
Req/Sec 6.40k 0.93k 7.32k 93.33%

764174 requests in 30.01s, 3.83GB read

Requests/sec: 25462.16

Transfer/sec: 130.69MB

Please sign in to comment.