Skip to content

Commit

Permalink
feat: add transformers to flatten single field types automatically (#…
Browse files Browse the repository at this point in the history
…2356)

Co-authored-by: Tushar Mathur <[email protected]>
  • Loading branch information
Shylock-Hg and tusharmath authored Jul 24, 2024
1 parent 9bcdc30 commit a2e7960
Show file tree
Hide file tree
Showing 5 changed files with 232 additions and 0 deletions.
114 changes: 114 additions & 0 deletions src/core/config/transformer/flatten_single_field.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
use std::collections::HashSet;

use crate::core::config::{AddField, Config, Omit};
use crate::core::transform::Transform;
use crate::core::valid::{Valid, Validator};

/// Flat single field type and inline to Query directly by addField
#[derive(Default)]
pub struct FlattenSingleField;

fn get_single_field_path(
config: &Config,
field_name: &str,
type_name: &str,
visited_types: &mut HashSet<String>,
) -> Option<Vec<String>> {
if visited_types.contains(type_name) {
// recursive type
return None;
}
visited_types.insert(type_name.to_owned());
let mut path = Vec::new();
path.push(field_name.to_owned());
if config.is_scalar(type_name) || config.enums.contains_key(type_name) {
return Some(path);
}
let ty = config.types.get(type_name);
if let Some(ty) = ty {
if ty.fields.len() == 1 {
if let Some((sub_field_name, sub_field)) = ty.fields.first_key_value() {
let sub_path = get_single_field_path(
config,
sub_field_name,
&sub_field.type_of,
visited_types,
);
if let Some(sub_path) = sub_path {
path.extend(sub_path);
Some(path)
} else {
None
}
} else {
None
}
} else {
None
}
} else {
None
}
}

impl Transform for FlattenSingleField {
type Value = Config;
type Error = String;
fn transform(&self, mut config: Self::Value) -> Valid<Self::Value, Self::Error> {
let origin_config = config.clone();
if let Some(root) = &config.schema.query {
let root_query = config.types.get_mut(root);
if let Some(root_query) = root_query {
let field_trans =
Valid::from_iter(root_query.fields.iter_mut(), |(name, field)| {
let mut visited_types = HashSet::<String>::new();
if let Some(path) = get_single_field_path(
&origin_config,
name,
&field.type_of,
&mut visited_types,
) {
if path.len() > 1 {
field.omit = Some(Omit {});
root_query
.added_fields
.push(AddField { name: name.to_owned(), path });
}
}
Valid::succeed(())
});
field_trans.map(|_| config)
} else {
Valid::fail("Query type is not existed.".to_owned())
}
} else {
Valid::succeed(config)
}
}
}

#[cfg(test)]
mod test {
use std::fs;

use tailcall_fixtures::configs;

use super::FlattenSingleField;
use crate::core::config::Config;
use crate::core::transform::Transform;
use crate::core::valid::Validator;

fn read_fixture(path: &str) -> String {
fs::read_to_string(path).unwrap()
}

#[test]
fn test_type_name_generator_transform() {
let config = Config::from_sdl(read_fixture(configs::FLATTEN_SINGLE_FIELD).as_str())
.to_result()
.unwrap();

let transformed_config = FlattenSingleField.transform(config).to_result().unwrap();
insta::assert_snapshot!(transformed_config.to_sdl());
}
}
2 changes: 2 additions & 0 deletions src/core/config/transformer/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod ambiguous_type;
mod consolidate_url;
mod flatten_single_field;
mod improve_type_names;
mod merge_types;
mod nested_unions;
Expand All @@ -10,6 +11,7 @@ mod union_input_type;

