Skip to content

Commit

Permalink
fix(grpc): resolve ambiguous types (#1718)
Browse files Browse the repository at this point in the history
Co-authored-by: Tushar Mathur <[email protected]>
Co-authored-by: hazyone <[email protected]>
Co-authored-by: meskill <[email protected]>
Co-authored-by: Everett Pompeii <[email protected]>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Shashi Kant <[email protected]>
Co-authored-by: shylock <[email protected]>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Eddy Oyieko <[email protected]>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Tim Burrows <[email protected]>
Co-authored-by: Tim Burrows <[email protected]>
Co-authored-by: Amit Singh <[email protected]>
Co-authored-by: codingsh <[email protected]>
Co-authored-by: Kunam Balaram Reddy <[email protected]>
Co-authored-by: Ezhil Shanmugham <[email protected]>
Co-authored-by: Biswarghya Biswas <[email protected]>
Co-authored-by: Ranjit Mahadik <[email protected]>
  • Loading branch information
19 people authored May 4, 2024
1 parent c89097b commit 0453c2d
Show file tree
Hide file tree
Showing 11 changed files with 408 additions and 164 deletions.
5 changes: 0 additions & 5 deletions generated/.tailcallrc.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1205,7 +1205,6 @@
},
"cache": {
"description": "Setting to indicate if the type can be cached.",
"default": null,
"anyOf": [
{
"$ref": "#/definitions/Cache"
Expand Down Expand Up @@ -1264,10 +1263,6 @@
}
]
},
"scalar": {
"description": "Flag to indicate if the type is a scalar.",
"type": "boolean"
},
"tag": {
"description": "Contains source information for the type.",
"anyOf": [
Expand Down
6 changes: 3 additions & 3 deletions src/blueprint/definitions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ fn process_field_within_type(context: ProcessFieldWithinTypeContext) -> Valid<Ty
}

let next_is_required = is_required && next_field.required;
if scalar::is_scalar(&next_field.type_of) {
if scalar::is_predefined_scalar(&next_field.type_of) {
return process_path(ProcessPathContext {
type_info,
config_module,
Expand Down Expand Up @@ -359,7 +359,7 @@ pub fn update_cache_resolvers<'a>(

fn validate_field_type_exist(config: &Config, field: &Field) -> Valid<(), String> {
let field_type = &field.type_of;
if !scalar::is_scalar(field_type) && !config.contains(field_type) {
if !scalar::is_predefined_scalar(field_type) && !config.contains(field_type) {
Valid::fail(format!("Undeclared type '{field_type}' was found"))
} else {
Valid::succeed(())
Expand Down Expand Up @@ -531,7 +531,7 @@ pub fn to_definitions<'a>() -> TryFold<'a, ConfigModule, Vec<Definition>, String
} else {
Valid::fail("No variants found for enum".to_string())
}
} else if type_.scalar {
} else 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 Down
2 changes: 1 addition & 1 deletion src/blueprint/mustache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ impl<'a> MustachePartsValidator<'a> {

if !is_query && val_type.is_nullable() {
return Err(format!("value '{}' is a nullable type", item.as_str()));
} else if len == 1 && !scalar::is_scalar(val_type.name()) {
} else if len == 1 && !scalar::is_predefined_scalar(val_type.name()) {
return Err(format!("value '{}' is not of a scalar type", item.as_str()));
} else if len == 1 {
break;
Expand Down
2 changes: 1 addition & 1 deletion src/blueprint/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ pub fn validate_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.variants.is_some() || ty.scalar() {
return true;
}
let res = validate_type_has_resolvers(type_name, ty, types);
Expand Down
264 changes: 166 additions & 98 deletions src/config/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ use crate::config::from_document::from_document;
use crate::config::source::Source;
use crate::directive::DirectiveCodec;
use crate::http::Method;
use crate::is_default;
use crate::json::JsonSchema;
use crate::macros::MergeRight;
use crate::merge_right::MergeRight;
use crate::valid::{Valid, Validator};
use crate::{is_default, scalar};

#[derive(
Serialize,
Expand Down Expand Up @@ -72,98 +72,6 @@ pub struct Config {
pub telemetry: Telemetry,
}

impl Config {
pub fn port(&self) -> u16 {
self.server.port.unwrap_or(8000)
}

pub fn find_type(&self, name: &str) -> Option<&Type> {
self.types.get(name)
}

pub fn find_union(&self, name: &str) -> Option<&Union> {
self.unions.get(name)
}

pub fn to_yaml(&self) -> Result<String> {
Ok(serde_yaml::to_string(self)?)
}

pub fn to_json(&self, pretty: bool) -> Result<String> {
if pretty {
Ok(serde_json::to_string_pretty(self)?)
} else {
Ok(serde_json::to_string(self)?)
}
}

pub fn to_document(&self) -> ServiceDocument {
self.clone().into()
}

pub fn to_sdl(&self) -> String {
let doc = self.to_document();
crate::document::print(doc)
}

pub fn query(mut self, query: &str) -> Self {
self.schema.query = Some(query.to_string());
self
}

pub fn types(mut self, types: Vec<(&str, Type)>) -> Self {
let mut graphql_types = BTreeMap::new();
for (name, type_) in types {
graphql_types.insert(name.to_string(), type_);
}
self.types = graphql_types;
self
}

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

/// Gets all the type names used in the schema.
pub fn get_all_used_type_names(&self) -> HashSet<String> {
let mut set = HashSet::new();
let mut stack = Vec::new();
if let Some(query) = &self.schema.query {
stack.push(query.clone());
}
if let Some(mutation) = &self.schema.mutation {
stack.push(mutation.clone());
}
while let Some(type_name) = stack.pop() {
if let Some(typ) = self.types.get(&type_name) {
set.insert(type_name);
for field in typ.fields.values() {
stack.extend(field.args.values().map(|arg| arg.type_of.clone()));
stack.push(field.type_of.clone());
}
}
}

set
}

pub fn get_all_unused_types(&self) -> HashSet<String> {
let used_types = self.get_all_used_type_names();
let all_types: HashSet<String> = self.types.keys().cloned().collect();
all_types.difference(&used_types).cloned().collect()
}

/// Removes all types that are not used in the schema.
pub fn remove_unused_types(mut self) -> Self {
let unused_types = self.get_all_unused_types();
for unused_type in unused_types {
self.types.remove(&unused_type);
}

self
}
}

///
/// Represents a GraphQL type.
/// A type can be an object, interface, enum or scalar.
Expand Down Expand Up @@ -196,10 +104,6 @@ pub struct Type {
pub variants: Option<BTreeSet<String>>,
#[serde(default, skip_serializing_if = "is_default")]
///
/// Flag to indicate if the type is a scalar.
pub scalar: bool,
#[serde(default)]
///
/// Setting to indicate if the type can be cached.
pub cache: Option<Cache>,
///
Expand All @@ -221,6 +125,10 @@ impl Type {
self.fields = graphql_fields;
self
}

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

#[derive(
Expand Down Expand Up @@ -467,7 +375,7 @@ pub struct Inline {
pub path: Vec<String>,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, schemars::JsonSchema)]
#[derive(Default, Serialize, Deserialize, Clone, Debug, PartialEq, Eq, schemars::JsonSchema)]
pub struct Arg {
#[serde(rename = "type")]
pub type_of: String,
Expand Down Expand Up @@ -693,6 +601,57 @@ pub struct AddField {
}

impl Config {
pub fn port(&self) -> u16 {
self.server.port.unwrap_or(8000)
}

pub fn find_type(&self, name: &str) -> Option<&Type> {
self.types.get(name)
}

pub fn find_union(&self, name: &str) -> Option<&Union> {
self.unions.get(name)
}

pub fn to_yaml(&self) -> Result<String> {
Ok(serde_yaml::to_string(self)?)
}

pub fn to_json(&self, pretty: bool) -> Result<String> {
if pretty {
Ok(serde_json::to_string_pretty(self)?)
} else {
Ok(serde_json::to_string(self)?)
}
}

pub fn to_document(&self) -> ServiceDocument {
self.clone().into()
}

pub fn to_sdl(&self) -> String {
let doc = self.to_document();
crate::document::print(doc)
}

pub fn query(mut self, query: &str) -> Self {
self.schema.query = Some(query.to_string());
self
}

pub fn types(mut self, types: Vec<(&str, Type)>) -> Self {
let mut graphql_types = BTreeMap::new();
for (name, type_) in types {
graphql_types.insert(name.to_string(), type_);
}
self.types = graphql_types;
self
}

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

pub fn from_json(json: &str) -> Result<Self> {
Ok(serde_json::from_str(json)?)
}
Expand Down Expand Up @@ -720,6 +679,115 @@ impl Config {
pub fn n_plus_one(&self) -> Vec<Vec<(String, String)>> {
super::n_plus_one::n_plus_one(self)
}

///
/// Given a starting type, this function searches for all the unique types
/// that this type can be connected to via it's fields
fn find_connections(&self, type_of: &str, mut types: HashSet<String>) -> HashSet<String> {
if let Some(type_) = self.find_type(type_of) {
types.insert(type_of.into());
for (_, field) in type_.fields.iter() {
if !types.contains(&field.type_of) {
types.insert(field.type_of.clone());
types = self.find_connections(&field.type_of, types);
}
}
}
types
}

///
/// Checks if a type is a scalar or not.
pub fn is_scalar(&self, type_name: &str) -> bool {
self.types
.get(type_name)
.map_or(scalar::is_predefined_scalar(type_name), |ty| ty.scalar())
}

///
/// Goes through the complete config and finds all the types that are used
/// as inputs directly ot indirectly.
pub fn input_types(&self) -> HashSet<String> {
self.arguments()
.iter()
.filter(|(_, arg)| !self.is_scalar(&arg.type_of))
.map(|(_, arg)| arg.type_of.as_str())
.fold(HashSet::new(), |types, type_of| {
self.find_connections(type_of, types)
})
}

/// Returns a list of all the types that are not used as inputs
pub fn output_types(&self) -> HashSet<String> {
let mut types = HashSet::new();
let input_types = self.input_types();

if let Some(ref query) = &self.schema.query {
types.insert(query.clone());
}

if let Some(ref mutation) = &self.schema.mutation {
types.insert(mutation.clone());
}

for (type_name, type_of) in self.types.iter() {
if (type_of.interface || !type_of.fields.is_empty()) && !input_types.contains(type_name)
{
for (_, field) in type_of.fields.iter() {
types.insert(field.type_of.clone());
}
}
}

types
}

/// Returns a list of all the arguments in the configuration
fn arguments(&self) -> Vec<(&String, &Arg)> {
self.types
.iter()
.filter(|(_, value)| !value.interface)
.flat_map(|(_, type_of)| type_of.fields.iter())
.flat_map(|(_, field)| field.args.iter())
.collect::<Vec<_>>()
}
/// Removes all types that are passed in the set
pub fn remove_types(mut self, types: HashSet<String>) -> Self {
for unused_type in types {
self.types.remove(&unused_type);
}

self
}

pub fn unused_types(&self) -> HashSet<String> {
let used_types = self.get_all_used_type_names();
let all_types: HashSet<String> = self.types.keys().cloned().collect();
all_types.difference(&used_types).cloned().collect()
}

/// Gets all the type names used in the schema.
pub fn get_all_used_type_names(&self) -> HashSet<String> {
let mut set = HashSet::new();
let mut stack = Vec::new();
if let Some(query) = &self.schema.query {
stack.push(query.clone());
}
if let Some(mutation) = &self.schema.mutation {
stack.push(mutation.clone());
}
while let Some(type_name) = stack.pop() {
if let Some(typ) = self.types.get(&type_name) {
set.insert(type_name);
for field in typ.fields.values() {
stack.extend(field.args.values().map(|arg| arg.type_of.clone()));
stack.push(field.type_of.clone());
}
}
}

set
}
}

#[derive(
Expand Down
Loading

1 comment on commit 0453c2d

@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.69ms 3.52ms 98.94ms 72.53%
Req/Sec 3.30k 265.06 4.06k 86.42%

393775 requests in 30.01s, 1.97GB read

Requests/sec: 13122.40

Transfer/sec: 67.35MB

Please sign in to comment.