Skip to content

Commit

Permalink
refactor: drop type variants (#1857)
Browse files Browse the repository at this point in the history
Co-authored-by: Tushar Mathur <[email protected]>
  • Loading branch information
Shylock-Hg and tusharmath authored May 7, 2024
1 parent 4474106 commit 57bf119
Show file tree
Hide file tree
Showing 21 changed files with 400 additions and 105 deletions.
40 changes: 29 additions & 11 deletions generated/.tailcallrc.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@
"schema"
],
"properties": {
"enums": {
"description": "A map of all the enum types in the schema",
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/Enum"
}
},
"links": {
"description": "A list of all links in the schema.",
"type": "array",
Expand Down Expand Up @@ -333,6 +340,28 @@
"ApplicationXWwwFormUrlencoded"
]
},
"Enum": {
"description": "Definition of GraphQL enum type",
"type": "object",
"required": [
"variants"
],
"properties": {
"doc": {
"type": [
"string",
"null"
]
},
"variants": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
}
}
},
"Expr": {
"description": "The `@expr` operators allows you to specify an expression that can evaluate to a value. The expression can be a static value or built form a Mustache template. schema.",
"type": "object",
Expand Down Expand Up @@ -1221,17 +1250,6 @@
"null"
]
},
"enum": {
"description": "Variants for the type if it's an enum",
"type": [
"array",
"null"
],
"items": {
"type": "string"
},
"uniqueItems": true
},
"fields": {
"description": "A map of field name and its definition.",
"type": "object",
Expand Down
45 changes: 24 additions & 21 deletions src/blueprint/definitions.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
use std::collections::BTreeSet;

use async_graphql_value::ConstValue;
use regex::Regex;

use crate::blueprint::Type::ListType;
use crate::blueprint::*;
use crate::config::{Config, Field, GraphQLOperationType, Protected, Union};
use crate::config::{Config, Enum, Field, GraphQLOperationType, Protected, Union};
use crate::directive::DirectiveCodec;
use crate::lambda::{Cache, Context, Expression};
use crate::try_fold::TryFold;
Expand Down Expand Up @@ -226,25 +224,21 @@ fn process_path(context: ProcessPathContext) -> Valid<Type, String> {
Valid::succeed(to_type(field, Some(is_required)))
}

fn to_enum_type_definition(
name: &str,
type_: &config::Type,
variants: &BTreeSet<String>,
) -> Valid<Definition, String> {
let enum_type_definition = Definition::Enum(EnumTypeDefinition {
name: name.to_string(),
fn to_enum_type_definition((name, eu): (&String, &Enum)) -> Definition {
Definition::Enum(EnumTypeDefinition {
name: name.to_owned(),
directives: Vec::new(),
description: type_.doc.clone(),
enum_values: variants
description: eu.doc.to_owned(),
enum_values: eu
.variants
.iter()
.map(|variant| EnumValueDefinition {
description: None,
name: variant.clone(),
directives: Vec::new(),
})
.collect(),
});
Valid::succeed(enum_type_definition)
})
}

fn to_object_type_definition(
Expand Down Expand Up @@ -525,13 +519,7 @@ pub fn to_definitions<'a>() -> TryFold<'a, ConfigModule, Vec<Definition>, String

Valid::from_iter(config_module.types.iter(), |(name, type_)| {
let dbl_usage = input_types.contains(name) && output_types.contains(name);
if let Some(variants) = &type_.variants {
if !variants.is_empty() {
to_enum_type_definition(name, type_, variants).trace(name)
} else {
Valid::fail("No variants found for enum".to_string())
}
} else if type_.scalar() {
if type_.scalar() {
to_scalar_type_definition(name).trace(name)
} else if dbl_usage {
Valid::fail("type is used in input and output".to_string()).trace(name)
Expand All @@ -556,5 +544,20 @@ pub fn to_definitions<'a>() -> TryFold<'a, ConfigModule, Vec<Definition>, String
types.extend(config_module.unions.iter().map(to_union_type_definition));
types
})
.fuse(Valid::from_iter(
config_module.enums.iter(),
|(name, type_)| {
if type_.variants.is_empty() {
Valid::fail("No variants found for enum".to_string())
} else {
Valid::succeed(to_enum_type_definition((name, type_)))
}
},
))
.map(|tp| {
let mut v = tp.0;
v.extend(tp.1);
v
})
})
}
26 changes: 12 additions & 14 deletions src/blueprint/from_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,27 +86,25 @@ where
let list = field.list();
let required = field.non_null();
let type_ = config.find_type(type_of);
let schema = match type_ {
Some(type_) => {
if let Some(variants) = type_.variants.clone() {
JsonSchema::Enum(variants)
} else {
let mut schema_fields = HashMap::new();
for (name, field) in type_.fields.iter() {
if field.script.is_none() && field.http.is_none() {
schema_fields.insert(name.clone(), to_json_schema_for_field(field, config));
}
}
JsonSchema::Obj(schema_fields)
let type_enum_ = config.find_enum(type_of);
let schema = if let Some(type_) = type_ {
let mut schema_fields = HashMap::new();
for (name, field) in type_.fields.iter() {
if field.script.is_none() && field.http.is_none() {
schema_fields.insert(name.clone(), to_json_schema_for_field(field, config));
}
}
None => match type_of {
JsonSchema::Obj(schema_fields)
} else if let Some(type_enum_) = type_enum_ {
JsonSchema::Enum(type_enum_.variants.to_owned())
} else {
match type_of {
"String" => JsonSchema::Str {},
"Int" => JsonSchema::Num {},
"Boolean" => JsonSchema::Bool {},
"JSON" => JsonSchema::Obj(HashMap::new()),
_ => JsonSchema::Str {},
},
}
};

if !required {
Expand Down
3 changes: 3 additions & 0 deletions src/blueprint/into_schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ fn to_type(def: &Definition) -> dynamic::Type {
for value in def.enum_values.iter() {
enum_type = enum_type.item(dynamic::EnumItem::new(value.name.clone()));
}
if let Some(desc) = def.description.clone() {
enum_type = enum_type.description(desc);
}
dynamic::Type::Enum(enum_type)
}
Definition::Union(def) => {
Expand Down
3 changes: 1 addition & 2 deletions src/blueprint/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,7 @@ pub fn validate_field_has_resolver(
if !field.has_resolver() {
let type_name = &field.type_of;
if let Some(ty) = types.get(type_name) {
// It's an enum
if ty.variants.is_some() || ty.scalar() {
if ty.scalar() {
return true;
}
let res = validate_type_has_resolvers(type_name, ty, types);
Expand Down
26 changes: 20 additions & 6 deletions src/config/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ pub struct Config {
#[serde(default, skip_serializing_if = "is_default")]
pub unions: BTreeMap<String, Union>,

///
/// A map of all the enum types in the schema
#[serde(default, skip_serializing_if = "is_default")]
pub enums: BTreeMap<String, Enum>,

///
/// A list of all links in the schema.
#[serde(default, skip_serializing_if = "is_default")]
Expand Down Expand Up @@ -94,10 +99,6 @@ pub struct Type {
///
/// Interfaces that the type implements.
pub implements: BTreeSet<String>,
#[serde(rename = "enum", default, skip_serializing_if = "is_default")]
///
/// Variants for the type if it's an enum
pub variants: Option<BTreeSet<String>>,
#[serde(default, skip_serializing_if = "is_default")]
///
/// Setting to indicate if the type can be cached.
Expand All @@ -123,7 +124,7 @@ impl Type {
}

pub fn scalar(&self) -> bool {
self.fields.is_empty() && self.variants.is_none()
self.fields.is_empty()
}
}

Expand Down Expand Up @@ -393,6 +394,13 @@ pub struct Union {
pub doc: Option<String>,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, schemars::JsonSchema, MergeRight)]
/// Definition of GraphQL enum type
pub struct Enum {
pub variants: BTreeSet<String>,
pub doc: Option<String>,
}

#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, schemars::JsonSchema)]
#[serde(deny_unknown_fields)]
/// The @http operator indicates that a field or node is backed by a REST API.
Expand Down Expand Up @@ -609,6 +617,10 @@ impl Config {
self.unions.get(name)
}

pub fn find_enum(&self, name: &str) -> Option<&Enum> {
self.enums.get(name)
}

pub fn to_yaml(&self) -> Result<String> {
Ok(serde_yaml::to_string(self)?)
}
Expand Down Expand Up @@ -645,7 +657,9 @@ impl Config {
}

pub fn contains(&self, name: &str) -> bool {
self.types.contains_key(name) || self.unions.contains_key(name)
self.types.contains_key(name)
|| self.unions.contains_key(name)
|| self.enums.contains_key(name)
}

pub fn from_json(json: &str) -> Result<Self> {
Expand Down
64 changes: 42 additions & 22 deletions src/config/from_document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ use async_graphql::Name;
use super::telemetry::Telemetry;
use super::{Tag, JS};
use crate::config::{
self, Cache, Call, Config, GraphQL, Grpc, Link, Modify, Omit, Protected, RootSchema, Server,
Union, Upstream,
self, Cache, Call, Config, Enum, GraphQL, Grpc, Link, Modify, Omit, Protected, RootSchema,
Server, Union, Upstream,
};
use crate::directive::DirectiveCodec;
use crate::valid::{Valid, Validator};
Expand All @@ -37,21 +37,24 @@ pub fn from_document(doc: ServiceDocument) -> Valid<Config, String> {

let types = to_types(&type_definitions);
let unions = to_union_types(&type_definitions);
let enums = to_enum_types(&type_definitions);
let schema = schema_definition(&doc).map(to_root_schema);
schema_definition(&doc).and_then(|sd| {
server(sd)
.fuse(upstream(sd))
.fuse(types)
.fuse(unions)
.fuse(enums)
.fuse(schema)
.fuse(links(sd))
.fuse(telemetry(sd))
.map(
|(server, upstream, types, unions, schema, links, telemetry)| Config {
|(server, upstream, types, unions, enums, schema, links, telemetry)| Config {
server,
upstream,
types,
unions,
enums,
schema,
links,
telemetry,
Expand Down Expand Up @@ -155,7 +158,7 @@ fn to_types(
&type_definition.node.directives,
)
.some(),
TypeKind::Enum(enum_type) => Valid::succeed(Some(to_enum(enum_type))),
TypeKind::Enum(_) => Valid::none(),
TypeKind::InputObject(input_object_type) => {
to_input_object(input_object_type, &type_definition.node.directives).some()
}
Expand Down Expand Up @@ -199,6 +202,31 @@ fn to_union_types(
)
}

fn to_enum_types(
type_definitions: &[&Positioned<TypeDefinition>],
) -> Valid<BTreeMap<String, Enum>, String> {
Valid::succeed(
type_definitions
.iter()
.filter_map(|type_definition| {
let type_name = pos_name_to_string(&type_definition.node.name);
let type_opt = match type_definition.node.kind.clone() {
TypeKind::Enum(enum_type) => to_enum(
enum_type,
type_definition
.node
.description
.to_owned()
.map(|pos| pos.node),
),
_ => return None,
};
Some((type_name, type_opt))
})
.collect(),
)
}

#[allow(clippy::too_many_arguments)]
fn to_object_type<T>(
object: &T,
Expand All @@ -219,26 +247,9 @@ where
let doc = description.to_owned().map(|pos| pos.node);
let implements = implements.iter().map(|pos| pos.node.to_string()).collect();
let added_fields = to_add_fields_from_directives(directives);
config::Type {
fields,
added_fields,
doc,
implements,
cache,
protected,
tag,
..Default::default()
}
config::Type { fields, added_fields, doc, implements, cache, protected, tag }
})
}
fn to_enum(enum_type: EnumType) -> config::Type {
let variants = enum_type
.values
.iter()
.map(|value| value.node.value.to_string())
.collect();
config::Type { variants: Some(variants), ..Default::default() }
}
fn to_input_object(
input_object_type: InputObjectType,
directives: &[Positioned<ConstDirective>],
Expand Down Expand Up @@ -375,6 +386,15 @@ fn to_union(union_type: UnionType, doc: &Option<String>) -> Union {
.collect();
Union { types, doc: doc.clone() }
}

fn to_enum(enum_type: EnumType, doc: Option<String>) -> Enum {
let variants = enum_type
.values
.iter()
.map(|member| member.node.value.node.as_str().to_owned())
.collect();
Enum { variants, doc }
}
fn to_const_field(directives: &[Positioned<ConstDirective>]) -> Option<config::Expr> {
directives.iter().find_map(|directive| {
if directive.node.name.node == config::Expr::directive_name() {
Expand Down
Loading

1 comment on commit 57bf119

@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.44ms 3.34ms 95.42ms 71.88%
Req/Sec 3.40k 124.13 3.73k 87.83%

405708 requests in 30.00s, 2.03GB read

Requests/sec: 13521.60

Transfer/sec: 69.40MB

Please sign in to comment.