pub use ambiguous_type::{AmbiguousType, Resolution};
pub use consolidate_url::ConsolidateURL;
pub use flatten_single_field::FlattenSingleField;
pub use improve_type_names::ImproveTypeNames;
pub use merge_types::TypeMerger;
pub use nested_unions::NestedUnions;
Expand Down
4 changes: 4 additions & 0 deletions src/core/config/transformer/preset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub struct Preset {
pub consolidate_url: f32,
pub tree_shake: bool,
pub use_better_names: bool,
unwrap_single_field_types: bool,
}

impl Preset {
Expand All @@ -20,6 +21,7 @@ impl Preset {
consolidate_url: 0.0,
tree_shake: false,
use_better_names: false,
unwrap_single_field_types: true,
}
}
}
Expand All @@ -39,6 +41,7 @@ impl Transform for Preset {
super::TypeMerger::new(self.merge_type)
.when(super::TypeMerger::is_enabled(self.merge_type)),
)
.pipe(super::FlattenSingleField.when(self.unwrap_single_field_types))
.pipe(super::ImproveTypeNames.when(self.use_better_names))
.pipe(
super::ConsolidateURL::new(self.consolidate_url)
Expand All @@ -55,6 +58,7 @@ impl Default for Preset {
consolidate_url: 0.5,
use_better_names: true,
tree_shake: true,
unwrap_single_field_types: true,
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
---
source: src/core/config/transformer/flatten_single_field.rs
expression: transformed_config.to_sdl()
---
schema @server @upstream {
query: Query
}

type Bar {
a: Int
}

type Connection {
user: User
}

type Foo {
bar: Bar
}

type NotSingle {
f1: Int
f2: Int
}

type NotSingleMiddle {
t1: Type1
}

type Query @addField(name: "foo", path: ["foo", "bar", "a"]) {
foo: Foo @omit
not_single: NotSingle
not_single_middle: NotSingleMiddle
user: User
}

type Type1 {
t2: Type2
}

type Type2 {
t3: Type3
t4: Type4
}

type Type3 {
t5: Int
}

type Type4 {
t6: Bool
}

type User {
connections: [Connection]
}
56 changes: 56 additions & 0 deletions tailcall-fixtures/fixtures/configs/flatten_single_field.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
schema {
query: Query
}

type Query {
foo: Foo
not_single: NotSingle
not_single_middle: NotSingleMiddle
user: User
}

# Type with only one field
type Foo {
bar: Bar
}

# Type with only one field
type Bar {
a: Int
}

type NotSingle {
f1: Int
f2: Int
}

# Middle nested type is not single
type NotSingleMiddle {
t1: Type1
}

type Type1 {
t2: Type2
}

type Type2 {
t3: Type3
t4: Type4
}

type Type3 {
t5: Int
}

type Type4 {
t6: Bool
}

# Recursive type
type User {
connections: [Connection]
}

type Connection {
user: User
}

2 comments on commit a2e7960

@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 231 tests
test run_execution_spec::add-field-index-list.md ... ok
test run_execution_spec::add-field-many-list.md ... ok
test run_execution_spec::add-field-many.md ... ok
test run_execution_spec::add-field-modify.md ... ok
test run_execution_spec::add-field-with-modify.md ... ok
test run_execution_spec::add-field-with-composition.md ... ok
test run_execution_spec::add-field.md ... ok
test run_execution_spec::apollo-tracing.md ... ok
test run_execution_spec::async-cache-disabled.md ... ok
test run_execution_spec::async-cache-enable-multiple-resolvers.md ... FAILED
test run_execution_spec::async-cache-global.md ... ok
test run_execution_spec::async-cache-enabled.md ... ok
test run_execution_spec::async-cache-inflight-request.md ... ok
test run_execution_spec::auth-protected-without-auth.md ... ok
test run_execution_spec::auth-jwt.md ... ok
test run_execution_spec::auth-basic.md ... ok
test run_execution_spec::batching-disabled.md ... ok
test run_execution_spec::batching-default.md ... ok
test run_execution_spec::auth.md ... ok
test run_execution_spec::batching-group-by-default.md ... ok
test run_execution_spec::batching-group-by-optional-key.md ... ok
test run_execution_spec::batching-group-by.md ... ok
test run_execution_spec::batching-post.md ... ok
test run_execution_spec::batching.md ... ok
test run_execution_spec::cache-control.md ... ok
test run_execution_spec::caching-collision.md ... ok
test run_execution_spec::call-graphql-datasource.md ... ok
test run_execution_spec::caching.md ... ok
test run_execution_spec::call-multiple-steps-piping.md ... ok
test run_execution_spec::call-mutation.md ... ok
test run_execution_spec::call-operator.md ... ok
test run_execution_spec::cors-allow-cred-false.md ... ok
test run_execution_spec::cors-invalid-expose-headers.md ... ok
test run_execution_spec::cors-invalid-headers.md ... ok
test run_execution_spec::cors-invalid-methods.md ... ok
test run_execution_spec::cors-invalid-origins.md ... ok
test run_execution_spec::cors-allow-cred-true.md ... ok
test run_execution_spec::cors-allow-cred-vary.md ... ok
test run_execution_spec::custom-headers.md ... ok
test run_execution_spec::dedupe_batch_query_execution.md ... ok
test run_execution_spec::default-value-arg.md ... ok
test run_execution_spec::experimental-headers-error.md ... ok
test run_execution_spec::default-value-config.md ... ok
test run_execution_spec::env-value.md ... ok
test run_execution_spec::experimental-headers.md ... ok
test run_execution_spec::graphql-dataloader-batch-request.md ... ok
test run_execution_spec::graphql-dataloader-no-batch-request.md ... ok
test run_execution_spec::graphql-datasource-errors.md ... FAILED
test run_execution_spec::graphql-datasource-mutation.md ... ok
test run_execution_spec::graphql-datasource-no-args.md ... ok
test run_execution_spec::graphql-datasource-query-directives.md ... ok
test run_execution_spec::graphql-datasource-with-args.md ... ok
test run_execution_spec::graphql-datasource-with-empty-enum.md ... ok
test run_execution_spec::graphql-datasource-with-mandatory-enum.md ... FAILED
test run_execution_spec::graphql-nested-datasource.md ... ok
test run_execution_spec::grpc-batch.md ... ok
test run_execution_spec::grpc-error.md ... ok
test run_execution_spec::grpc-json.md ... ok
test run_execution_spec::grpc-map.md ... ok
test run_execution_spec::grpc-oneof.md ... FAILED
test run_execution_spec::grpc-override-url-from-upstream.md ... ok
test run_execution_spec::grpc-proto-with-same-package.md ... ok
test run_execution_spec::grpc-reflection.md ... ok
test run_execution_spec::grpc-simple.md ... ok
test run_execution_spec::grpc-url-from-upstream.md ... ok
test run_execution_spec::https.md ... ok
test run_execution_spec::inline-index-list.md ... ok
test run_execution_spec::inline-field.md ... FAILED
test run_execution_spec::input-type-protected-error.md ... ok
test run_execution_spec::io-cache.md ... ok
test run_execution_spec::inline-many-list.md ... ok
test run_execution_spec::inline-many.md ... ok
test run_execution_spec::js-directive.md ... FAILED
test run_execution_spec::modified-field.md ... ok
test run_execution_spec::mutation-put.md ... ok
test run_execution_spec::mutation.md ... ok
test run_execution_spec::n-plus-one-list.md ... ok
test run_execution_spec::jsonplaceholder-call-post.md ... ok
test run_execution_spec::n-plus-one.md ... ok
test run_execution_spec::nested-objects.md ... ok
test run_execution_spec::nested-recursive-types.md ... ok
test run_execution_spec::nesting-level3.md ... ok
test run_execution_spec::nullable-arg-query.md ... ok
test run_execution_spec::omit-index-list.md ... ok
test run_execution_spec::omit-resolved-by-parent.md ... FAILED
test run_execution_spec::recursive-types-no-resolver.md ... ok
test run_execution_spec::omit-many.md ... ok
test run_execution_spec::recursive-types-json.md ... ok
test run_execution_spec::recursive-types.md ... ok
test run_execution_spec::ref-other.md ... ok
test run_execution_spec::ref-other-nested.md ... ok
test run_execution_spec::rename-field.md ... ok
test run_execution_spec::resolve-with-headers.md ... ok
test run_execution_spec::request-to-upstream-batching.md ... ok
test run_execution_spec::resolve-with-vars.md ... ok
test run_execution_spec::resolved-by-parent.md ... FAILED
test run_execution_spec::rest-api-error.md ... ok
test run_execution_spec::rest-api-post.md ... ok
test run_execution_spec::rest-api.md ... ok
test run_execution_spec::showcase.md ... ok
test run_execution_spec::test-add-field-error.md ... ok
test run_execution_spec::simple-graphql.md ... ok
test run_execution_spec::simple-query.md ... ok
test run_execution_spec::test-add-field-list.md ... ok
test run_execution_spec::test-all-blueprint-errors.md ... ok
test run_execution_spec::test-batch-operator-post.md ... ok
test run_execution_spec::test-add-field.md ... ok
test run_execution_spec::test-add-link-to-empty-config.md ... ok
test run_execution_spec::test-call-operator-errors.md ... ok
test run_execution_spec::test-conflict-allowed-headers.md ... ok
test run_execution_spec::test-batching-group-by.md ... ok
test run_execution_spec::test-conflict-vars.md ... ok
test run_execution_spec::test-cache.md ... ok
test run_execution_spec::test-dbl-usage-many.md ... ok
test run_execution_spec::test-custom-scalar.md ... ok
test run_execution_spec::test-custom-types.md ... ok
test run_execution_spec::test-directives-undef-null-fields.md ... ok
test run_execution_spec::test-duplicated-link.md ... ok
test run_execution_spec::test-empty-link.md ... ok
test run_execution_spec::test-enable-jit.md ... ok
test run_execution_spec::test-description-many.md ... ok
test run_execution_spec::test-enum-aliases.md ... ok
test run_execution_spec::test-enum-empty.md ... ok
test run_execution_spec::test-enum-default.md ... ok
test run_execution_spec::test-enum-merge.md ... ok
test run_execution_spec::test-expr-error.md ... ok
test run_execution_spec::test-expr-scalar-as-string.md ... ok
test run_execution_spec::test-expr-with-add-field.md ... ok
test run_execution_spec::test-expr-with-inline.md ... ok
test run_execution_spec::test-enum-description.md ... FAILED
test run_execution_spec::test-expr-with-mustache.md ... ok
test run_execution_spec::test-field-already-implemented-from-Interface.md ... ok
test run_execution_spec::test-enum.md ... FAILED
test run_execution_spec::test-graphqlsource-no-base-url.md ... ok
test run_execution_spec::test-groupby-without-batching.md ... ok
test run_execution_spec::test-grpc-group-by.md ... ok
test run_execution_spec::test-grpc-invalid-method-format.md ... ok
test run_execution_spec::test-grpc-invalid-proto-id.md ... ok
test run_execution_spec::test-grpc-missing-fields.md ... ok
test run_execution_spec::test-grpc-nested-data.md ... ok
test run_execution_spec::test-grpc-nested-optional.md ... ok
test run_execution_spec::test-grpc-optional.md ... ok
test run_execution_spec::test-grpc-proto-path.md ... ok
test run_execution_spec::test-grpc-service-method.md ... ok
test run_execution_spec::test-grpc-service.md ... ok
test run_execution_spec::test-expr.md ... ok
test run_execution_spec::test-hostname-faliure.md ... ok
test run_execution_spec::test-graphqlsource.md ... ok
test run_execution_spec::test-grpc.md ... ok
test run_execution_spec::test-http-baseurl.md ... ok
test run_execution_spec::test-http-batchKey.md ... FAILED
test run_execution_spec::test-http-with-add-field.md ... ok
test run_execution_spec::test-http-with-inline.md ... ok
test run_execution_spec::test-http-headers.md ... ok
test run_execution_spec::test-http-with-mustache-expr.md ... ok
test run_execution_spec::test-inline-error.md ... ok
test run_execution_spec::test-http-tmpl.md ... ok
test run_execution_spec::test-http.md ... ok
test run_execution_spec::test-inline-list.md ... ok
test run_execution_spec::test-inline.md ... ok
test run_execution_spec::test-input-out.md ... ok
test run_execution_spec::test-input-documentation.md ... ok
test run_execution_spec::test-input-with-arg-out.md ... ok
test run_execution_spec::test-interface-from-json.md ... ok
test run_execution_spec::test-invalid-query-in-http.md ... ok
test run_execution_spec::test-invalid-server.md ... ok
test run_execution_spec::test-js-multi-onRequest-handlers.md ... ok
test run_execution_spec::test-js-multiple-scripts.md ... ok
test run_execution_spec::test-interface-result.md ... ok
test run_execution_spec::test-interface.md ... ok
test run_execution_spec::test-lack-resolver.md ... ok
test run_execution_spec::test-js-request-response-2.md ... ok
test run_execution_spec::test-list-args.md ... ok
test run_execution_spec::test-js-request-response.md ... ok
test run_execution_spec::test-merge-batch.md ... ok
test run_execution_spec::test-merge-query.md ... ok
test run_execution_spec::test-merge-nested.md ... ok
test run_execution_spec::test-merge-union.md ... ok
test run_execution_spec::test-merge-right-with-link-config.md ... ok
test run_execution_spec::test-missing-argument-on-all-resolvers.md ... ok
test run_execution_spec::test-missing-query-resolver.md ... ok
test run_execution_spec::test-missing-mutation-resolver.md ... ok
test run_execution_spec::test-missing-root-types.md ... ok
test run_execution_spec::test-missing-schema-query.md ... ok
test run_execution_spec::test-merge-server-sdl.md ... ok
test run_execution_spec::test-multiple-config-types.md ... ok
test run_execution_spec::test-multiple-resolvable-directives-on-field.md ... ok
test run_execution_spec::test-modify.md ... ok
test run_execution_spec::test-multi-interface.md ... ok
test run_execution_spec::test-nested-input.md ... ok
test run_execution_spec::test-no-base-url.md ... ok
test run_execution_spec::test-nested-link.md ... ok
test run_execution_spec::test-nested-value.md ... ok
test run_execution_spec::test-null-in-array.md ... ok
test run_execution_spec::test-null-in-object.md ... ok
test run_execution_spec::test-params-as-body.md ... ok
test run_execution_spec::test-omit-list.md ... ok
test run_execution_spec::test-omit.md ... ok
test run_execution_spec::test-query-documentation.md ... ok
test run_execution_spec::test-query.md ... ok
test run_execution_spec::test-response-header-value.md ... ok
test run_execution_spec::test-response-headers-multi.md ... ok
test run_execution_spec::test-response-headers-name.md ... ok
test run_execution_spec::test-ref-other.md ... ok
test run_execution_spec::test-response-header-merge.md ... ok
test run_execution_spec::test-scalars-builtin.md ... ok
test run_execution_spec::test-scalars-validation.md ... ok
test run_execution_spec::test-scalars-integers.md ... ok
test run_execution_spec::test-server-base-types.md ... ok
test run_execution_spec::test-set-cookie-headers.md ... ok
test run_execution_spec::test-server-vars.md ... ok
test run_execution_spec::test-static-value.md ... ok
test run_execution_spec::test-undefined-query.md ... ok
test run_execution_spec::test-scalars.md ... ok
test run_execution_spec::test-union-ambiguous.md ... FAILED
test run_execution_spec::test-union-same-types.md ... ok
test run_execution_spec::test-union-many-types.md ... ok
test run_execution_spec::test-tag.md ... ok
test run_execution_spec::test-upstream-headers.md ... ok
test run_execution_spec::undeclared-type-no-base-url.md ... ok
test run_execution_spec::undeclared-type.md ... ok
test run_execution_spec::upstream-batching.md ... ok
test run_execution_spec::test-upstream.md ... ok
test run_execution_spec::test-union.md ... FAILED
test run_execution_spec::upstream-fail-request.md ... ok
test run_execution_spec::with-args-url.md ... ok
test run_execution_spec::with-args.md ... ok
test run_execution_spec::with-nesting.md ... ok
test run_execution_spec::yaml-nested-unions.md ... ok
test run_execution_spec::yaml-union-in-type.md ... ok
test run_execution_spec::yaml-union.md ... ok

failures:

---- run_execution_spec::async-cache-enable-multiple-resolvers.md ----
test panicked: snapshot assertion for 'async-cache-enable-multiple-resolvers.md_0' failed in line 202

---- run_execution_spec::graphql-datasource-errors.md ----
test panicked: not yet implemented

---- run_execution_spec::graphql-datasource-with-mandatory-enum.md ----
test panicked: snapshot assertion for 'graphql-datasource-with-mandatory-enum.md_0' failed in line 202

---- run_execution_spec::grpc-oneof.md ----
test panicked: snapshot assertion for 'grpc-oneof.md_0' failed in line 202

---- run_execution_spec::inline-field.md ----
test panicked: snapshot assertion for 'inline-field.md_0' failed in line 202

---- run_execution_spec::js-directive.md ----
test panicked: snapshot assertion for 'js-directive.md_0' failed in line 202

---- run_execution_spec::omit-resolved-by-parent.md ----
test panicked: snapshot assertion for 'omit-resolved-by-parent.md_0' failed in line 202

---- run_execution_spec::resolved-by-parent.md ----
test panicked: snapshot assertion for 'resolved-by-parent.md_0' failed in line 202

---- run_execution_spec::test-enum-description.md ----
test panicked: snapshot assertion for 'test-enum-description.md_2' failed in line 202

---- run_execution_spec::test-enum.md ----
test panicked: snapshot assertion for 'test-enum.md_2' failed in line 202

---- run_execution_spec::test-http-batchKey.md ----
test panicked: snapshot assertion for 'test-http-batchKey.md_0' failed in line 202

---- run_execution_spec::test-union-ambiguous.md ----
test panicked: snapshot assertion for 'test-union-ambiguous.md_3' failed in line 202

---- run_execution_spec::test-union.md ----
test panicked: snapshot assertion for 'test-union.md_3' failed in line 202

failures:
run_execution_spec::async-cache-enable-multiple-resolvers.md
run_execution_spec::graphql-datasource-errors.md
run_execution_spec::graphql-datasource-with-mandatory-enum.md
run_execution_spec::grpc-oneof.md
run_execution_spec::inline-field.md
run_execution_spec::js-directive.md
run_execution_spec::omit-resolved-by-parent.md
run_execution_spec::resolved-by-parent.md
run_execution_spec::test-enum-description.md
run_execution_spec::test-enum.md
run_execution_spec::test-http-batchKey.md
run_execution_spec::test-union-ambiguous.md
run_execution_spec::test-union.md

test result: FAILED. 218 passed; 13 failed; 0 ignored; 0 measured; 0 filtered out; finished in 14.31s

@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.34ms 3.31ms 87.56ms 71.13%
Req/Sec 3.44k 195.22 6.63k 94.01%

411830 requests in 30.10s, 2.06GB read

Requests/sec: 13682.20

Transfer/sec: 70.23MB

Please sign in to comment.