diff --git a/.codespellignore b/.codespellignore index 58218f7774..8a34eeef24 100644 --- a/.codespellignore +++ b/.codespellignore @@ -3,4 +3,5 @@ unit jsonplaceholder crate implementor -implementors \ No newline at end of file +implementors +alo \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index d8641749b5..bb8c490e0d 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,6 +1,9 @@ { "image": "mcr.microsoft.com/devcontainers/universal:2", "features": { - "ghcr.io/devcontainers/features/rust:1": {} + "ghcr.io/devcontainers/features/rust:1": { + "version": "1.75", + "profile": "default" + } } } diff --git a/.github/workflows/spell-check.yml b/.github/workflows/spell-check.yml new file mode 100644 index 0000000000..7e5708b558 --- /dev/null +++ b/.github/workflows/spell-check.yml @@ -0,0 +1,30 @@ +name: Spell check + +on: + push: + branches: + - "**" + paths: + - "docs/**" + - "**.md" + - "**.rs" + pull_request: + branches: [main] + types: [opened, reopened, synchronize] + paths: + - "docs/**" + - "**.md" + - "**.rs" + +jobs: + check_spelling: + name: Check spelling + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: codespell-project/actions-codespell@v2 + with: + skip: ".git,*/package.json,*/package-lock.json,*.lock,.github,.vscode,assets, *.snap" + ignore_words_file: .codespellignore + check_hidden: false diff --git a/.prettierrc b/.prettierrc index b38a6da2a4..75740ae27a 100644 --- a/.prettierrc +++ b/.prettierrc @@ -5,30 +5,6 @@ "tabWidth": 2, "bracketSpacing": false, "overrides": [ - { - "files": [ - "add-field-many-list.graphql", - "add-field-many-list.md", - "add-field-many.graphql", - "add-field-many.md", - "inline-many.graphql", - "inline-many.md", - "inline-many-list.graphql", - "inline-many-list.md", - "test-grpc.graphql", - "test-add-link-to-empty-config.graphql", - "test-add-link-to-empty-config.md", - "test-grpc.md", - "test-expr-success.graphql", - "test-expr-success.md", - "omit-many.graphql", - "omit-many.md", - "auth.md" - ], - "options": { - "printWidth": 210 - } - }, { "files": ["devcontainer.json"], "options": { diff --git a/Cargo.lock b/Cargo.lock index ad9df39e9e..0c324a0693 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -820,12 +820,6 @@ version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17f6e324229dc011159fcc089755d1e2e216a90d43a7dea6853ca740b84f35e7" -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - [[package]] name = "cfg-if" version = "1.0.0" @@ -948,16 +942,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "combine" -version = "4.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" -dependencies = [ - "bytes", - "memchr", -] - [[package]] name = "concurrent-queue" version = "2.4.0" @@ -2624,28 +2608,6 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" -[[package]] -name = "jni" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" -dependencies = [ - "cesu8", - "cfg-if", - "combine", - "jni-sys", - "log", - "thiserror", - "walkdir", - "windows-sys 0.45.0", -] - -[[package]] -name = "jni-sys" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" - [[package]] name = "js-sys" version = "0.3.69" @@ -2999,15 +2961,6 @@ dependencies = [ "linked-hash-map", ] -[[package]] -name = "malloc_buf" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" -dependencies = [ - "libc", -] - [[package]] name = "maplit" version = "1.0.2" @@ -3197,12 +3150,6 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" -[[package]] -name = "ndk-context" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" - [[package]] name = "new_debug_unreachable" version = "1.0.6" @@ -3316,15 +3263,6 @@ dependencies = [ "libloading", ] -[[package]] -name = "objc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" -dependencies = [ - "malloc_buf", -] - [[package]] name = "object" version = "0.32.2" @@ -3873,9 +3811,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.80" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56dea16b0a29e94408b9aa5e2940a4eedbd128a1ba20e8f7ae60fd3d465af0e" +checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" dependencies = [ "unicode-ident", ] @@ -4202,12 +4140,6 @@ dependencies = [ "bitflags 2.5.0", ] -[[package]] -name = "raw-window-handle" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" - [[package]] name = "rayon" version = "1.10.0" @@ -4665,9 +4597,9 @@ checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" [[package]] name = "serde" -version = "1.0.199" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c9f6e76df036c77cd94996771fb40db98187f096dd0b9af39c6c6e452ba966a" +checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" dependencies = [ "serde_derive", ] @@ -4715,9 +4647,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.199" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc" +checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" dependencies = [ "proc-macro2", "quote", @@ -5257,7 +5189,6 @@ dependencies = [ "ttl_cache", "update-informer", "url", - "webbrowser", "which 6.0.1", ] @@ -6224,23 +6155,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "webbrowser" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b6f804e41d0852e16d2eaee61c7e4f7d3e8ffdb7b8ed85886aeb0791fe9fcd" -dependencies = [ - "core-foundation", - "home", - "jni", - "log", - "ndk-context", - "objc", - "raw-window-handle", - "url", - "web-sys", -] - [[package]] name = "webpki-roots" version = "0.25.4" @@ -6324,15 +6238,6 @@ dependencies = [ "windows-targets 0.52.5", ] -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.48.0" @@ -6351,21 +6256,6 @@ dependencies = [ "windows-targets 0.52.5", ] -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-targets" version = "0.48.5" @@ -6397,12 +6287,6 @@ dependencies = [ "windows_x86_64_msvc 0.52.5", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -6415,12 +6299,6 @@ version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -6433,12 +6311,6 @@ version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -6457,12 +6329,6 @@ version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -6475,12 +6341,6 @@ version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -6493,12 +6353,6 @@ version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -6511,12 +6365,6 @@ version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.48.5" diff --git a/Cargo.toml b/Cargo.toml index 083687db41..b50107097a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,12 +50,12 @@ tokio = { workspace = true } anyhow = { workspace = true } derive_setters = "0.1.6" thiserror = "1.0.59" -serde_json = { version = "1.0", features = ["preserve_order"] } -serde = { version = "1.0", features = ["derive"] } +serde_json = { version = "1.0.116", features = ["preserve_order"] } +serde = { version = "1.0.200", features = ["derive"] } serde_qs = "0.13" -serde_yaml = "0.9" +serde_yaml = "0.9.34" serde_urlencoded = "0.7.1" -url = { version = "2", features = ["serde"] } +url = { version = "2.5.0", features = ["serde"] } reqwest = { version = "0.11", features = [ "json", "rustls-tls", @@ -79,7 +79,6 @@ futures-channel = { version = "0.3.30" } futures-timer = { version = "3.0.3", features = ["wasm-bindgen"] } futures-util = { workspace = true } lru = { version = "0.12.3" } -webbrowser = { version = "1.0.0", features = ["hardened", "disable-wsl"] } async-std = { version = "1.12.0", features = [ "wasm-bindgen-futures", "unstable", @@ -229,26 +228,9 @@ codegen-units = 1 opt-level = 'z' [[bench]] -name = "json_like_bench" -harness = false - -[[bench]] -name = "request_template_bench" -harness = false - -[[bench]] -name = "data_loader_bench" -harness = false - -[[bench]] -name = "impl_path_string_for_evaluation_context" -harness = false - -[[bench]] -name = "protobuf_convert_output" +name = "tailcall_benches" harness = false [[test]] name = "execution_spec" -harness = false - +harness = false \ No newline at end of file diff --git a/README.md b/README.md index 646291f793..e11bd0c634 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ The below file is a standard `.graphQL` file, with a few additions such as `@ser ```graphql schema - @server(port: 8000, graphiql: true, hostname: "0.0.0.0") + @server(port: 8000, hostname: "0.0.0.0") @upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: true) { query: Query } diff --git a/benches/data_loader_bench.rs b/benches/data_loader_bench.rs index bc7398ba53..59b47a49e9 100644 --- a/benches/data_loader_bench.rs +++ b/benches/data_loader_bench.rs @@ -7,7 +7,7 @@ use std::sync::Arc; use async_graphql::futures_util::future::join_all; use async_graphql_value::ConstValue; use async_trait::async_trait; -use criterion::{criterion_group, criterion_main, Criterion}; +use criterion::Criterion; use hyper::body::Bytes; use reqwest::Request; use tailcall::config::Batch; @@ -68,7 +68,7 @@ impl tailcall::Cache for Cache { } } -fn benchmark_data_loader(c: &mut Criterion) { +pub fn benchmark_data_loader(c: &mut Criterion) { c.bench_function("test_data_loader", |b| { b.iter(|| { let client = Arc::new(MockHttpClient { request_count: Arc::new(AtomicUsize::new(0)) }); @@ -111,10 +111,3 @@ fn benchmark_data_loader(c: &mut Criterion) { }) }); } - -criterion_group! { - name = benches; - config = Criterion::default(); - targets = benchmark_data_loader -} -criterion_main!(benches); diff --git a/benches/impl_path_string_for_evaluation_context.rs b/benches/impl_path_string_for_evaluation_context.rs index d6c0669cc0..fbd622f03e 100644 --- a/benches/impl_path_string_for_evaluation_context.rs +++ b/benches/impl_path_string_for_evaluation_context.rs @@ -6,7 +6,7 @@ use std::time::Duration; use async_graphql::context::SelectionField; use async_graphql::{Name, Value}; use async_trait::async_trait; -use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; +use criterion::{BenchmarkId, Criterion}; use http_cache_reqwest::{Cache, CacheMode, HttpCache, HttpCacheOptions, MokaManager}; use hyper::body::Bytes; use hyper::header::HeaderValue; @@ -251,7 +251,7 @@ fn request_context() -> RequestContext { .upstream(upstream) } -fn bench_main(c: &mut Criterion) { +pub fn bench_main(c: &mut Criterion) { let mut req_ctx = request_context().allowed_headers(TEST_HEADERS.clone()); req_ctx.server.vars = TEST_VARS.clone(); @@ -271,6 +271,3 @@ fn bench_main(c: &mut Criterion) { }); } } - -criterion_group!(benches, bench_main); -criterion_main!(benches); diff --git a/benches/json_like_bench.rs b/benches/json_like_bench.rs index 945fcd8ba5..85ec1916b5 100644 --- a/benches/json_like_bench.rs +++ b/benches/json_like_bench.rs @@ -1,8 +1,8 @@ -use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use criterion::{black_box, Criterion}; use serde_json::json; use tailcall::json::JsonLike; -fn benchmark_batched_body(c: &mut Criterion) { +pub fn benchmark_batched_body(c: &mut Criterion) { c.bench_function("test_batched_body", |b| { b.iter(|| { let input = json!({ @@ -30,7 +30,7 @@ fn benchmark_batched_body(c: &mut Criterion) { }); } -fn benchmark_group_by(c: &mut Criterion) { +pub fn benchmark_group_by(c: &mut Criterion) { let input = json!({ "data": [ {"type": "A", "value": {"id": "1"}}, @@ -47,6 +47,3 @@ fn benchmark_group_by(c: &mut Criterion) { }) }); } - -criterion_group!(benches, benchmark_batched_body, benchmark_group_by); -criterion_main!(benches); diff --git a/benches/protobuf_convert_output.rs b/benches/protobuf_convert_output.rs index ff40f53978..ae8bc13739 100644 --- a/benches/protobuf_convert_output.rs +++ b/benches/protobuf_convert_output.rs @@ -1,7 +1,7 @@ use std::path::Path; use anyhow::Result; -use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use criterion::{black_box, Criterion}; use rand::{thread_rng, Fill}; use serde_json::{json, Value}; use tailcall::blueprint::GrpcMethod; @@ -42,7 +42,7 @@ fn create_dummy_value(n: usize, m: usize) -> Result { Ok(value) } -fn benchmark_convert_output(c: &mut Criterion) { +pub fn benchmark_convert_output(c: &mut Criterion) { let proto_file_path = Path::new(PROTO_DIR).join(PROTO_FILE); let file_descriptor_set = protox::compile([proto_file_path], ["."]).unwrap(); let protobuf_set = ProtobufSet::from_proto_file(file_descriptor_set).unwrap(); @@ -65,6 +65,3 @@ fn benchmark_convert_output(c: &mut Criterion) { }) }); } - -criterion_group!(benches, benchmark_convert_output); -criterion_main!(benches); diff --git a/benches/request_template_bench.rs b/benches/request_template_bench.rs index bc67c1486f..4ab3d94694 100644 --- a/benches/request_template_bench.rs +++ b/benches/request_template_bench.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; -use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use criterion::{black_box, Criterion}; use derive_setters::Setters; use hyper::HeaderMap; use serde_json::json; @@ -30,7 +30,7 @@ impl HasHeaders for Context { &self.headers } } -fn benchmark_to_request(c: &mut Criterion) { +pub fn benchmark_to_request(c: &mut Criterion) { let tmpl_mustache = RequestTemplate::try_from(Endpoint::new( "http://localhost:3000/{{args.b}}?a={{args.a}}&b={{args.b}}&c={{args.c}}".to_string(), )) @@ -59,10 +59,3 @@ fn benchmark_to_request(c: &mut Criterion) { }) }); } - -criterion_group! { - name = benches; - config = Criterion::default(); - targets = benchmark_to_request -} -criterion_main!(benches); diff --git a/benches/tailcall_benches.rs b/benches/tailcall_benches.rs new file mode 100644 index 0000000000..d4f0a02e0d --- /dev/null +++ b/benches/tailcall_benches.rs @@ -0,0 +1,23 @@ +use criterion::{criterion_group, criterion_main, Criterion}; + +mod data_loader_bench; +mod impl_path_string_for_evaluation_context; +mod json_like_bench; +mod protobuf_convert_output; +mod request_template_bench; + +fn all_benchmarks(c: &mut Criterion) { + data_loader_bench::benchmark_data_loader(c); + impl_path_string_for_evaluation_context::bench_main(c); + json_like_bench::benchmark_batched_body(c); + json_like_bench::benchmark_group_by(c); + protobuf_convert_output::benchmark_convert_output(c); + request_template_bench::benchmark_to_request(c); +} + +criterion_group! { + name = benches; + config = Criterion::default(); + targets = all_benchmarks +} +criterion_main!(benches); diff --git a/ci-benchmark/benchmark.graphql b/ci-benchmark/benchmark.graphql index aac44a69ad..56d3a5e041 100644 --- a/ci-benchmark/benchmark.graphql +++ b/ci-benchmark/benchmark.graphql @@ -1,6 +1,4 @@ -schema - @server(port: 8000, graphiql: true, hostname: "0.0.0.0") - @upstream(baseURL: "http://jsonplaceholder.typicode.com") { +schema @server(port: 8000, hostname: "0.0.0.0") @upstream(baseURL: "http://jsonplaceholder.typicode.com") { query: Query } diff --git a/examples/apollo-tracing.graphql b/examples/apollo-tracing.graphql index d1718b676d..550a174bae 100644 --- a/examples/apollo-tracing.graphql +++ b/examples/apollo-tracing.graphql @@ -1,5 +1,5 @@ schema - @server(port: 8000, graphiql: true, hostname: "0.0.0.0") + @server(port: 8000, hostname: "0.0.0.0") @upstream(baseURL: "http://jsonplaceholder.typicode.com") @telemetry(export: {apollo: {apiKey: "1234", graphRef: "abc@123"}}) { query: Query diff --git a/examples/auth.graphql b/examples/auth.graphql index f8d50d242e..07b69ea0a0 100644 --- a/examples/auth.graphql +++ b/examples/auth.graphql @@ -1,5 +1,5 @@ schema - @server(port: 8000, graphiql: true) + @server(port: 8000) @upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: true) @link(id: "auth-basic", type: Htpasswd, src: ".htpasswd") @link(id: "auth-jwt", type: Jwks, src: ".jwks") { diff --git a/examples/call.graphql b/examples/call.graphql index 76da11d1d0..f0af6d6a51 100644 --- a/examples/call.graphql +++ b/examples/call.graphql @@ -1,4 +1,4 @@ -schema @server(graphiql: true) @upstream(baseURL: "http://jsonplaceholder.typicode.com") { +schema @upstream(baseURL: "http://jsonplaceholder.typicode.com") { query: Query } diff --git a/examples/cors.graphql b/examples/cors.graphql index 3d4c4af23a..7e1a78a34b 100644 --- a/examples/cors.graphql +++ b/examples/cors.graphql @@ -1,7 +1,6 @@ schema @server( port: 8000 - graphiql: true hostname: "0.0.0.0" headers: {cors: {allowOrigins: ["*"], allowHeaders: ["*"], allowMethods: [POST, GET, OPTIONS]}} ) diff --git a/examples/graphql-composition.graphql b/examples/graphql-composition.graphql index e7079e1250..4666a2af82 100644 --- a/examples/graphql-composition.graphql +++ b/examples/graphql-composition.graphql @@ -3,7 +3,7 @@ # and then you can run this example and test it on 8001 port schema - @server(port: 8001, graphiql: true, queryValidation: false, hostname: "0.0.0.0") + @server(port: 8001, queryValidation: false, hostname: "0.0.0.0") @upstream(baseURL: "http://localhost:8000/graphql", httpCache: true, batch: {delay: 1}) { query: Query } diff --git a/examples/grpc-reflection.graphql b/examples/grpc-reflection.graphql index 1d96f10149..b2099d7876 100644 --- a/examples/grpc-reflection.graphql +++ b/examples/grpc-reflection.graphql @@ -1,6 +1,6 @@ # for test upstream server see [repo](https://github.com/tailcallhq/tailcall/tree/main/tailcall-upstream-grpc) schema - @server(port: 8000, graphiql: true) + @server(port: 8000) @upstream(baseURL: "http://localhost:50051", httpCache: true, batch: {delay: 10}) @link(src: "http://localhost:50051", type: Grpc) { query: Query diff --git a/examples/grpc.graphql b/examples/grpc.graphql index e358218d71..2828b82d22 100644 --- a/examples/grpc.graphql +++ b/examples/grpc.graphql @@ -1,6 +1,6 @@ # for test upstream server see [repo](https://github.com/tailcallhq/rust-grpc) schema - @server(port: 8000, graphiql: true) + @server(port: 8000) @upstream(baseURL: "http://localhost:50051", httpCache: true, batch: {delay: 10}) @link(id: "news", src: "../tailcall-fixtures/fixtures/protobuf/news.proto", type: Protobuf) { query: Query diff --git a/examples/jsonplaceholder.json b/examples/jsonplaceholder.json index e50eb02ed2..f38a74e343 100644 --- a/examples/jsonplaceholder.json +++ b/examples/jsonplaceholder.json @@ -1,7 +1,6 @@ { "$schema": "./.tailcallrc.schema.json", "server": { - "graphiql": true, "hostname": "0.0.0.0", "port": 8000 }, diff --git a/examples/jsonplaceholder.yml b/examples/jsonplaceholder.yml index c6224182f2..17bdbb7e8e 100644 --- a/examples/jsonplaceholder.yml +++ b/examples/jsonplaceholder.yml @@ -1,5 +1,4 @@ server: - graphiql: true hostname: 0.0.0.0 port: 8000 upstream: diff --git a/examples/jsonplaceholder_batch.graphql b/examples/jsonplaceholder_batch.graphql index 4c5eb45081..bb54487aab 100644 --- a/examples/jsonplaceholder_batch.graphql +++ b/examples/jsonplaceholder_batch.graphql @@ -1,5 +1,5 @@ schema - @server(port: 8000, graphiql: true) + @server(port: 8000) @upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: true, batch: {delay: 1, maxSize: 1000}) { query: Query } diff --git a/examples/jsonplaceholder_batch.json b/examples/jsonplaceholder_batch.json index 9d2c754e4a..9b46cafcf6 100644 --- a/examples/jsonplaceholder_batch.json +++ b/examples/jsonplaceholder_batch.json @@ -1,7 +1,6 @@ { "server": { - "port": 8000, - "graphiql": true + "port": 8000 }, "upstream": { "baseURL": "http://jsonplaceholder.typicode.com", diff --git a/examples/jsonplaceholder_batch.yml b/examples/jsonplaceholder_batch.yml index d1f1c9fb7b..1a280b638e 100644 --- a/examples/jsonplaceholder_batch.yml +++ b/examples/jsonplaceholder_batch.yml @@ -1,6 +1,5 @@ server: port: 8000 - graphiql: true upstream: baseURL: http://jsonplaceholder.typicode.com httpCache: true diff --git a/examples/jsonplaceholder_http_2.graphql b/examples/jsonplaceholder_http_2.graphql index 5ac363643f..f09c2d33d1 100644 --- a/examples/jsonplaceholder_http_2.graphql +++ b/examples/jsonplaceholder_http_2.graphql @@ -1,5 +1,5 @@ schema - @server(port: 3000, graphiql: true, queryValidation: false, hostname: "0.0.0.0", version: HTTP2) + @server(port: 3000, queryValidation: false, hostname: "0.0.0.0", version: HTTP2) @link(type: Cert, src: "./example.crt") @link(type: Key, src: "./example.key") @upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: true) { diff --git a/examples/jsonplaceholder_script.graphql b/examples/jsonplaceholder_script.graphql index ab23e05e47..0cfab82800 100644 --- a/examples/jsonplaceholder_script.graphql +++ b/examples/jsonplaceholder_script.graphql @@ -1,5 +1,5 @@ schema - @server(port: 8000, graphiql: true, hostname: "0.0.0.0") + @server(port: 8000, hostname: "0.0.0.0") @upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: true) @link(type: Script, src: "scripts/echo.js") { query: Query diff --git a/examples/rest-api.graphql b/examples/rest-api.graphql index 11191d3586..59d39e8601 100644 --- a/examples/rest-api.graphql +++ b/examples/rest-api.graphql @@ -1,5 +1,5 @@ schema - @server(port: 8000, graphiql: true, hostname: "0.0.0.0") + @server(port: 8000, hostname: "0.0.0.0") @upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: true) @link(type: Operation, src: "operations/routes.graphql") { query: Query diff --git a/examples/telemetry-otlp.graphql b/examples/telemetry-otlp.graphql index e256dfd79a..a6775f6c5e 100644 --- a/examples/telemetry-otlp.graphql +++ b/examples/telemetry-otlp.graphql @@ -1,5 +1,5 @@ schema - @server(port: 8000, graphiql: true, hostname: "0.0.0.0") + @server(port: 8000, hostname: "0.0.0.0") @upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: true) @link(type: Operation, src: "operations/routes.graphql") @link(id: "news", src: "../tailcall-fixtures/fixtures/protobuf/news.proto", type: Protobuf) diff --git a/examples/telemetry-prometheus.graphql b/examples/telemetry-prometheus.graphql index 0fe17bd38d..e0ff49f1fb 100644 --- a/examples/telemetry-prometheus.graphql +++ b/examples/telemetry-prometheus.graphql @@ -1,5 +1,5 @@ schema - @server(port: 8000, graphiql: true, hostname: "0.0.0.0") + @server(port: 8000, hostname: "0.0.0.0") @upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: true) @telemetry(export: {prometheus: {path: "/metrics"}}) { query: Query diff --git a/examples/telemetry-stdout.graphql b/examples/telemetry-stdout.graphql index 572d980a1e..1907d6e2b4 100644 --- a/examples/telemetry-stdout.graphql +++ b/examples/telemetry-stdout.graphql @@ -1,5 +1,5 @@ schema - @server(port: 8000, graphiql: true, hostname: "0.0.0.0") + @server(port: 8000, hostname: "0.0.0.0") @upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: true) @telemetry(export: {stdout: {pretty: true}}) { query: Query diff --git a/generated/.tailcallrc.graphql b/generated/.tailcallrc.graphql index cbba820756..a598f757e4 100644 --- a/generated/.tailcallrc.graphql +++ b/generated/.tailcallrc.graphql @@ -237,11 +237,6 @@ directive @server( """ globalResponseTimeout: Int """ - `graphiql` activates the GraphiQL IDE at the root path within Tailcall, a tool for - query development and testing. @default `false`. - """ - graphiql: Boolean - """ `headers` contains key-value pairs that are included as default headers in server responses, allowing for consistent header management across all responses. """ @@ -355,6 +350,11 @@ directive @upstream( """ connectTimeout: Int """ + When set to `true`, it will ensure no HTTP, GRPC, or any other IO call is made more + than once within the context of a single GraphQL request. + """ + dedupe: Boolean! + """ The `http2Only` setting allows you to specify whether the client should always issue HTTP2 requests, without checking if the server supports it or not. By default it is set to `false` for all HTTP requests made by the server, but is automatically diff --git a/generated/.tailcallrc.schema.json b/generated/.tailcallrc.schema.json index 265020e9f4..b2f55892fe 100644 --- a/generated/.tailcallrc.schema.json +++ b/generated/.tailcallrc.schema.json @@ -961,13 +961,6 @@ ], "format": "int64" }, - "graphiql": { - "description": "`graphiql` activates the GraphiQL IDE at the root path within Tailcall, a tool for query development and testing. @default `false`.", - "type": [ - "boolean", - "null" - ] - }, "headers": { "description": "`headers` contains key-value pairs that are included as default headers in server responses, allowing for consistent header management across all responses.", "anyOf": [ @@ -1351,6 +1344,10 @@ "format": "uint64", "minimum": 0.0 }, + "dedupe": { + "description": "When set to `true`, it will ensure no HTTP, GRPC, or any other IO call is made more than once within the context of a single GraphQL request.", + "type": "boolean" + }, "http2Only": { "description": "The `http2Only` setting allows you to specify whether the client should always issue HTTP2 requests, without checking if the server supports it or not. By default it is set to `false` for all HTTP requests made by the server, but is automatically set to true for GRPC.", "type": [ diff --git a/npm/package-lock.json b/npm/package-lock.json index 28ca47660e..f3f3e9b7d7 100644 --- a/npm/package-lock.json +++ b/npm/package-lock.json @@ -17,9 +17,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.10.tgz", - "integrity": "sha512-Q+mk96KJ+FZ30h9fsJl+67IjNJm3x2eX+GBWGmocAKgzp27cowCOOqSdscX80s0SpdFXZnIv/+1xD1EctFx96Q==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", "cpu": [ "ppc64" ], @@ -33,9 +33,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.10.tgz", - "integrity": "sha512-7W0bK7qfkw1fc2viBfrtAEkDKHatYfHzr/jKAHNr9BvkYDXPcC6bodtm8AyLJNNuqClLNaeTLuwURt4PRT9d7w==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", "cpu": [ "arm" ], @@ -49,9 +49,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.10.tgz", - "integrity": "sha512-1X4CClKhDgC3by7k8aOWZeBXQX8dHT5QAMCAQDArCLaYfkppoARvh0fit3X2Qs+MXDngKcHv6XXyQCpY0hkK1Q==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", "cpu": [ "arm64" ], @@ -65,9 +65,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.10.tgz", - "integrity": "sha512-O/nO/g+/7NlitUxETkUv/IvADKuZXyH4BHf/g/7laqKC4i/7whLpB0gvpPc2zpF0q9Q6FXS3TS75QHac9MvVWw==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", "cpu": [ "x64" ], @@ -81,9 +81,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.10.tgz", - "integrity": "sha512-YSRRs2zOpwypck+6GL3wGXx2gNP7DXzetmo5pHXLrY/VIMsS59yKfjPizQ4lLt5vEI80M41gjm2BxrGZ5U+VMA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", "cpu": [ "arm64" ], @@ -97,9 +97,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.10.tgz", - "integrity": "sha512-alfGtT+IEICKtNE54hbvPg13xGBe4GkVxyGWtzr+yHO7HIiRJppPDhOKq3zstTcVf8msXb/t4eavW3jCDpMSmA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", "cpu": [ "x64" ], @@ -113,9 +113,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.10.tgz", - "integrity": "sha512-dMtk1wc7FSH8CCkE854GyGuNKCewlh+7heYP/sclpOG6Cectzk14qdUIY5CrKDbkA/OczXq9WesqnPl09mj5dg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", "cpu": [ "arm64" ], @@ -129,9 +129,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.10.tgz", - "integrity": "sha512-G5UPPspryHu1T3uX8WiOEUa6q6OlQh6gNl4CO4Iw5PS+Kg5bVggVFehzXBJY6X6RSOMS8iXDv2330VzaObm4Ag==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", "cpu": [ "x64" ], @@ -145,9 +145,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.10.tgz", - "integrity": "sha512-j6gUW5aAaPgD416Hk9FHxn27On28H4eVI9rJ4az7oCGTFW48+LcgNDBN+9f8rKZz7EEowo889CPKyeaD0iw9Kg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", "cpu": [ "arm" ], @@ -161,9 +161,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.10.tgz", - "integrity": "sha512-QxaouHWZ+2KWEj7cGJmvTIHVALfhpGxo3WLmlYfJ+dA5fJB6lDEIg+oe/0//FuyVHuS3l79/wyBxbHr0NgtxJQ==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", "cpu": [ "arm64" ], @@ -177,9 +177,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.10.tgz", - "integrity": "sha512-4ub1YwXxYjj9h1UIZs2hYbnTZBtenPw5NfXCRgEkGb0b6OJ2gpkMvDqRDYIDRjRdWSe/TBiZltm3Y3Q8SN1xNg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", "cpu": [ "ia32" ], @@ -193,9 +193,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.10.tgz", - "integrity": "sha512-lo3I9k+mbEKoxtoIbM0yC/MZ1i2wM0cIeOejlVdZ3D86LAcFXFRdeuZmh91QJvUTW51bOK5W2BznGNIl4+mDaA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", "cpu": [ "loong64" ], @@ -209,9 +209,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.10.tgz", - "integrity": "sha512-J4gH3zhHNbdZN0Bcr1QUGVNkHTdpijgx5VMxeetSk6ntdt+vR1DqGmHxQYHRmNb77tP6GVvD+K0NyO4xjd7y4A==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", "cpu": [ "mips64el" ], @@ -225,9 +225,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.10.tgz", - "integrity": "sha512-tgT/7u+QhV6ge8wFMzaklOY7KqiyitgT1AUHMApau32ZlvTB/+efeCtMk4eXS+uEymYK249JsoiklZN64xt6oQ==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", "cpu": [ "ppc64" ], @@ -241,9 +241,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.10.tgz", - "integrity": "sha512-0f/spw0PfBMZBNqtKe5FLzBDGo0SKZKvMl5PHYQr3+eiSscfJ96XEknCe+JoOayybWUFQbcJTrk946i3j9uYZA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", "cpu": [ "riscv64" ], @@ -257,9 +257,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.10.tgz", - "integrity": "sha512-pZFe0OeskMHzHa9U38g+z8Yx5FNCLFtUnJtQMpwhS+r4S566aK2ci3t4NCP4tjt6d5j5uo4h7tExZMjeKoehAA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", "cpu": [ "s390x" ], @@ -273,9 +273,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.10.tgz", - "integrity": "sha512-SpYNEqg/6pZYoc+1zLCjVOYvxfZVZj6w0KROZ3Fje/QrM3nfvT2llI+wmKSrWuX6wmZeTapbarvuNNK/qepSgA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", "cpu": [ "x64" ], @@ -289,9 +289,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.10.tgz", - "integrity": "sha512-ACbZ0vXy9zksNArWlk2c38NdKg25+L9pr/mVaj9SUq6lHZu/35nx2xnQVRGLrC1KKQqJKRIB0q8GspiHI3J80Q==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", "cpu": [ "x64" ], @@ -305,9 +305,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.10.tgz", - "integrity": "sha512-PxcgvjdSjtgPMiPQrM3pwSaG4kGphP+bLSb+cihuP0LYdZv1epbAIecHVl5sD3npkfYBZ0ZnOjR878I7MdJDFg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", "cpu": [ "x64" ], @@ -321,9 +321,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.10.tgz", - "integrity": "sha512-ZkIOtrRL8SEJjr+VHjmW0znkPs+oJXhlJbNwfI37rvgeMtk3sxOQevXPXjmAPZPigVTncvFqLMd+uV0IBSEzqA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", "cpu": [ "x64" ], @@ -337,9 +337,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.10.tgz", - "integrity": "sha512-+Sa4oTDbpBfGpl3Hn3XiUe4f8TU2JF7aX8cOfqFYMMjXp6ma6NJDztl5FDG8Ezx0OjwGikIHw+iA54YLDNNVfw==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", "cpu": [ "arm64" ], @@ -353,9 +353,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.10.tgz", - "integrity": "sha512-EOGVLK1oWMBXgfttJdPHDTiivYSjX6jDNaATeNOaCOFEVcfMjtbx7WVQwPSE1eIfCp/CaSF2nSrDtzc4I9f8TQ==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", "cpu": [ "ia32" ], @@ -369,9 +369,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.10.tgz", - "integrity": "sha512-whqLG6Sc70AbU73fFYvuYzaE4MNMBIlR1Y/IrUeOXFrWHxBEjjbZaQ3IXIQS8wJdAzue2GwYZCjOrgrU1oUHoA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", "cpu": [ "x64" ], @@ -554,9 +554,9 @@ } }, "node_modules/esbuild": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.10.tgz", - "integrity": "sha512-S1Y27QGt/snkNYrRcswgRFqZjaTG5a5xM3EQo97uNBnH505pdzSNe/HLBq1v0RO7iK/ngdbhJB6mDAp0OK+iUA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", "dev": true, "hasInstallScript": true, "bin": { @@ -566,29 +566,29 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.19.10", - "@esbuild/android-arm": "0.19.10", - "@esbuild/android-arm64": "0.19.10", - "@esbuild/android-x64": "0.19.10", - "@esbuild/darwin-arm64": "0.19.10", - "@esbuild/darwin-x64": "0.19.10", - "@esbuild/freebsd-arm64": "0.19.10", - "@esbuild/freebsd-x64": "0.19.10", - "@esbuild/linux-arm": "0.19.10", - "@esbuild/linux-arm64": "0.19.10", - "@esbuild/linux-ia32": "0.19.10", - "@esbuild/linux-loong64": "0.19.10", - "@esbuild/linux-mips64el": "0.19.10", - "@esbuild/linux-ppc64": "0.19.10", - "@esbuild/linux-riscv64": "0.19.10", - "@esbuild/linux-s390x": "0.19.10", - "@esbuild/linux-x64": "0.19.10", - "@esbuild/netbsd-x64": "0.19.10", - "@esbuild/openbsd-x64": "0.19.10", - "@esbuild/sunos-x64": "0.19.10", - "@esbuild/win32-arm64": "0.19.10", - "@esbuild/win32-ia32": "0.19.10", - "@esbuild/win32-x64": "0.19.10" + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" } }, "node_modules/escape-string-regexp": { @@ -645,9 +645,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.2.tgz", - "integrity": "sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==", + "version": "4.7.3", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.3.tgz", + "integrity": "sha512-ZvkrzoUA0PQZM6fy6+/Hce561s+faD1rsNwhnO5FelNjyy7EMGJ3Rz1AQ8GYDWjhRs/7dBLOEJvhK8MiEJOAFg==", "dev": true, "dependencies": { "resolve-pkg-maps": "^1.0.0" @@ -800,13 +800,13 @@ } }, "node_modules/tsx": { - "version": "4.7.3", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.7.3.tgz", - "integrity": "sha512-+fQnMqIp/jxZEXLcj6WzYy9FhcS5/Dfk8y4AtzJ6ejKcKqmfTF8Gso/jtrzDggCF2zTU20gJa6n8XqPYwDAUYQ==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.9.0.tgz", + "integrity": "sha512-UY0UUhDPL6MkqkZU4xTEjEBOLfV+RIt4xeeJ1qwK73xai4/zveG+X6+tieILa7rjtegUW2LE4p7fw7gAoLuytA==", "dev": true, "dependencies": { - "esbuild": "~0.19.10", - "get-tsconfig": "^4.7.2" + "esbuild": "~0.20.2", + "get-tsconfig": "^4.7.3" }, "bin": { "tsx": "dist/cli.mjs" @@ -819,9 +819,9 @@ } }, "node_modules/type-fest": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.18.0.tgz", - "integrity": "sha512-+dbmiyliDY/2TTcjCS7NpI9yV2iEFlUDk5TKnsbkN7ZoRu5s7bT+zvYtNFhFXC2oLwURGT2frACAZvbbyNBI+w==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.18.1.tgz", + "integrity": "sha512-qXhgeNsX15bM63h5aapNFcQid9jRF/l3ojDoDFmekDQEUufZ9U4ErVt6SjDxnHp48Ltrw616R8yNc3giJ3KvVQ==", "engines": { "node": ">=16" }, diff --git a/src/async_cache.rs b/src/async_cache.rs index ed191baa31..042fdb05f4 100644 --- a/src/async_cache.rs +++ b/src/async_cache.rs @@ -14,8 +14,8 @@ pub struct AsyncCache { #[derive(Clone)] pub enum CacheValue { - Pending(Sender>), - Ready(Result), + Pending(Sender>>), + Ready(Arc>), } impl @@ -41,7 +41,7 @@ impl Pin> + 'a + Send>> + Send, - ) -> Result { + ) -> Arc> { if let Some(cache_value) = self.get_cache_value(&key) { match cache_value { CacheValue::Pending(tx) => tx.subscribe().recv().await.unwrap(), @@ -53,7 +53,7 @@ impl = futures_util::future::join_all(handles) .await .into_iter() - .map(|res| res.unwrap().unwrap()) // Unwrap the Result from the join, and the Result from get_or_eval + .map(|res| res.unwrap().as_ref().clone().unwrap()) // Unwrap the Result from the join, and the Result from get_or_eval .collect(); // Check that all tasks received the correct value. diff --git a/src/blueprint/blueprint.rs b/src/blueprint/blueprint.rs index 5cd109d411..e9289faeb9 100644 --- a/src/blueprint/blueprint.rs +++ b/src/blueprint/blueprint.rs @@ -53,6 +53,10 @@ impl Type { Type::ListType { non_null, .. } => *non_null, } } + /// checks if the type is a list + pub fn is_list(&self) -> bool { + matches!(self, Type::ListType { .. }) + } } #[derive(Clone, Debug)] diff --git a/src/blueprint/into_schema.rs b/src/blueprint/into_schema.rs index 9b886bb848..6102314f6a 100644 --- a/src/blueprint/into_schema.rs +++ b/src/blueprint/into_schema.rs @@ -2,6 +2,7 @@ use std::borrow::Cow; use std::sync::Arc; use async_graphql::dynamic::{self, FieldFuture, FieldValue, SchemaBuilder}; +use async_graphql::ErrorExtensions; use async_graphql_value::ConstValue; use futures_util::TryFutureExt; use tracing::Instrument; @@ -68,8 +69,10 @@ fn to_type(def: &Definition) -> dynamic::Type { let ctx: ResolverContext = ctx.into(); let ctx = EvaluationContext::new(req_ctx, &ctx); - let const_value = - expr.eval(ctx, &Concurrent::Sequential).await?; + let const_value = expr + .eval(ctx, &Concurrent::Sequential) + .await + .map_err(|err| err.extend())?; let p = match const_value { ConstValue::List(a) => Some(FieldValue::list(a)), ConstValue::Null => FieldValue::NONE, diff --git a/src/blueprint/server.rs b/src/blueprint/server.rs index b62f8e63eb..5caaad887f 100644 --- a/src/blueprint/server.rs +++ b/src/blueprint/server.rs @@ -19,7 +19,6 @@ pub struct Server { pub enable_apollo_tracing: bool, pub enable_cache_control_header: bool, pub enable_set_cookie_header: bool, - pub enable_graphiql: bool, pub enable_introspection: bool, pub enable_query_validation: bool, pub enable_response_validation: bool, @@ -134,7 +133,6 @@ impl TryFrom for Server { enable_apollo_tracing: (config_server).enable_apollo_tracing(), enable_cache_control_header: (config_server).enable_cache_control(), enable_set_cookie_header: (config_server).enable_set_cookies(), - enable_graphiql: (config_server).enable_graphiql(), enable_introspection: (config_server).enable_introspection(), enable_query_validation: (config_server).enable_query_validation(), enable_response_validation: (config_server).enable_http_validation(), diff --git a/src/blueprint/upstream.rs b/src/blueprint/upstream.rs index 279bbf1a55..b0d935d649 100644 --- a/src/blueprint/upstream.rs +++ b/src/blueprint/upstream.rs @@ -27,6 +27,7 @@ pub struct Upstream { pub http_cache: bool, pub batch: Option, pub http2_only: bool, + pub dedupe: bool, } impl Upstream { @@ -78,6 +79,7 @@ impl TryFrom<&ConfigModule> for Upstream { http_cache: (config_upstream).get_enable_http_cache(), batch, http2_only: (config_upstream).get_http_2_only(), + dedupe: (config_upstream).get_dedupe(), }) .to_result() } diff --git a/src/cli/runtime/http.rs b/src/cli/runtime/http.rs index 8b1ec3baa7..070be612bb 100644 --- a/src/cli/runtime/http.rs +++ b/src/cli/runtime/http.rs @@ -200,7 +200,7 @@ mod tests { let header_serv = server.mock(|when, then| { when.method(httpmock::Method::GET).path("/test"); - then.status(200).body("Alo"); + then.status(200).body("Hello"); }); let native_http = NativeHttp::init(&Default::default(), &Default::default()); @@ -216,7 +216,7 @@ mod tests { assert!(result.is_ok()); let response = result.unwrap(); assert_eq!(response.status, reqwest::StatusCode::OK); - assert_eq!(response.body, Bytes::from("Alo")); + assert_eq!(response.body, Bytes::from("Hello")); header_serv.assert(); } diff --git a/src/cli/server/http_1.rs b/src/cli/server/http_1.rs index 6d725483cd..2fa86de2b0 100644 --- a/src/cli/server/http_1.rs +++ b/src/cli/server/http_1.rs @@ -33,7 +33,7 @@ pub async fn start_http_1( let builder = hyper::Server::try_bind(&addr) .map_err(CLIError::from)? .http1_pipeline_flush(sc.app_ctx.blueprint.server.pipeline_flush); - super::log_launch_and_open_browser(sc.as_ref()); + super::log_launch(sc.as_ref()); if let Some(sender) = server_up_sender { sender diff --git a/src/cli/server/http_2.rs b/src/cli/server/http_2.rs index 0066d8bdbf..97ebe8df22 100644 --- a/src/cli/server/http_2.rs +++ b/src/cli/server/http_2.rs @@ -45,7 +45,7 @@ pub async fn start_http_2( let builder = Server::builder(acceptor).http2_only(true); - super::log_launch_and_open_browser(sc.as_ref()); + super::log_launch(sc.as_ref()); if let Some(sender) = server_up_sender { sender diff --git a/src/cli/server/mod.rs b/src/cli/server/mod.rs index efce6d98c6..c547c774e3 100644 --- a/src/cli/server/mod.rs +++ b/src/cli/server/mod.rs @@ -7,17 +7,15 @@ pub use server::Server; use self::server_config::ServerConfig; -fn log_launch_and_open_browser(sc: &ServerConfig) { +fn log_launch(sc: &ServerConfig) { let addr = sc.addr().to_string(); tracing::info!( "🚀 Tailcall launched at [{}] over {}", addr, sc.http_version() ); - if sc.graphiql() { - let url = sc.graphiql_url(); - tracing::info!("🌍 Playground: {}", url); - let _ = webbrowser::open(url.as_str()); - } + let url = sc.graphiql_url(); + let url = format!("https://tailcall.run/playground/?u={}/graphql", url); + tracing::info!("🌍 Playground: {}", url); } diff --git a/src/cli/server/server_config.rs b/src/cli/server/server_config.rs index 7538633f05..e1b60bc0e1 100644 --- a/src/cli/server/server_config.rs +++ b/src/cli/server/server_config.rs @@ -66,8 +66,4 @@ impl ServerConfig { format!("{}://{}", protocol, addr) } - - pub fn graphiql(&self) -> bool { - self.blueprint.server.enable_graphiql - } } diff --git a/src/config/reader.rs b/src/config/reader.rs index 680e58d79d..2cebbe477e 100644 --- a/src/config/reader.rs +++ b/src/config/reader.rs @@ -5,6 +5,7 @@ use rustls_pemfile; use rustls_pki_types::{ CertificateDer, PrivateKeyDer, PrivatePkcs1KeyDer, PrivatePkcs8KeyDer, PrivateSec1KeyDer, }; +use url::Url; use super::{ConfigModule, Content, Link, LinkType}; use crate::config::{Config, ConfigReaderContext, Source}; @@ -233,10 +234,12 @@ impl ConfigReader { Ok(config_module) } - /// Checks if path is absolute else it joins file path with relative dir - /// path + /// Checks if path is a URL or absolute path, returns directly if so. + /// Otherwise, it joins file path with relative dir path. fn resolve_path(src: &str, root_dir: Option<&Path>) -> String { - if Path::new(&src).is_absolute() { + if let Ok(url) = Url::parse(src) { + url.to_string() + } else if Path::new(&src).is_absolute() { src.to_string() } else { let path = root_dir.unwrap_or(Path::new("")); @@ -361,13 +364,18 @@ mod reader_tests { let path_dir = Path::new("abc/xyz"); let file_relative = "foo/bar/my.proto"; let file_absolute = "/foo/bar/my.proto"; + let remote_url_path = "https://raw.githubusercontent.com/tailcallhq/tailcall/main/tailcall-fixtures/fixtures/protobuf/news.proto"; assert_eq!( path_dir.to_path_buf().join(file_relative), PathBuf::from(ConfigReader::resolve_path(file_relative, Some(path_dir))) ); assert_eq!( - "/foo/bar/my.proto", + file_absolute, ConfigReader::resolve_path(file_absolute, Some(path_dir)) ); + assert_eq!( + remote_url_path, + ConfigReader::resolve_path(remote_url_path, Some(path_dir)) + ); } } diff --git a/src/config/server.rs b/src/config/server.rs index f83e88f88a..f8e7e60969 100644 --- a/src/config/server.rs +++ b/src/config/server.rs @@ -40,11 +40,6 @@ pub struct Server { /// termination, acting as a safeguard against long-running queries. pub global_response_timeout: Option, - #[serde(default, skip_serializing_if = "is_default")] - /// `graphiql` activates the GraphiQL IDE at the root path within Tailcall, - /// a tool for query development and testing. @default `false`. - pub graphiql: Option, - #[serde(default, skip_serializing_if = "is_default")] /// `hostname` sets the server hostname. pub hostname: Option, @@ -134,9 +129,7 @@ impl Server { pub fn enable_apollo_tracing(&self) -> bool { self.apollo_tracing.unwrap_or(false) } - pub fn enable_graphiql(&self) -> bool { - self.graphiql.unwrap_or(false) - } + pub fn get_global_response_timeout(&self) -> i64 { self.global_response_timeout.unwrap_or(0) } diff --git a/src/config/upstream.rs b/src/config/upstream.rs index 837bbc03ea..32b0366b89 100644 --- a/src/config/upstream.rs +++ b/src/config/upstream.rs @@ -135,6 +135,11 @@ pub struct Upstream { /// The User-Agent header value to be used in HTTP requests. @default /// `Tailcall/1.0` pub user_agent: Option, + + #[serde(default, skip_serializing_if = "is_default")] + /// When set to `true`, it will ensure no HTTP, GRPC, or any other IO call + /// is made more than once within the context of a single GraphQL request. + pub dedupe: bool, } impl Upstream { @@ -186,6 +191,10 @@ impl Upstream { pub fn get_http_2_only(&self) -> bool { self.http2_only.unwrap_or(false) } + + pub fn get_dedupe(&self) -> bool { + self.dedupe + } } #[cfg(test)] diff --git a/src/data_loader/cache.rs b/src/data_loader/cache.rs index 085818d8c7..3c4ed82d60 100644 --- a/src/data_loader/cache.rs +++ b/src/data_loader/cache.rs @@ -45,6 +45,7 @@ where fn insert(&mut self, _key: Cow<'_, Self::Key>, _val: Cow<'_, Self::Value>) {} #[inline] + #[allow(dead_code)] fn remove(&mut self, _key: &K) {} #[inline] diff --git a/src/data_loader/storage.rs b/src/data_loader/storage.rs index ea36c74f83..1d35837224 100644 --- a/src/data_loader/storage.rs +++ b/src/data_loader/storage.rs @@ -17,6 +17,7 @@ pub trait CacheStorage: Send + Sync + 'static { /// cache, then it updates the key's value. fn insert(&mut self, key: Cow<'_, Self::Key>, val: Cow<'_, Self::Value>); + #[allow(dead_code)] /// Removes the value corresponding to the key from the cache. fn remove(&mut self, key: &Self::Key); diff --git a/src/grpc/protobuf.rs b/src/grpc/protobuf.rs index 51819426df..c58360405d 100644 --- a/src/grpc/protobuf.rs +++ b/src/grpc/protobuf.rs @@ -227,9 +227,9 @@ impl ProtobufOperation { ) })?; - let mut ser = serde_json::Serializer::new(vec![]); - message.serialize_with_options(&mut ser, &self.serialize_options)?; - let json = serde_json::from_slice::(ser.into_inner().as_ref())?; + let mut serializer = serde_json::Serializer::new(vec![]); + message.serialize_with_options(&mut serializer, &self.serialize_options)?; + let json = serde_json::from_slice::(serializer.into_inner().as_ref())?; Ok(json) } diff --git a/src/http/mod.rs b/src/http/mod.rs index ba8c00d7fd..d4705e45f8 100644 --- a/src/http/mod.rs +++ b/src/http/mod.rs @@ -1,3 +1,15 @@ +pub use cache::*; +pub use data_loader::*; +pub use data_loader_request::*; +use headers::HeaderValue; +pub use method::Method; +pub use request_context::RequestContext; +pub use request_handler::{handle_request, API_URL_PREFIX}; +pub use request_template::RequestTemplate; +pub use response::*; + +pub use crate::app_context::AppContext; + mod data_loader; mod cache; @@ -10,13 +22,5 @@ mod response; pub mod showcase; mod telemetry; -pub use cache::*; -pub use data_loader::*; -pub use data_loader_request::*; -pub use method::Method; -pub use request_context::RequestContext; -pub use request_handler::{graphiql, handle_request, API_URL_PREFIX}; -pub use request_template::RequestTemplate; -pub use response::*; - -pub use crate::app_context::AppContext; +pub static TAILCALL_HTTPS_ORIGIN: HeaderValue = HeaderValue::from_static("https://tailcall.run"); +pub static TAILCALL_HTTP_ORIGIN: HeaderValue = HeaderValue::from_static("http://tailcall.run"); diff --git a/src/http/request_context.rs b/src/http/request_context.rs index 158f52440c..7a3e8da519 100644 --- a/src/http/request_context.rs +++ b/src/http/request_context.rs @@ -15,6 +15,7 @@ use crate::graphql::GraphqlDataLoader; use crate::grpc; use crate::grpc::data_loader::GrpcDataLoader; use crate::http::{AppContext, DataLoaderRequest, HttpDataLoader}; +use crate::lambda::EvaluationError; use crate::runtime::TargetRuntime; #[derive(Setters)] @@ -33,7 +34,7 @@ pub struct RequestContext { pub min_max_age: Arc>>, pub cache_public: Arc>>, pub runtime: TargetRuntime, - pub cache: AsyncCache, + pub cache: AsyncCache, } impl RequestContext { diff --git a/src/http/request_handler.rs b/src/http/request_handler.rs index 36e6ce078c..c7a0e1a719 100644 --- a/src/http/request_handler.rs +++ b/src/http/request_handler.rs @@ -1,48 +1,28 @@ -use std::borrow::Cow; use std::collections::BTreeSet; use std::ops::Deref; use std::sync::Arc; use anyhow::Result; -use async_graphql::http::{playground_source, GraphQLPlaygroundConfig}; use async_graphql::ServerError; -use hyper::header::{self, CONTENT_TYPE}; +use hyper::header::{self, HeaderValue, CONTENT_TYPE}; use hyper::http::Method; use hyper::{Body, HeaderMap, Request, Response, StatusCode}; use opentelemetry::trace::SpanKind; use opentelemetry_semantic_conventions::trace::{HTTP_REQUEST_METHOD, HTTP_ROUTE}; -use prometheus::{Encoder, ProtobufEncoder, TextEncoder, PROTOBUF_FORMAT, TEXT_FORMAT}; +use prometheus::{Encoder, ProtobufEncoder, TextEncoder, TEXT_FORMAT}; use serde::de::DeserializeOwned; use tracing::Instrument; use tracing_opentelemetry::OpenTelemetrySpanExt; use super::request_context::RequestContext; use super::telemetry::{get_response_status_code, RequestCounter}; -use super::{showcase, telemetry, AppContext}; +use super::{showcase, telemetry, AppContext, TAILCALL_HTTPS_ORIGIN, TAILCALL_HTTP_ORIGIN}; use crate::async_graphql_hyper::{GraphQLRequestLike, GraphQLResponse}; use crate::blueprint::telemetry::TelemetryExporter; use crate::config::{PrometheusExporter, PrometheusFormat}; pub const API_URL_PREFIX: &str = "/api"; -pub fn graphiql(req: &Request) -> Result> { - let query = req.uri().query(); - let endpoint = "/graphql"; - let endpoint = if let Some(query) = query { - if query.is_empty() { - Cow::Borrowed(endpoint) - } else { - Cow::Owned(format!("{}?{}", endpoint, query)) - } - } else { - Cow::Borrowed(endpoint) - }; - - Ok(Response::new(Body::from(playground_source( - GraphQLPlaygroundConfig::new(&endpoint).title("Tailcall - GraphQL IDE"), - )))) -} - fn prometheus_metrics(prometheus_exporter: &PrometheusExporter) -> Result> { let metric_families = prometheus::default_registry().gather(); let mut buffer = vec![]; @@ -56,7 +36,7 @@ fn prometheus_metrics(prometheus_exporter: &PrometheusExporter) -> Result TEXT_FORMAT, - PrometheusFormat::Protobuf => PROTOBUF_FORMAT, + PrometheusFormat::Protobuf => prometheus::PROTOBUF_FORMAT, }; Ok(Response::builder() @@ -162,6 +142,38 @@ fn create_allowed_headers(headers: &HeaderMap, allowed: &BTreeSet) -> He new_headers } +async fn handle_origin_tailcall( + req: Request, + app_ctx: Arc, + request_counter: &mut RequestCounter, +) -> Result> { + let method = req.method(); + if method == Method::OPTIONS { + let mut res = Response::new(Body::default()); + res.headers_mut().insert( + header::ACCESS_CONTROL_ALLOW_ORIGIN, + HeaderValue::from_static("https://tailcall.run"), + ); + res.headers_mut().insert( + header::ACCESS_CONTROL_ALLOW_METHODS, + HeaderValue::from_static("GET, POST, OPTIONS"), + ); + res.headers_mut().insert( + header::ACCESS_CONTROL_ALLOW_HEADERS, + HeaderValue::from_static("*"), + ); + Ok(res) + } else { + let mut res = handle_request_inner::(req, app_ctx, request_counter).await?; + res.headers_mut().insert( + header::ACCESS_CONTROL_ALLOW_ORIGIN, + HeaderValue::from_static("https://tailcall.run"), + ); + + Ok(res) + } +} + async fn handle_request_with_cors( req: Request, app_ctx: Arc, @@ -288,10 +300,6 @@ async fn handle_request_inner( } }; - if app_ctx.blueprint.server.enable_graphiql { - return graphiql(&req); - } - not_found() } _ => not_found(), @@ -317,6 +325,12 @@ pub async fn handle_request( let response = if app_ctx.blueprint.server.cors.is_some() { handle_request_with_cors::(req, app_ctx, &mut req_counter).await + } else if let Some(origin) = req.headers().get(&header::ORIGIN) { + if origin == TAILCALL_HTTPS_ORIGIN || origin == TAILCALL_HTTP_ORIGIN { + handle_origin_tailcall::(req, app_ctx, &mut req_counter).await + } else { + handle_request_inner::(req, app_ctx, &mut req_counter).await + } } else { handle_request_inner::(req, app_ctx, &mut req_counter).await }; diff --git a/src/lambda/cache.rs b/src/lambda/cache.rs index 2e0829f587..de53392b82 100644 --- a/src/lambda/cache.rs +++ b/src/lambda/cache.rs @@ -3,10 +3,11 @@ use std::num::NonZeroU64; use std::ops::Deref; use std::pin::Pin; -use anyhow::Result; use async_graphql_value::ConstValue; -use super::{Concurrent, Eval, EvaluationContext, Expression, ResolverContextLike}; +use super::{ + Concurrent, Eval, EvaluationContext, EvaluationError, Expression, ResolverContextLike, +}; pub trait CacheKey { fn cache_key(&self, ctx: &Ctx) -> u64; @@ -39,7 +40,7 @@ impl Eval for Cache { &'a self, ctx: EvaluationContext<'a, Ctx>, conc: &'a Concurrent, - ) -> Pin> + 'a + Send>> { + ) -> Pin> + 'a + Send>> { Box::pin(async move { if let Expression::IO(io) = self.expr.deref() { let key = io.cache_key(&ctx); diff --git a/src/lambda/concurrent.rs b/src/lambda/concurrent.rs index 792cf43ce1..b0aad7c1c2 100644 --- a/src/lambda/concurrent.rs +++ b/src/lambda/concurrent.rs @@ -1,6 +1,8 @@ use futures_util::stream::FuturesUnordered; use futures_util::{Future, StreamExt}; +use super::EvaluationError; + /// /// Concurrent controls the concurrency of a fold or foreach operation on lists. /// It's a flag that is set based on operators that are applied on a list. @@ -19,7 +21,7 @@ impl Concurrent { iter: impl Iterator, acc: B, f: impl Fn(B, A) -> anyhow::Result, - ) -> anyhow::Result + ) -> Result where F: Future, { @@ -46,9 +48,9 @@ impl Concurrent { &self, iter: impl Iterator, f: impl Fn(A) -> B, - ) -> anyhow::Result> + ) -> Result, EvaluationError> where - F: Future>, + F: Future>, { self.fold(iter, vec![], |mut acc, val| { acc.push(f(val?)); diff --git a/src/lambda/eval.rs b/src/lambda/eval.rs index fe1aef7156..9c784d514f 100644 --- a/src/lambda/eval.rs +++ b/src/lambda/eval.rs @@ -1,9 +1,7 @@ use core::future::Future; use std::pin::Pin; -use anyhow::Result; - -use super::{Concurrent, EvaluationContext, ResolverContextLike}; +use super::{Concurrent, EvaluationContext, EvaluationError, ResolverContextLike}; pub trait Eval where @@ -13,7 +11,7 @@ where &'a self, ctx: EvaluationContext<'a, Ctx>, conc: &'a Concurrent, - ) -> Pin> + 'a + Send>> + ) -> Pin> + 'a + Send>> where Output: 'a; } diff --git a/src/lambda/expression.rs b/src/lambda/expression.rs index d15deb109f..7910565de2 100644 --- a/src/lambda/expression.rs +++ b/src/lambda/expression.rs @@ -3,12 +3,12 @@ use std::fmt::{Debug, Display}; use std::pin::Pin; use std::sync::Arc; -use anyhow::{anyhow, Result}; use async_graphql::ErrorExtensions; use async_graphql_value::ConstValue; use thiserror::Error; use super::{Concurrent, Eval, EvaluationContext, ResolverContextLike, IO}; +use crate::auth; use crate::blueprint::DynamicValue; use crate::json::JsonLike; use crate::lambda::cache::Cache; @@ -67,10 +67,19 @@ pub enum EvaluationError { #[error("APIValidationError: {0:?}")] APIValidationError(Vec), - #[error("ExprEvalError: {0:?}")] + #[error("ExprEvalError: {0}")] ExprEvalError(String), + + #[error("DeserializeError: {0}")] + DeserializeError(String), + + #[error("Authentication Failure: {0}")] + AuthError(auth::error::Error), } +// TODO: remove conversion from anyhow and don't use anyhow to pass errors +// since it loses potentially valuable information that could be later provided +// in the error extensions impl From for EvaluationError { fn from(value: anyhow::Error) -> Self { match value.downcast::() { @@ -99,19 +108,19 @@ impl ErrorExtensions for EvaluationError { grpc_status_details, } = self { - e.set("grpc_code", *grpc_code); - e.set("grpc_description", grpc_description); - e.set("grpc_status_message", grpc_status_message); - e.set("grpc_status_details", grpc_status_details.clone()); + e.set("grpcCode", *grpc_code); + e.set("grpcDescription", grpc_description); + e.set("grpcStatusMessage", grpc_status_message); + e.set("grpcStatusDetails", grpc_status_details.clone()); } }) } } impl<'a> From> for EvaluationError { - fn from(_value: crate::valid::ValidationError<&'a str>) -> Self { + fn from(value: crate::valid::ValidationError<&'a str>) -> Self { EvaluationError::APIValidationError( - _value + value .as_vec() .iter() .map(|e| e.message.to_owned()) @@ -120,6 +129,12 @@ impl<'a> From> for EvaluationError { } } +impl From for EvaluationError { + fn from(value: auth::error::Error) -> Self { + EvaluationError::AuthError(value) + } +} + impl Expression { pub fn and_then(self, next: Self) -> Self { Expression::Context(Context::PushArgs { expr: Box::new(self), and_then: Box::new(next) }) @@ -136,7 +151,7 @@ impl Eval for Expression { &'a self, ctx: EvaluationContext<'a, Ctx>, conc: &'a Concurrent, - ) -> Pin> + 'a + Send>> { + ) -> Pin> + 'a + Send>> { Box::pin(async move { match self { Expression::Context(op) => match op { @@ -165,14 +180,13 @@ impl Eval for Expression { .unwrap_or(&async_graphql::Value::Null) .clone()) } - Expression::Dynamic(value) => value.render_value(&ctx), + Expression::Dynamic(value) => Ok(value.render_value(&ctx)), Expression::Protect(expr) => { ctx.request_ctx .auth_ctx .validate(ctx.request_ctx) .await - .to_result() - .map_err(|e| anyhow!("Authentication Failure: {}", e.to_string()))?; + .to_result()?; expr.eval(ctx, conc).await } Expression::IO(operation) => operation.eval(ctx, conc).await, diff --git a/src/lambda/io.rs b/src/lambda/io.rs index 97d101b379..d896642933 100644 --- a/src/lambda/io.rs +++ b/src/lambda/io.rs @@ -2,7 +2,7 @@ use core::future::Future; use std::pin::Pin; use std::sync::Arc; -use anyhow::Result; +use async_graphql::from_value; use async_graphql_value::ConstValue; use reqwest::Request; @@ -49,21 +49,22 @@ impl Eval for IO { &'a self, ctx: super::EvaluationContext<'a, Ctx>, _conc: &'a super::Concurrent, - ) -> Pin> + 'a + Send>> { - let key = self.cache_key(&ctx); - Box::pin(async move { - ctx.request_ctx - .cache - .get_or_eval(key, move || { - Box::pin(async { - self.eval_inner(ctx, _conc) - .await - .map_err(|err| err.to_string()) + ) -> Pin> + 'a + Send>> { + if ctx.request_ctx.upstream.dedupe { + Box::pin(async move { + let key = self.cache_key(&ctx); + ctx.request_ctx + .cache + .get_or_eval(key, move || { + Box::pin(async { self.eval_inner(ctx, _conc).await }) }) - }) - .await - .map_err(|err| anyhow::anyhow!(err)) - }) + .await + .as_ref() + .clone() + }) + } else { + Box::pin(self.eval_inner(ctx, _conc)) + } } } @@ -72,7 +73,7 @@ impl IO { &'a self, ctx: super::EvaluationContext<'a, Ctx>, _conc: &'a super::Concurrent, - ) -> Pin> + 'a + Send>> { + ) -> Pin> + 'a + Send>> { Box::pin(async move { match self { IO::Http { req_template, dl_id, .. } => { @@ -190,7 +191,7 @@ fn set_cookie_headers<'ctx, Ctx: ResolverContextLike<'ctx>>( async fn execute_raw_request<'ctx, Ctx: ResolverContextLike<'ctx>>( ctx: &EvaluationContext<'ctx, Ctx>, req: Request, -) -> Result> { +) -> Result, EvaluationError> { let response = ctx .request_ctx .runtime @@ -207,12 +208,10 @@ async fn execute_raw_grpc_request<'ctx, Ctx: ResolverContextLike<'ctx>>( ctx: &EvaluationContext<'ctx, Ctx>, req: Request, operation: &ProtobufOperation, -) -> Result> { - Ok( - execute_grpc_request(&ctx.request_ctx.runtime, operation, req) - .await - .map_err(EvaluationError::from)?, - ) +) -> Result, EvaluationError> { + execute_grpc_request(&ctx.request_ctx.runtime, operation, req) + .await + .map_err(EvaluationError::from) } async fn execute_grpc_request_with_dl< @@ -227,7 +226,7 @@ async fn execute_grpc_request_with_dl< ctx: &EvaluationContext<'ctx, Ctx>, rendered: RenderedRequestTemplate, data_loader: Option<&DataLoader>, -) -> Result> { +) -> Result, EvaluationError> { let headers = ctx .request_ctx .upstream @@ -253,7 +252,7 @@ async fn execute_request_with_dl< ctx: &EvaluationContext<'ctx, Ctx>, req: Request, data_loader: Option<&DataLoader>, -) -> Result> { +) -> Result, EvaluationError> { let headers = ctx .request_ctx .upstream @@ -275,8 +274,9 @@ fn parse_graphql_response<'ctx, Ctx: ResolverContextLike<'ctx>>( ctx: &EvaluationContext<'ctx, Ctx>, res: Response, field_name: &str, -) -> Result { - let res: async_graphql::Response = serde_json::from_value(res.body.into_json()?)?; +) -> Result { + let res: async_graphql::Response = + from_value(res.body).map_err(|err| EvaluationError::DeserializeError(err.to_string()))?; for error in res.errors { ctx.add_error(error); diff --git a/src/lambda/list.rs b/src/lambda/list.rs deleted file mode 100644 index 8390b9e367..0000000000 --- a/src/lambda/list.rs +++ /dev/null @@ -1,76 +0,0 @@ -use core::future::Future; -use std::pin::Pin; - -use anyhow::Result; -use async_graphql_value::ConstValue; -use futures_util::future::join_all; - -use super::{ - Concurrent, Eval, EvaluationContext, EvaluationError, Expression, ResolverContextLike, -}; - -#[derive(Clone, Debug, strum_macros::Display)] -pub enum List { - Concat(Vec), -} - -impl Eval for List { - fn eval<'a, Ctx: ResolverContextLike<'a> + Sync + Send>( - &'a self, - ctx: EvaluationContext<'a, Ctx>, - conc: &'a Concurrent, - ) -> Pin> + 'a + Send>> { - Box::pin(async move { - match self { - List::Concat(list) => { - join_all(list.iter().map(|expr| expr.eval(ctx.clone(), conc))) - .await - .into_iter() - .try_fold(async_graphql::Value::List(vec![]), |acc, result| { - match (acc, result?) { - (ConstValue::List(mut lhs), ConstValue::List(rhs)) => { - lhs.extend(rhs); - Ok(ConstValue::List(lhs)) - } - _ => Err(EvaluationError::ExprEvalError( - "element is not a list".into(), - ))?, - } - }) - } - } - }) - } -} - -impl Eval for T -where - T: AsRef<[Expression]> + Send + Sync, - C: FromIterator, -{ - fn eval<'a, Ctx: ResolverContextLike<'a> + Sync + Send>( - &'a self, - ctx: EvaluationContext<'a, Ctx>, - conc: &'a Concurrent, - ) -> Pin> + 'a + Send>> { - Box::pin(async move { - let future_iter = self - .as_ref() - .iter() - .map(|expr| expr.eval(ctx.clone(), conc)); - match *conc { - Concurrent::Parallel => join_all(future_iter) - .await - .into_iter() - .collect::>(), - Concurrent::Sequential => { - let mut results = Vec::with_capacity(self.as_ref().len()); - for future in future_iter { - results.push(future.await?); - } - Ok(results.into_iter().collect()) - } - } - }) - } -} diff --git a/src/lambda/logic.rs b/src/lambda/logic.rs deleted file mode 100644 index 6552f492e1..0000000000 --- a/src/lambda/logic.rs +++ /dev/null @@ -1,141 +0,0 @@ -use core::future::Future; -use std::pin::Pin; - -use anyhow::Result; -use async_graphql_value::ConstValue; - -use super::{Concurrent, Eval, EvaluationContext, Expression, ResolverContextLike}; - -#[derive(Clone, Debug, strum_macros::Display)] -pub enum Logic { - If { - cond: Box, - then: Box, - els: Box, - }, - And(Vec), - Or(Vec), - Cond(Vec<(Box, Box)>), - DefaultTo(Box, Box), - IsEmpty(Box), - Not(Box), -} - -impl Eval for Logic { - fn eval<'a, Ctx: ResolverContextLike<'a> + Sync + Send>( - &'a self, - ctx: EvaluationContext<'a, Ctx>, - conc: &'a Concurrent, - ) -> Pin> + 'a + Send>> { - Box::pin(async move { - Ok(match self { - Logic::Or(list) => { - let future_iter = list.iter().map(|expr| { - let ctx = ctx.clone(); - async move { expr.eval(ctx.clone(), conc).await } - }); - - conc.fold(future_iter, false, |acc, val| Ok(acc || is_truthy(&val?))) - .await - .map(ConstValue::from)? - } - Logic::Cond(list) => { - for (cond, expr) in list.iter() { - if is_truthy(&cond.eval(ctx.clone(), conc).await?) { - return expr.eval(ctx, conc).await; - } - } - ConstValue::Null - } - Logic::DefaultTo(value, default) => { - let result = value.eval(ctx.clone(), conc).await?; - if is_empty(&result) { - default.eval(ctx, conc).await? - } else { - result - } - } - Logic::IsEmpty(expr) => is_empty(&expr.eval(ctx, conc).await?).into(), - Logic::Not(expr) => (!is_truthy(&expr.eval(ctx, conc).await?)).into(), - - Logic::And(list) => { - let future_iter = list.iter().map(|expr| { - let ctx = ctx.clone(); - async move { expr.eval(ctx, conc).await } - }); - - conc.fold(future_iter, true, |acc, val| Ok(acc && is_truthy(&val?))) - .await - .map(ConstValue::from)? - } - Logic::If { cond, then, els } => { - let cond = cond.eval(ctx.clone(), conc).await?; - if is_truthy(&cond) { - then.eval(ctx, conc).await? - } else { - els.eval(ctx, conc).await? - } - } - }) - }) - } -} - -/// Check if a value is truthy -/// -/// Special cases: -/// 1. An empty string is considered falsy -/// 2. A collection of bytes is truthy, even if the value in those bytes is 0. -/// An empty collection is falsy. -pub fn is_truthy(value: &async_graphql::Value) -> bool { - use async_graphql::{Number, Value}; - use hyper::body::Bytes; - - match value { - &Value::Null => false, - &Value::Enum(_) => true, - &Value::List(_) => true, - &Value::Object(_) => true, - Value::String(s) => !s.is_empty(), - &Value::Boolean(b) => b, - Value::Number(n) => n != &Number::from(0), - Value::Binary(b) => b != &Bytes::default(), - } -} - -fn is_empty(value: &async_graphql::Value) -> bool { - match value { - ConstValue::Null => true, - ConstValue::Number(_) | ConstValue::Boolean(_) | ConstValue::Enum(_) => false, - ConstValue::Binary(bytes) => bytes.is_empty(), - ConstValue::List(list) => list.is_empty(), - ConstValue::Object(obj) => obj.is_empty(), - ConstValue::String(string) => string.is_empty(), - } -} - -#[cfg(test)] -mod tests { - use async_graphql::{Name, Number, Value}; - use hyper::body::Bytes; - use indexmap::IndexMap; - - use crate::lambda::is_truthy; - - #[test] - fn test_is_truthy() { - assert!(is_truthy(&Value::Enum(Name::new("EXAMPLE")))); - assert!(is_truthy(&Value::List(vec![]))); - assert!(is_truthy(&Value::Object(IndexMap::default()))); - assert!(is_truthy(&Value::String("Hello".to_string()))); - assert!(is_truthy(&Value::Boolean(true))); - assert!(is_truthy(&Value::Number(Number::from(1)))); - assert!(is_truthy(&Value::Binary(Bytes::from_static(&[0, 1, 2])))); - - assert!(!is_truthy(&Value::Null)); - assert!(!is_truthy(&Value::String("".to_string()))); - assert!(!is_truthy(&Value::Boolean(false))); - assert!(!is_truthy(&Value::Number(Number::from(0)))); - assert!(!is_truthy(&Value::Binary(Bytes::default()))); - } -} diff --git a/src/lambda/math.rs b/src/lambda/math.rs deleted file mode 100644 index 6f7f88319a..0000000000 --- a/src/lambda/math.rs +++ /dev/null @@ -1,167 +0,0 @@ -use core::future::Future; -use std::ops; -use std::pin::Pin; - -use anyhow::Result; -use async_graphql_value::ConstValue; - -use super::{ - Concurrent, Eval, EvaluationContext, EvaluationError, Expression, ResolverContextLike, -}; -use crate::json::JsonLike; - -#[derive(Clone, Debug, strum_macros::Display)] -pub enum Math { - Mod(Box, Box), - Add(Box, Box), - Dec(Box), - Divide(Box, Box), - Inc(Box), - Multiply(Box, Box), - Negate(Box), - Product(Vec), - Subtract(Box, Box), - Sum(Vec), -} - -impl Eval for Math { - fn eval<'a, Ctx: ResolverContextLike<'a> + Sync + Send>( - &'a self, - ctx: EvaluationContext<'a, Ctx>, - conc: &'a Concurrent, - ) -> Pin> + 'a + Send>> { - Box::pin(async move { - Ok(match self { - Math::Mod(lhs, rhs) => { - let lhs = lhs.eval(ctx.clone(), conc).await?; - let rhs = rhs.eval(ctx, conc).await?; - - try_i64_operation(&lhs, &rhs, ops::Rem::rem) - .or_else(|| try_u64_operation(&lhs, &rhs, ops::Rem::rem)) - .ok_or(EvaluationError::ExprEvalError("mod".into()))? - } - Math::Add(lhs, rhs) => { - let lhs = lhs.eval(ctx.clone(), conc).await?; - let rhs = rhs.eval(ctx, conc).await?; - - try_f64_operation(&lhs, &rhs, ops::Add::add) - .or_else(|| try_u64_operation(&lhs, &rhs, ops::Add::add)) - .or_else(|| try_i64_operation(&lhs, &rhs, ops::Add::add)) - .ok_or(EvaluationError::ExprEvalError("add".into()))? - } - Math::Dec(val) => { - let val = val.eval(ctx, conc).await?; - - val.as_f64_ok() - .ok() - .map(|val| (val - 1f64).into()) - .or_else(|| val.as_u64_ok().ok().map(|val| (val - 1u64).into())) - .or_else(|| val.as_i64_ok().ok().map(|val| (val - 1i64).into())) - .ok_or(EvaluationError::ExprEvalError("dec".into()))? - } - Math::Divide(lhs, rhs) => { - let lhs = lhs.eval(ctx.clone(), conc).await?; - let rhs = rhs.eval(ctx, conc).await?; - - try_f64_operation(&lhs, &rhs, ops::Div::div) - .or_else(|| try_u64_operation(&lhs, &rhs, ops::Div::div)) - .or_else(|| try_i64_operation(&lhs, &rhs, ops::Div::div)) - .ok_or(EvaluationError::ExprEvalError("divide".into()))? - } - Math::Inc(val) => { - let val = val.eval(ctx, conc).await?; - - val.as_f64_ok() - .ok() - .map(|val| (val + 1f64).into()) - .or_else(|| val.as_u64_ok().ok().map(|val| (val + 1u64).into())) - .or_else(|| val.as_i64_ok().ok().map(|val| (val + 1i64).into())) - .ok_or(EvaluationError::ExprEvalError("dec".into()))? - } - Math::Multiply(lhs, rhs) => { - let lhs = lhs.eval(ctx.clone(), conc).await?; - let rhs = rhs.eval(ctx, conc).await?; - - try_f64_operation(&lhs, &rhs, ops::Mul::mul) - .or_else(|| try_u64_operation(&lhs, &rhs, ops::Mul::mul)) - .or_else(|| try_i64_operation(&lhs, &rhs, ops::Mul::mul)) - .ok_or(EvaluationError::ExprEvalError("multiply".into()))? - } - Math::Negate(val) => { - let val = val.eval(ctx, conc).await?; - - val.as_f64_ok() - .ok() - .map(|val| (-val).into()) - .or_else(|| val.as_i64_ok().ok().map(|val| (-val).into())) - .ok_or(EvaluationError::ExprEvalError("neg".into()))? - } - Math::Product(exprs) => { - let results: Vec<_> = exprs.eval(ctx, conc).await?; - - results.into_iter().try_fold(1i64.into(), |lhs, rhs| { - try_f64_operation(&lhs, &rhs, ops::Mul::mul) - .or_else(|| try_u64_operation(&lhs, &rhs, ops::Mul::mul)) - .or_else(|| try_i64_operation(&lhs, &rhs, ops::Mul::mul)) - .ok_or(EvaluationError::ExprEvalError("product".into())) - })? - } - Math::Subtract(lhs, rhs) => { - let lhs = lhs.eval(ctx.clone(), conc).await?; - let rhs = rhs.eval(ctx, conc).await?; - - try_f64_operation(&lhs, &rhs, ops::Sub::sub) - .or_else(|| try_u64_operation(&lhs, &rhs, ops::Sub::sub)) - .or_else(|| try_i64_operation(&lhs, &rhs, ops::Sub::sub)) - .ok_or(EvaluationError::ExprEvalError("subtract".into()))? - } - Math::Sum(exprs) => { - let results: Vec<_> = exprs.eval(ctx, conc).await?; - - results.into_iter().try_fold(0i64.into(), |lhs, rhs| { - try_f64_operation(&lhs, &rhs, ops::Add::add) - .or_else(|| try_u64_operation(&lhs, &rhs, ops::Add::add)) - .or_else(|| try_i64_operation(&lhs, &rhs, ops::Add::add)) - .ok_or(EvaluationError::ExprEvalError("sum".into())) - })? - } - }) - }) - } -} - -fn try_f64_operation(lhs: &ConstValue, rhs: &ConstValue, f: F) -> Option -where - F: Fn(f64, f64) -> f64, -{ - match (lhs, rhs) { - (ConstValue::Number(lhs), ConstValue::Number(rhs)) => lhs - .as_f64() - .and_then(|lhs| rhs.as_f64().map(|rhs| f(lhs, rhs).into())), - _ => None, - } -} - -fn try_i64_operation(lhs: &ConstValue, rhs: &ConstValue, f: F) -> Option -where - F: Fn(i64, i64) -> i64, -{ - match (lhs, rhs) { - (ConstValue::Number(lhs), ConstValue::Number(rhs)) => lhs - .as_i64() - .and_then(|lhs| rhs.as_i64().map(|rhs| f(lhs, rhs).into())), - _ => None, - } -} - -fn try_u64_operation(lhs: &ConstValue, rhs: &ConstValue, f: F) -> Option -where - F: Fn(u64, u64) -> u64, -{ - match (lhs, rhs) { - (ConstValue::Number(lhs), ConstValue::Number(rhs)) => lhs - .as_u64() - .and_then(|lhs| rhs.as_u64().map(|rhs| f(lhs, rhs).into())), - _ => None, - } -} diff --git a/src/lambda/mod.rs b/src/lambda/mod.rs index 529dac1010..ee0cc3d8b1 100644 --- a/src/lambda/mod.rs +++ b/src/lambda/mod.rs @@ -5,11 +5,7 @@ mod evaluation_context; mod expression; mod graphql_operation_context; mod io; -mod list; -mod logic; -mod math; mod modify; -mod relation; mod resolver_context_like; pub use cache::*; @@ -19,8 +15,4 @@ pub use evaluation_context::EvaluationContext; pub use expression::*; pub use graphql_operation_context::GraphQLOperationContext; pub use io::*; -pub use list::*; -pub use logic::*; -pub use math::*; -pub use relation::*; pub use resolver_context_like::{EmptyResolverContext, ResolverContext, ResolverContextLike}; diff --git a/src/lambda/relation.rs b/src/lambda/relation.rs deleted file mode 100644 index 313bce77f2..0000000000 --- a/src/lambda/relation.rs +++ /dev/null @@ -1,336 +0,0 @@ -use core::future::Future; -use std::cmp::Ordering; -use std::collections::HashSet; -use std::pin::Pin; - -use anyhow::Result; -use async_graphql_value::ConstValue; -use futures_util::future::join_all; - -use super::{ - Concurrent, Eval, EvaluationContext, EvaluationError, Expression, ResolverContextLike, -}; -use crate::helpers::value::HashableConstValue; - -#[derive(Clone, Debug, strum_macros::Display)] -pub enum Relation { - Intersection(Vec), - Difference(Vec, Vec), - Equals(Box, Box), - Gt(Box, Box), - Gte(Box, Box), - Lt(Box, Box), - Lte(Box, Box), - Max(Vec), - Min(Vec), - PathEq(Box, Vec, Box), - PropEq(Box, String, Box), - SortPath(Box, Vec), - SymmetricDifference(Vec, Vec), - Union(Vec, Vec), -} - -impl Eval for Relation { - fn eval<'a, Ctx: ResolverContextLike<'a> + Sync + Send>( - &'a self, - ctx: EvaluationContext<'a, Ctx>, - conc: &'a Concurrent, - ) -> Pin> + 'a + Send>> { - Box::pin(async move { - Ok(match self { - Relation::Intersection(exprs) => { - let results = - join_all(exprs.iter().map(|expr| expr.eval(ctx.clone(), conc))).await; - - let mut results_iter = results.into_iter(); - - let set: HashSet<_> = match results_iter.next() { - Some(first) => match first? { - ConstValue::List(list) => { - list.into_iter().map(HashableConstValue).collect() - } - _ => Err(EvaluationError::ExprEvalError( - "element is not a list".into(), - ))?, - }, - None => Err(EvaluationError::ExprEvalError( - "element is not a list".into(), - ))?, - }; - - let final_set = - results_iter.try_fold(set, |mut acc, result| match result? { - ConstValue::List(list) => { - let set: HashSet<_> = - list.into_iter().map(HashableConstValue).collect(); - acc = acc.intersection(&set).cloned().collect(); - Ok::<_, anyhow::Error>(acc) - } - _ => Err(EvaluationError::ExprEvalError( - "element is not a list".into(), - ))?, - })?; - - final_set - .into_iter() - .map(|HashableConstValue(const_value)| const_value) - .collect() - } - Relation::Difference(lhs, rhs) => { - set_operation(ctx, conc, lhs, rhs, |lhs, rhs| { - lhs.difference(&rhs) - .cloned() - .map(|HashableConstValue(const_value)| const_value) - .collect() - }) - .await? - } - Relation::Equals(lhs, rhs) => { - (lhs.eval(ctx.clone(), conc).await? == rhs.eval(ctx, conc).await?).into() - } - Relation::Gt(lhs, rhs) => { - let lhs = lhs.eval(ctx.clone(), conc).await?; - let rhs = rhs.eval(ctx, conc).await?; - - (compare(&lhs, &rhs) == Some(Ordering::Greater)).into() - } - Relation::Gte(lhs, rhs) => { - let lhs = lhs.eval(ctx.clone(), conc).await?; - let rhs = rhs.eval(ctx, conc).await?; - - matches!( - compare(&lhs, &rhs), - Some(Ordering::Greater) | Some(Ordering::Equal) - ) - .into() - } - Relation::Lt(lhs, rhs) => { - let lhs = lhs.eval(ctx.clone(), conc).await?; - let rhs = rhs.eval(ctx, conc).await?; - - (compare(&lhs, &rhs) == Some(Ordering::Less)).into() - } - Relation::Lte(lhs, rhs) => { - let lhs = lhs.eval(ctx.clone(), conc).await?; - let rhs = rhs.eval(ctx, conc).await?; - - matches!( - compare(&lhs, &rhs), - Some(Ordering::Less) | Some(Ordering::Equal) - ) - .into() - } - Relation::Max(exprs) => { - let mut results: Vec<_> = exprs.eval(ctx, conc).await?; - - let last = results.pop().ok_or(EvaluationError::ExprEvalError( - "`max` cannot be called on empty list".into(), - ))?; - - results.into_iter().try_fold(last, |mut largest, current| { - let ord = compare(&largest, ¤t); - largest = match ord { - Some(Ordering::Greater | Ordering::Equal) => largest, - Some(Ordering::Less) => current, - _ => Err(anyhow::anyhow!( - "`max` cannot be calculated for types that cannot be compared" - ))?, - }; - Ok::<_, anyhow::Error>(largest) - })? - } - Relation::Min(exprs) => { - let mut results: Vec<_> = exprs.eval(ctx, conc).await?; - - let last = results.pop().ok_or(EvaluationError::ExprEvalError( - "`min` cannot be called on empty list".into(), - ))?; - - results.into_iter().try_fold(last, |mut largest, current| { - let ord = compare(&largest, ¤t); - largest = match ord { - Some(Ordering::Less | Ordering::Equal) => largest, - Some(Ordering::Greater) => current, - _ => Err(anyhow::anyhow!( - "`min` cannot be calculated for types that cannot be compared" - ))?, - }; - Ok::<_, anyhow::Error>(largest) - })? - } - Relation::PathEq(lhs, path, rhs) => { - let lhs = lhs.eval(ctx.clone(), conc).await?; - let lhs = get_path_for_const_value_owned(path, lhs) - .ok_or(anyhow::anyhow!("Could not find path: {path:?}"))?; - - let rhs = rhs.eval(ctx, conc).await?; - let rhs = get_path_for_const_value_owned(path, rhs) - .ok_or(anyhow::anyhow!("Could not find path: {path:?}"))?; - - (lhs == rhs).into() - } - Relation::PropEq(lhs, prop, rhs) => { - let lhs = lhs.eval(ctx.clone(), conc).await?; - let lhs = get_path_for_const_value_owned(&[prop], lhs) - .ok_or(anyhow::anyhow!("Could not find path: {prop:?}"))?; - - let rhs = rhs.eval(ctx, conc).await?; - let rhs = get_path_for_const_value_owned(&[prop], rhs) - .ok_or(anyhow::anyhow!("Could not find path: {prop:?}"))?; - - (lhs == rhs).into() - } - Relation::SortPath(expr, path) => { - let value = expr.eval(ctx, conc).await?; - let values = match value { - ConstValue::List(list) => list, - _ => Err(EvaluationError::ExprEvalError( - "`sortPath` can only be applied to expressions that return list".into(), - ))?, - }; - - let is_comparable = is_list_comparable(&values); - let mut values: Vec<_> = values.into_iter().enumerate().collect(); - - if !is_comparable { - Err(anyhow::anyhow!( - "sortPath requires a list of comparable types" - ))? - } - - let value_paths: Vec<_> = values - .iter() - .filter_map(|(_, val)| get_path_for_const_value_ref(path, val)) - .cloned() - .collect(); - - if values.len() != value_paths.len() { - Err(anyhow::anyhow!( - "path is not valid for all the element in the list: {value_paths:?}" - ))? - } - - values.sort_by(|(index1, _), (index2, _)| { - compare(&value_paths[*index1], &value_paths[*index2]).unwrap() - }); - - values - .into_iter() - .map(|(_, val)| val) - .collect::>() - .into() - } - Relation::SymmetricDifference(lhs, rhs) => { - set_operation(ctx, conc, lhs, rhs, |lhs, rhs| { - lhs.symmetric_difference(&rhs) - .cloned() - .map(|HashableConstValue(const_value)| const_value) - .collect() - }) - .await? - } - Relation::Union(lhs, rhs) => { - set_operation(ctx, conc, lhs, rhs, |lhs, rhs| { - lhs.union(&rhs) - .cloned() - .map(|HashableConstValue(const_value)| const_value) - .collect() - }) - .await? - } - }) - }) - } -} - -fn is_list_comparable(list: &[ConstValue]) -> bool { - list.iter() - .zip(list.iter().skip(1)) - .all(|(lhs, rhs)| is_pair_comparable(lhs, rhs)) -} - -fn compare(lhs: &ConstValue, rhs: &ConstValue) -> Option { - Some(match (lhs, rhs) { - (ConstValue::Null, ConstValue::Null) => Ordering::Equal, - (ConstValue::Boolean(lhs), ConstValue::Boolean(rhs)) => lhs.partial_cmp(rhs)?, - (ConstValue::Enum(lhs), ConstValue::Enum(rhs)) => lhs.partial_cmp(rhs)?, - (ConstValue::Number(lhs), ConstValue::Number(rhs)) => lhs - .as_f64() - .partial_cmp(&rhs.as_f64()) - .or(lhs.as_i64().partial_cmp(&rhs.as_i64())) - .or(lhs.as_u64().partial_cmp(&rhs.as_u64()))?, - (ConstValue::Binary(lhs), ConstValue::Binary(rhs)) => lhs.partial_cmp(rhs)?, - (ConstValue::String(lhs), ConstValue::String(rhs)) => lhs.partial_cmp(rhs)?, - (ConstValue::List(lhs), ConstValue::List(rhs)) => lhs - .iter() - .zip(rhs.iter()) - .find_map(|(lhs, rhs)| compare(lhs, rhs).filter(|ord| ord != &Ordering::Equal)) - .unwrap_or(lhs.len().partial_cmp(&rhs.len())?), - _ => None?, - }) -} - -fn is_pair_comparable(lhs: &ConstValue, rhs: &ConstValue) -> bool { - matches!( - (lhs, rhs), - (ConstValue::Null, ConstValue::Null) - | (ConstValue::Boolean(_), ConstValue::Boolean(_)) - | (ConstValue::Enum(_), ConstValue::Enum(_)) - | (ConstValue::Number(_), ConstValue::Number(_)) - | (ConstValue::Binary(_), ConstValue::Binary(_)) - | (ConstValue::String(_), ConstValue::String(_)) - | (ConstValue::List(_), ConstValue::List(_)) - ) -} - -#[allow(clippy::too_many_arguments)] -async fn set_operation<'a, 'b, Ctx: ResolverContextLike<'a> + Sync + Send, F>( - ctx: EvaluationContext<'a, Ctx>, - conc: &'a Concurrent, - lhs: &'a [Expression], - rhs: &'a [Expression], - operation: F, -) -> Result -where - F: Fn(HashSet, HashSet) -> Vec, -{ - let (lhs, rhs) = futures_util::join!( - conc.foreach( - lhs.iter().map(|e| e.eval(ctx.clone(), conc)), - HashableConstValue - ), - conc.foreach( - rhs.iter().map(|e| e.eval(ctx.clone(), conc)), - HashableConstValue - ) - ); - Ok(operation(HashSet::from_iter(lhs?), HashSet::from_iter(rhs?)).into()) -} - -fn get_path_for_const_value_ref<'a>( - path: &[impl AsRef], - mut const_value: &'a ConstValue, -) -> Option<&'a ConstValue> { - for path in path.iter() { - const_value = match const_value { - ConstValue::Object(ref obj) => obj.get(path.as_ref())?, - _ => None?, - } - } - - Some(const_value) -} - -fn get_path_for_const_value_owned( - path: &[impl AsRef], - mut const_value: ConstValue, -) -> Option { - for path in path.iter() { - const_value = match const_value { - ConstValue::Object(mut obj) => obj.swap_remove(path.as_ref())?, - _ => None?, - } - } - - Some(const_value) -} diff --git a/src/serde_value_ext.rs b/src/serde_value_ext.rs index 1b0f46f6de..9751b70f48 100644 --- a/src/serde_value_ext.rs +++ b/src/serde_value_ext.rs @@ -1,6 +1,5 @@ use std::borrow::Cow; -use anyhow::Result; use async_graphql::{Name, Value as GraphQLValue}; use indexmap::IndexMap; @@ -8,13 +7,13 @@ use crate::blueprint::DynamicValue; use crate::path::PathString; pub trait ValueExt { - fn render_value(&self, ctx: &impl PathString) -> Result; + fn render_value(&self, ctx: &impl PathString) -> GraphQLValue; } impl ValueExt for DynamicValue { - fn render_value<'a>(&self, ctx: &'a impl PathString) -> Result { + fn render_value<'a>(&self, ctx: &'a impl PathString) -> GraphQLValue { match self { - DynamicValue::Value(value) => Ok(value.to_owned()), + DynamicValue::Value(value) => value.to_owned(), DynamicValue::Mustache(m) => { let rendered: Cow<'a, str> = Cow::Owned(m.render(ctx)); @@ -22,21 +21,24 @@ impl ValueExt for DynamicValue { // parsing can fail when Mustache::render returns bare string and since // that string is not wrapped with quotes serde_json will fail to parse it // but, we can just use that string as is - .or_else(|_| Ok(GraphQLValue::String(rendered.into_owned()))) + .unwrap_or_else(|_| GraphQLValue::String(rendered.into_owned())) } DynamicValue::Object(obj) => { - let out: Result> = obj + let out: IndexMap<_, _> = obj .iter() .map(|(k, v)| { let key = Cow::Borrowed(k.as_str()); - v.render_value(ctx).map(|val| (Name::new(&key), val)) + let val = v.render_value(ctx); + + (Name::new(key), val) }) .collect(); - out.map(GraphQLValue::Object) + + GraphQLValue::Object(out) } DynamicValue::Array(arr) => { - let out: Result> = arr.iter().map(|v| v.render_value(ctx)).collect(); - out.map(GraphQLValue::List) + let out: Vec<_> = arr.iter().map(|v| v.render_value(ctx)).collect(); + GraphQLValue::List(out) } } } @@ -56,7 +58,7 @@ mod tests { let ctx = json!({"foo": {"bar": "baz"}}); let result = value.render_value(&ctx); let expected = async_graphql::Value::from_json(json!({"a": {"bar": "baz"}})).unwrap(); - assert_eq!(result.unwrap(), expected); + assert_eq!(result, expected); } #[test] @@ -66,7 +68,7 @@ mod tests { let ctx = json!({"foo": {"bar": {"baz": 1}}}); let result = value.render_value(&ctx); let expected = async_graphql::Value::from_json(json!({"a": 1})).unwrap(); - assert_eq!(result.unwrap(), expected); + assert_eq!(result, expected); } #[test] @@ -76,7 +78,7 @@ mod tests { let ctx = json!({"foo": {"bar": {"baz": "foo"}}}); let result = value.render_value(&ctx); let expected = async_graphql::Value::from_json(json!({"a": "foo"})).unwrap(); - assert_eq!(result.unwrap(), expected); + assert_eq!(result, expected); } #[test] @@ -86,7 +88,7 @@ mod tests { let ctx = json!({"foo": {"bar": {"baz": null}}}); let result = value.render_value(&ctx); let expected = async_graphql::Value::from_json(json!(null)).unwrap(); - assert_eq!(result.unwrap(), expected); + assert_eq!(result, expected); } #[test] @@ -96,7 +98,7 @@ mod tests { let ctx = json!({"foo": {"bar": {"baz": true}}}); let result = value.render_value(&ctx); let expected = async_graphql::Value::from_json(json!({"a": true})).unwrap(); - assert_eq!(result.unwrap(), expected); + assert_eq!(result, expected); } #[test] @@ -106,7 +108,7 @@ mod tests { let ctx = json!({"foo": {"bar": {"baz": 1.1}}}); let result = value.render_value(&ctx); let expected = async_graphql::Value::from_json(json!({"a": 1.1})).unwrap(); - assert_eq!(result.unwrap(), expected); + assert_eq!(result, expected); } #[test] @@ -116,7 +118,7 @@ mod tests { let ctx = json!({"foo": {"bar": {"baz": [1,2,3]}}}); let result = value.render_value(&ctx); let expected = async_graphql::Value::from_json(json!({"a": [1, 2, 3]})).unwrap(); - assert_eq!(result.unwrap(), expected); + assert_eq!(result, expected); } #[test] @@ -126,7 +128,7 @@ mod tests { let ctx = json!({"foo": {"bar": {"baz": 1, "qux": 2}}}); let result = value.render_value(&ctx); let expected = async_graphql::Value::from_json(json!({"a": [1, 2]})).unwrap(); - assert_eq!(result.unwrap(), expected); + assert_eq!(result, expected); } #[test] @@ -134,7 +136,7 @@ mod tests { let value = json!("{{foo}}"); let value = DynamicValue::try_from(&value).unwrap(); let ctx = json!({"foo": "bar"}); - let result = value.render_value(&ctx).unwrap(); + let result = value.render_value(&ctx); let expected = async_graphql::Value::String("bar".to_owned()); assert_eq!(result, expected); } @@ -146,7 +148,7 @@ mod tests { let ctx = json!({"foo": {"bar": {"baz": 1, "qux": 2}}}); let result = value.render_value(&ctx); let expected = async_graphql::Value::from_json(json!([{"a": 1}, {"a":2}])).unwrap(); - assert_eq!(result.unwrap(), expected); + assert_eq!(result, expected); } #[test] @@ -158,6 +160,6 @@ mod tests { let expected = async_graphql::Value::from_json(json!([{"a": [{"aa": 1}]}, {"a":[{"aa": 2}]}])) .unwrap(); - assert_eq!(result.unwrap(), expected); + assert_eq!(result, expected); } } diff --git a/tailcall-autogen/Cargo.toml b/tailcall-autogen/Cargo.toml index 826bfe08db..33f9c0665a 100644 --- a/tailcall-autogen/Cargo.toml +++ b/tailcall-autogen/Cargo.toml @@ -10,7 +10,7 @@ edition = "2021" anyhow = "1.0.82" lazy_static = "1.4.0" schemars = "0.8.17" -serde = "1.0.199" +serde = "1.0.200" serde_json = "1.0.116" tailcall = { path = "../" } tokio = { version = "1.37.0", features = ["full"] } diff --git a/tailcall-aws-lambda/Cargo.toml b/tailcall-aws-lambda/Cargo.toml index b42f167c0c..811139f933 100644 --- a/tailcall-aws-lambda/Cargo.toml +++ b/tailcall-aws-lambda/Cargo.toml @@ -18,8 +18,8 @@ edition = "2021" lambda_http = "0.11.1" lambda_runtime = "0.11.1" tokio = { version = "1", features = ["macros", "fs"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } +tracing = { version = "0.1.40", features = ["log"] } +tracing-subscriber = { version = "0.3.18", default-features = false, features = ["fmt"] } dotenvy = "0.15.7" anyhow = "1.0.82" diff --git a/tailcall-cloudflare/package-lock.json b/tailcall-cloudflare/package-lock.json index 03161d2c67..93c92ecc3f 100644 --- a/tailcall-cloudflare/package-lock.json +++ b/tailcall-cloudflare/package-lock.json @@ -107,9 +107,9 @@ } }, "node_modules/@cloudflare/workers-types": { - "version": "4.20240423.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20240423.0.tgz", - "integrity": "sha512-ssuccb3j+URp6mP2p0PcQE9vmS3YeKBQnALHF9P3yQfUAFozuhTsDTbqmL+zPrJvUcG7SL2xVQkNDF9QJeKDZw==", + "version": "4.20240502.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20240502.0.tgz", + "integrity": "sha512-OB1jIyPOzyOcuZFHWhsQnkRLN6u8+jmU9X3T4KZlGgn3Ivw8pBiswhLOp+yFeChR3Y4/5+V0hPFRko5SReordg==", "dev": true }, "node_modules/@cspotcode/source-map-support": { @@ -2240,9 +2240,9 @@ } }, "node_modules/wrangler": { - "version": "3.53.0", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.53.0.tgz", - "integrity": "sha512-JxkvCQekL9j8Mu4CEKM/HEVyDnymWzKQuMUuJH0yum1AilutD5HAP9kVVYmvu7BvwlRyRUAj8TI5OUxXnLCEpQ==", + "version": "3.53.1", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.53.1.tgz", + "integrity": "sha512-bdMRQdHYdvowIwOhEMFkARIZUh56aDw7HLUZ/2JreBjj760osXE4Fc4L1TCkfRRBWgB6/LKF5LA4OcvORMYmHg==", "dev": true, "dependencies": { "@cloudflare/kv-asset-handler": "0.3.2", @@ -2774,9 +2774,9 @@ "optional": true }, "@cloudflare/workers-types": { - "version": "4.20240423.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20240423.0.tgz", - "integrity": "sha512-ssuccb3j+URp6mP2p0PcQE9vmS3YeKBQnALHF9P3yQfUAFozuhTsDTbqmL+zPrJvUcG7SL2xVQkNDF9QJeKDZw==", + "version": "4.20240502.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20240502.0.tgz", + "integrity": "sha512-OB1jIyPOzyOcuZFHWhsQnkRLN6u8+jmU9X3T4KZlGgn3Ivw8pBiswhLOp+yFeChR3Y4/5+V0hPFRko5SReordg==", "dev": true }, "@cspotcode/source-map-support": { @@ -4150,9 +4150,9 @@ } }, "wrangler": { - "version": "3.53.0", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.53.0.tgz", - "integrity": "sha512-JxkvCQekL9j8Mu4CEKM/HEVyDnymWzKQuMUuJH0yum1AilutD5HAP9kVVYmvu7BvwlRyRUAj8TI5OUxXnLCEpQ==", + "version": "3.53.1", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.53.1.tgz", + "integrity": "sha512-bdMRQdHYdvowIwOhEMFkARIZUh56aDw7HLUZ/2JreBjj760osXE4Fc4L1TCkfRRBWgB6/LKF5LA4OcvORMYmHg==", "dev": true, "requires": { "@cloudflare/kv-asset-handler": "0.3.2", diff --git a/tailcall-cloudflare/src/handle.rs b/tailcall-cloudflare/src/handle.rs index 36fb31b525..4dd2123d22 100644 --- a/tailcall-cloudflare/src/handle.rs +++ b/tailcall-cloudflare/src/handle.rs @@ -3,10 +3,10 @@ use std::collections::HashMap; use std::rc::Rc; use std::sync::{Arc, RwLock}; -use hyper::{Body, Method, Request, Response}; +use hyper::{Body, Request, Response}; use lazy_static::lazy_static; use tailcall::async_graphql_hyper::GraphQLRequest; -use tailcall::http::{graphiql, handle_request, showcase, AppContext}; +use tailcall::http::{handle_request, showcase, AppContext}; use crate::http::{to_request, to_response}; use crate::runtime; @@ -28,14 +28,6 @@ pub async fn fetch( ); let req = to_request(req).await?; - // Quick exit to GraphiQL - // - // Has to be done here, since when using GraphiQL, a config query parameter is - // not specified, and get_app_ctx will fail without it. - if req.method() == Method::GET { - return to_response(graphiql(&req)?).await; - } - let env = Rc::new(env); let app_ctx = match get_app_ctx(env, &req).await? { Ok(app_ctx) => app_ctx, diff --git a/tailcall-cloudflare/tests/cf_tests.spec.ts b/tailcall-cloudflare/tests/cf_tests.spec.ts index 8ea562bbbb..879f8303f2 100644 --- a/tailcall-cloudflare/tests/cf_tests.spec.ts +++ b/tailcall-cloudflare/tests/cf_tests.spec.ts @@ -1,4 +1,4 @@ -import {describe, test, expect} from "vitest" +import {describe, expect, test} from "vitest" import {readFile} from "fs/promises" import {mf} from "./mf" @@ -17,15 +17,6 @@ describe("fetch", () => { await bucket.put("examples/jsonplaceholder_batch.graphql", placeholder_batch) }) - test("ide", async () => { - let resp = await mf.dispatchFetch("https://fake.host/", { - method: "GET", - }) - let body = await resp.text() - expect(body.includes("Tailcall - GraphQL IDE")).toBe(true) - expect(resp.status).toBe(200) - }) - test("sample_resp", async () => { let resp = await mf.dispatchFetch("https://fake.host/graphql?config=examples/jsonplaceholder.graphql", { method: "POST", diff --git a/tailcall-fixtures/fixtures/configs/user-posts.graphql b/tailcall-fixtures/fixtures/configs/user-posts.graphql index 079c492893..e45f9aa569 100644 --- a/tailcall-fixtures/fixtures/configs/user-posts.graphql +++ b/tailcall-fixtures/fixtures/configs/user-posts.graphql @@ -1,5 +1,5 @@ schema - @server(port: 8000, graphiql: true, hostname: "0.0.0.0") + @server(port: 8000, hostname: "0.0.0.0") @upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: true) { query: Query } diff --git a/tailcall-macros/Cargo.toml b/tailcall-macros/Cargo.toml index 6cbf8a83a5..036f418fd8 100644 --- a/tailcall-macros/Cargo.toml +++ b/tailcall-macros/Cargo.toml @@ -8,5 +8,5 @@ proc-macro = true [dependencies] syn = { version = "2.0.60", features = ["derive", "full"] } -quote = "1.0" -proc-macro2 = "1.0" +quote = "1.0.36" +proc-macro2 = "1.0.81" diff --git a/tailcall-prettier/src/lib.rs b/tailcall-prettier/src/lib.rs index 13b706a359..74f2f25b54 100644 --- a/tailcall-prettier/src/lib.rs +++ b/tailcall-prettier/src/lib.rs @@ -9,7 +9,7 @@ lazy_static::lazy_static! { static ref PRETTIER: Arc = Arc::new(Prettier::new()); } -pub async fn format>(source: T, parser: Parser) -> Result { +pub async fn format>(source: T, parser: &Parser) -> Result { PRETTIER.format(source.as_ref().to_string(), parser).await } @@ -19,7 +19,7 @@ mod tests { #[tokio::test] async fn test_js() -> anyhow::Result<()> { - let prettier = format("const x={a:3};", Parser::Js).await?; + let prettier = format("const x={a:3};", &Parser::Js).await?; assert_eq!("const x = {a: 3}\n", prettier); Ok(()) } diff --git a/tailcall-prettier/src/parser.rs b/tailcall-prettier/src/parser.rs index 1a0484983f..26a31bc9e8 100644 --- a/tailcall-prettier/src/parser.rs +++ b/tailcall-prettier/src/parser.rs @@ -1,6 +1,6 @@ use anyhow::{anyhow, Result}; -#[derive(strum_macros::Display)] +#[derive(strum_macros::Display, Clone)] pub enum Parser { Gql, Yml, diff --git a/tailcall-prettier/src/prettier.rs b/tailcall-prettier/src/prettier.rs index 0693ae6b67..b196d667ce 100644 --- a/tailcall-prettier/src/prettier.rs +++ b/tailcall-prettier/src/prettier.rs @@ -19,7 +19,8 @@ impl Prettier { Self { runtime } } - pub async fn format(&self, source: String, parser: Parser) -> Result { + pub async fn format<'a>(&'a self, source: String, parser: &'a Parser) -> Result { + let parser = parser.clone(); self.runtime .spawn_blocking(move || { let mut command = command(); @@ -38,7 +39,10 @@ impl Prettier { if output.status.success() { Ok(String::from_utf8(output.stdout)?) } else { - Err(anyhow!("Prettier formatting failed")) + Err(anyhow!( + "Prettier formatting failed: {}", + String::from_utf8(output.stderr).unwrap() + )) } }) .await? diff --git a/tailcall-query-plan/src/resolver.rs b/tailcall-query-plan/src/resolver.rs index c7868e8a79..9b875be77d 100644 --- a/tailcall-query-plan/src/resolver.rs +++ b/tailcall-query-plan/src/resolver.rs @@ -59,7 +59,7 @@ impl FieldPlan { &'a self, ctx: EvaluationContext<'a, Ctx>, ) -> Result { - self.resolver.eval(ctx, &Concurrent::Sequential).await + Ok(self.resolver.eval(ctx, &Concurrent::Sequential).await?) } } diff --git a/tests/core/snapshots/apollo-tracing.md_merged.snap b/tests/core/snapshots/apollo-tracing.md_merged.snap index c65bafe30d..655fb0c12e 100644 --- a/tests/core/snapshots/apollo-tracing.md_merged.snap +++ b/tests/core/snapshots/apollo-tracing.md_merged.snap @@ -2,7 +2,7 @@ source: tests/core/spec.rs expression: merged --- -schema @server(graphiql: true, hostname: "0.0.0.0", port: 8000) @upstream { +schema @server(hostname: "0.0.0.0", port: 8000) @upstream { query: Query } diff --git a/tests/core/snapshots/async-cache-disabled.md_0.snap b/tests/core/snapshots/async-cache-disabled.md_0.snap new file mode 100644 index 0000000000..96461ffba6 --- /dev/null +++ b/tests/core/snapshots/async-cache-disabled.md_0.snap @@ -0,0 +1,19 @@ +--- +source: tests/core/spec.rs +expression: response +--- +{ + "status": 200, + "headers": { + "content-type": "application/json" + }, + "body": { + "data": { + "posts": { + "user": { + "name": "Leanne Graham" + } + } + } + } +} diff --git a/tests/core/snapshots/async-cache-disabled.md_client.snap b/tests/core/snapshots/async-cache-disabled.md_client.snap new file mode 100644 index 0000000000..2971e51959 --- /dev/null +++ b/tests/core/snapshots/async-cache-disabled.md_client.snap @@ -0,0 +1,36 @@ +--- +source: tests/core/spec.rs +expression: client +--- +scalar Date + +scalar Email + +scalar Empty + +scalar JSON + +scalar PhoneNumber + +type Post { + body: String + id: Int + title: String + user: User + userId: Int! +} + +type Query { + posts: Post +} + +scalar Url + +type User { + id: Int + name: String +} + +schema { + query: Query +} diff --git a/tests/core/snapshots/async-cache-disabled.md_merged.snap b/tests/core/snapshots/async-cache-disabled.md_merged.snap new file mode 100644 index 0000000000..7f150778ec --- /dev/null +++ b/tests/core/snapshots/async-cache-disabled.md_merged.snap @@ -0,0 +1,24 @@ +--- +source: tests/core/spec.rs +expression: merged +--- +schema @server(port: 8000, queryValidation: false) @upstream(baseURL: "http://jsonplaceholder.typicode.com") { + query: Query +} + +type Post { + body: String + id: Int + title: String + user: User @http(path: "/users/{{.value.userId}}") + userId: Int! +} + +type Query { + posts: Post @http(path: "/post?id=1") +} + +type User { + id: Int + name: String +} diff --git a/tests/core/snapshots/async-cache-enable-multiple-resolvers.md_0.snap b/tests/core/snapshots/async-cache-enable-multiple-resolvers.md_0.snap new file mode 100644 index 0000000000..5774eaa25a --- /dev/null +++ b/tests/core/snapshots/async-cache-enable-multiple-resolvers.md_0.snap @@ -0,0 +1,34 @@ +--- +source: tests/core/spec.rs +expression: response +--- +{ + "status": 200, + "headers": { + "content-type": "application/json" + }, + "body": { + "data": { + "posts": [ + { + "user": { + "name": "Leanne Graham" + }, + "taggedUsers": [ + {}, + {} + ] + }, + { + "user": { + "name": "Leanne Graham" + }, + "taggedUsers": [ + {}, + {} + ] + } + ] + } + } +} diff --git a/tests/core/snapshots/async-cache-enable-multiple-resolvers.md_client.snap b/tests/core/snapshots/async-cache-enable-multiple-resolvers.md_client.snap new file mode 100644 index 0000000000..23f1cadfd7 --- /dev/null +++ b/tests/core/snapshots/async-cache-enable-multiple-resolvers.md_client.snap @@ -0,0 +1,37 @@ +--- +source: tests/core/spec.rs +expression: client +--- +scalar Date + +scalar Email + +scalar Empty + +scalar JSON + +scalar PhoneNumber + +type Post { + body: String + id: Int! + taggedUsers: [User] + title: String + user: User + userId: Int! +} + +type Query { + posts: [Post] +} + +scalar Url + +type User { + id: Int + name: String +} + +schema { + query: Query +} diff --git a/tests/core/snapshots/async-cache-enable-multiple-resolvers.md_merged.snap b/tests/core/snapshots/async-cache-enable-multiple-resolvers.md_merged.snap new file mode 100644 index 0000000000..b7c0ef74aa --- /dev/null +++ b/tests/core/snapshots/async-cache-enable-multiple-resolvers.md_merged.snap @@ -0,0 +1,25 @@ +--- +source: tests/core/spec.rs +expression: merged +--- +schema @server(port: 8000, queryValidation: false) @upstream(baseURL: "http://jsonplaceholder.typicode.com", dedupe: true) { + query: Query +} + +type Post { + body: String + id: Int! + taggedUsers: [User] @http(path: "/taggedUsers/{{.value.id}}") + title: String + user: User @http(path: "/users/{{.value.userId}}") + userId: Int! +} + +type Query { + posts: [Post] @http(path: "/posts?id=1") +} + +type User { + id: Int + name: String +} diff --git a/tests/core/snapshots/async-cache-enabled.md_0.snap b/tests/core/snapshots/async-cache-enabled.md_0.snap new file mode 100644 index 0000000000..b2709ed123 --- /dev/null +++ b/tests/core/snapshots/async-cache-enabled.md_0.snap @@ -0,0 +1,26 @@ +--- +source: tests/core/spec.rs +expression: response +--- +{ + "status": 200, + "headers": { + "content-type": "application/json" + }, + "body": { + "data": { + "posts": [ + { + "user": { + "name": "Leanne Graham" + } + }, + { + "user": { + "name": "Leanne Graham" + } + } + ] + } + } +} diff --git a/tests/core/snapshots/async-cache-enabled.md_client.snap b/tests/core/snapshots/async-cache-enabled.md_client.snap new file mode 100644 index 0000000000..7ae47b261d --- /dev/null +++ b/tests/core/snapshots/async-cache-enabled.md_client.snap @@ -0,0 +1,36 @@ +--- +source: tests/core/spec.rs +expression: client +--- +scalar Date + +scalar Email + +scalar Empty + +scalar JSON + +scalar PhoneNumber + +type Post { + body: String + id: Int + title: String + user: User + userId: Int! +} + +type Query { + posts: [Post] +} + +scalar Url + +type User { + id: Int + name: String +} + +schema { + query: Query +} diff --git a/tests/core/snapshots/async-cache-enabled.md_merged.snap b/tests/core/snapshots/async-cache-enabled.md_merged.snap new file mode 100644 index 0000000000..2baa7e4203 --- /dev/null +++ b/tests/core/snapshots/async-cache-enabled.md_merged.snap @@ -0,0 +1,24 @@ +--- +source: tests/core/spec.rs +expression: merged +--- +schema @server(port: 8000, queryValidation: false) @upstream(baseURL: "http://jsonplaceholder.typicode.com", dedupe: true) { + query: Query +} + +type Post { + body: String + id: Int + title: String + user: User @http(path: "/users/{{.value.userId}}") + userId: Int! +} + +type Query { + posts: [Post] @http(path: "/posts?id=1") +} + +type User { + id: Int + name: String +} diff --git a/tests/core/snapshots/auth-basic.md_merged.snap b/tests/core/snapshots/auth-basic.md_merged.snap index 4e174cdbc4..990292607f 100644 --- a/tests/core/snapshots/auth-basic.md_merged.snap +++ b/tests/core/snapshots/auth-basic.md_merged.snap @@ -2,7 +2,7 @@ source: tests/core/spec.rs expression: merged --- -schema @server(graphiql: true, port: 8000) @upstream @link(id: "htpasswd", src: ".htpasswd", type: Htpasswd) { +schema @server(port: 8000) @upstream @link(id: "htpasswd", src: ".htpasswd", type: Htpasswd) { query: Query mutation: Mutation } diff --git a/tests/core/snapshots/auth-jwt.md_merged.snap b/tests/core/snapshots/auth-jwt.md_merged.snap index 9ea0b077da..69bf7be283 100644 --- a/tests/core/snapshots/auth-jwt.md_merged.snap +++ b/tests/core/snapshots/auth-jwt.md_merged.snap @@ -2,7 +2,7 @@ source: tests/core/spec.rs expression: merged --- -schema @server(graphiql: true, port: 8000) @upstream @link(id: "jwks", src: "jwks.json", type: Jwks) { +schema @server(port: 8000) @upstream @link(id: "jwks", src: "jwks.json", type: Jwks) { query: Query mutation: Mutation } diff --git a/tests/core/snapshots/call-graphql-datasource.md_merged.snap b/tests/core/snapshots/call-graphql-datasource.md_merged.snap index ec344567c4..12db968e5d 100644 --- a/tests/core/snapshots/call-graphql-datasource.md_merged.snap +++ b/tests/core/snapshots/call-graphql-datasource.md_merged.snap @@ -2,7 +2,7 @@ source: tests/core/spec.rs expression: merged --- -schema @server(graphiql: true, hostname: "0.0.0.0", port: 8000) @upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: true) { +schema @server(hostname: "0.0.0.0", port: 8000) @upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: true) { query: Query } diff --git a/tests/core/snapshots/call-operator.md_merged.snap b/tests/core/snapshots/call-operator.md_merged.snap index ad316db56f..dc9d89aabc 100644 --- a/tests/core/snapshots/call-operator.md_merged.snap +++ b/tests/core/snapshots/call-operator.md_merged.snap @@ -2,7 +2,7 @@ source: tests/core/spec.rs expression: merged --- -schema @server(graphiql: true, hostname: "0.0.0.0", port: 8000) @upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: true) @link(id: "news", src: "news.proto", type: Protobuf) { +schema @server(hostname: "0.0.0.0", port: 8000) @upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: true) @link(id: "news", src: "news.proto", type: Protobuf) { query: Query } diff --git a/tests/core/snapshots/grpc-batch.md_merged.snap b/tests/core/snapshots/grpc-batch.md_merged.snap index 14626ab137..26d6e41803 100644 --- a/tests/core/snapshots/grpc-batch.md_merged.snap +++ b/tests/core/snapshots/grpc-batch.md_merged.snap @@ -2,7 +2,7 @@ source: tests/core/spec.rs expression: merged --- -schema @server(graphiql: true, port: 8000) @upstream(batch: {delay: 10, headers: []}, httpCache: true) @link(id: "news", src: "news.proto", type: Protobuf) { +schema @server(port: 8000) @upstream(batch: {delay: 10, headers: []}, httpCache: true) @link(id: "news", src: "news.proto", type: Protobuf) { query: Query } diff --git a/tests/core/snapshots/grpc-error.md_0.snap b/tests/core/snapshots/grpc-error.md_0.snap index e69720283c..cca7ca0d8c 100644 --- a/tests/core/snapshots/grpc-error.md_0.snap +++ b/tests/core/snapshots/grpc-error.md_0.snap @@ -17,7 +17,17 @@ expression: response "line": 1, "column": 9 } - ] + ], + "extensions": { + "grpcCode": 3, + "grpcDescription": "Client specified an invalid argument", + "grpcStatusDetails": { + "code": 3, + "message": "error message", + "details": [] + }, + "grpcStatusMessage": "grpc message" + } } ] } diff --git a/tests/core/snapshots/grpc-error.md_merged.snap b/tests/core/snapshots/grpc-error.md_merged.snap index 54e5f0b144..385aa86cce 100644 --- a/tests/core/snapshots/grpc-error.md_merged.snap +++ b/tests/core/snapshots/grpc-error.md_merged.snap @@ -2,7 +2,7 @@ source: tests/core/spec.rs expression: merged --- -schema @server(graphiql: true, port: 8000) @upstream(batch: {delay: 10, headers: []}, httpCache: true) @link(id: "news", src: "news.proto", type: Protobuf) { +schema @server(port: 8000) @upstream(batch: {delay: 10, headers: []}, httpCache: true) @link(id: "news", src: "news.proto", type: Protobuf) { query: Query } diff --git a/tests/core/snapshots/grpc-override-url-from-upstream.md_merged.snap b/tests/core/snapshots/grpc-override-url-from-upstream.md_merged.snap index a42f6b7991..b4310be979 100644 --- a/tests/core/snapshots/grpc-override-url-from-upstream.md_merged.snap +++ b/tests/core/snapshots/grpc-override-url-from-upstream.md_merged.snap @@ -2,7 +2,7 @@ source: tests/core/spec.rs expression: merged --- -schema @server(graphiql: true, port: 8000) @upstream(baseURL: "http://not-a-valid-grpc-url.com", batch: {delay: 10, headers: []}, httpCache: true) @link(id: "news", src: "news.proto", type: Protobuf) { +schema @server(port: 8000) @upstream(baseURL: "http://not-a-valid-grpc-url.com", batch: {delay: 10, headers: []}, httpCache: true) @link(id: "news", src: "news.proto", type: Protobuf) { query: Query } diff --git a/tests/core/snapshots/grpc-proto-with-same-package.md_merged.snap b/tests/core/snapshots/grpc-proto-with-same-package.md_merged.snap index 885b23f062..627c602738 100644 --- a/tests/core/snapshots/grpc-proto-with-same-package.md_merged.snap +++ b/tests/core/snapshots/grpc-proto-with-same-package.md_merged.snap @@ -2,7 +2,7 @@ source: tests/core/spec.rs expression: merged --- -schema @server(graphiql: true, port: 8000) @upstream(baseURL: "http://localhost:50051") @link(src: "foo.proto", type: Protobuf) @link(src: "bar.proto", type: Protobuf) { +schema @server(port: 8000) @upstream(baseURL: "http://localhost:50051") @link(src: "foo.proto", type: Protobuf) @link(src: "bar.proto", type: Protobuf) { query: Query } diff --git a/tests/core/snapshots/grpc-reflection.md_merged.snap b/tests/core/snapshots/grpc-reflection.md_merged.snap index ef18012e5e..3588897f4b 100644 --- a/tests/core/snapshots/grpc-reflection.md_merged.snap +++ b/tests/core/snapshots/grpc-reflection.md_merged.snap @@ -2,7 +2,7 @@ source: tests/core/spec.rs expression: merged --- -schema @server(graphiql: true, port: 8000) @upstream(baseURL: "http://localhost:50051", httpCache: true) @link(src: "http://localhost:50051", type: Grpc) { +schema @server(port: 8000) @upstream(baseURL: "http://localhost:50051", httpCache: true) @link(src: "http://localhost:50051", type: Grpc) { query: Query } diff --git a/tests/core/snapshots/grpc-simple.md_merged.snap b/tests/core/snapshots/grpc-simple.md_merged.snap index 54e5f0b144..385aa86cce 100644 --- a/tests/core/snapshots/grpc-simple.md_merged.snap +++ b/tests/core/snapshots/grpc-simple.md_merged.snap @@ -2,7 +2,7 @@ source: tests/core/spec.rs expression: merged --- -schema @server(graphiql: true, port: 8000) @upstream(batch: {delay: 10, headers: []}, httpCache: true) @link(id: "news", src: "news.proto", type: Protobuf) { +schema @server(port: 8000) @upstream(batch: {delay: 10, headers: []}, httpCache: true) @link(id: "news", src: "news.proto", type: Protobuf) { query: Query } diff --git a/tests/core/snapshots/grpc-url-from-upstream.md_merged.snap b/tests/core/snapshots/grpc-url-from-upstream.md_merged.snap index 0ad2090a9f..ccab578907 100644 --- a/tests/core/snapshots/grpc-url-from-upstream.md_merged.snap +++ b/tests/core/snapshots/grpc-url-from-upstream.md_merged.snap @@ -2,7 +2,7 @@ source: tests/core/spec.rs expression: merged --- -schema @server(graphiql: true, port: 8000) @upstream(baseURL: "http://localhost:50051", batch: {delay: 10, headers: []}, httpCache: true) @link(id: "news", src: "news.proto", type: Protobuf) { +schema @server(port: 8000) @upstream(baseURL: "http://localhost:50051", batch: {delay: 10, headers: []}, httpCache: true) @link(id: "news", src: "news.proto", type: Protobuf) { query: Query } diff --git a/tests/core/snapshots/io-cache.md_merged.snap b/tests/core/snapshots/io-cache.md_merged.snap index 7669ff0baa..87b91f625b 100644 --- a/tests/core/snapshots/io-cache.md_merged.snap +++ b/tests/core/snapshots/io-cache.md_merged.snap @@ -2,7 +2,7 @@ source: tests/core/spec.rs expression: merged --- -schema @server(graphiql: true, hostname: "0.0.0.0", port: 8000) @upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: true) { +schema @server(hostname: "0.0.0.0", port: 8000) @upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: true) { query: Query } diff --git a/tests/core/snapshots/jsonplaceholder-call-post.md_merged.snap b/tests/core/snapshots/jsonplaceholder-call-post.md_merged.snap index a5a42cbbac..e49ac9d01a 100644 --- a/tests/core/snapshots/jsonplaceholder-call-post.md_merged.snap +++ b/tests/core/snapshots/jsonplaceholder-call-post.md_merged.snap @@ -2,7 +2,7 @@ source: tests/core/spec.rs expression: merged --- -schema @server(graphiql: true, hostname: "0.0.0.0", port: 8000) @upstream(baseURL: "http://jsonplaceholder.typicode.com", batch: {delay: 100, headers: []}, httpCache: true) { +schema @server(hostname: "0.0.0.0", port: 8000) @upstream(baseURL: "http://jsonplaceholder.typicode.com", batch: {delay: 100, headers: []}, httpCache: true) { query: Query } diff --git a/tests/core/snapshots/test-enum-default.md_0.snap b/tests/core/snapshots/test-enum-default.md_0.snap index d177f0791f..84d79bf621 100644 --- a/tests/core/snapshots/test-enum-default.md_0.snap +++ b/tests/core/snapshots/test-enum-default.md_0.snap @@ -21,7 +21,7 @@ expression: response }, { "id": 3, - "foo": "ND" + "foo": "NOT_DEFINED" } ] } diff --git a/tests/core/snapshots/test-enum-default.md_client.snap b/tests/core/snapshots/test-enum-default.md_client.snap index 45aa5dcbe8..8af62223b3 100644 --- a/tests/core/snapshots/test-enum-default.md_client.snap +++ b/tests/core/snapshots/test-enum-default.md_client.snap @@ -27,7 +27,7 @@ type Query { enum Status { DRAFT - ND + NOT_DEFINED PUBLISHED } diff --git a/tests/core/snapshots/test-enum-default.md_merged.snap b/tests/core/snapshots/test-enum-default.md_merged.snap index fedfe3b663..f5750f210c 100644 --- a/tests/core/snapshots/test-enum-default.md_merged.snap +++ b/tests/core/snapshots/test-enum-default.md_merged.snap @@ -2,13 +2,13 @@ source: tests/core/spec.rs expression: merged --- -schema @server(graphiql: true, port: 8080) @upstream(baseURL: "http://localhost:50051", batch: {delay: 10, headers: []}, httpCache: true) @link(id: "news", src: "./service.proto", type: Protobuf) { +schema @server(port: 8080) @upstream(baseURL: "http://localhost:50051", batch: {delay: 10, headers: []}, httpCache: true) @link(id: "news", src: "./service.proto", type: Protobuf) { query: Query } enum Status { DRAFT - ND + NOT_DEFINED PUBLISHED } diff --git a/tests/core/snapshots/test-params-as-body.md_merged.snap b/tests/core/snapshots/test-params-as-body.md_merged.snap index 8d9f37e2a6..321ec2ce72 100644 --- a/tests/core/snapshots/test-params-as-body.md_merged.snap +++ b/tests/core/snapshots/test-params-as-body.md_merged.snap @@ -2,7 +2,7 @@ source: tests/core/spec.rs expression: merged --- -schema @server(graphiql: true, port: 8000) @upstream(baseURL: "http://jsonplaceholder.typicode.com") { +schema @server(port: 8000) @upstream(baseURL: "http://jsonplaceholder.typicode.com") { query: Query } diff --git a/tests/core/snapshots/test-scalars.md_0.snap b/tests/core/snapshots/test-scalars.md_0.snap index f9589339d0..3eab7453aa 100644 --- a/tests/core/snapshots/test-scalars.md_0.snap +++ b/tests/core/snapshots/test-scalars.md_0.snap @@ -9,7 +9,7 @@ expression: response }, "body": { "data": { - "email": "alo@valid.com" + "email": "hello@valid.com" } } } diff --git a/tests/core/snapshots/test-scalars.md_4.snap b/tests/core/snapshots/test-scalars.md_4.snap index f05fab41e1..307ade083b 100644 --- a/tests/core/snapshots/test-scalars.md_4.snap +++ b/tests/core/snapshots/test-scalars.md_4.snap @@ -8,20 +8,11 @@ expression: response "content-type": "application/json" }, "body": { - "data": null, - "errors": [ - { - "message": "internal: invalid value for scalar \"Email\", expected \"FieldValue::Value\"", - "locations": [ - { - "line": 1, - "column": 3 - } - ], - "path": [ - "email" - ] - } - ] + "data": { + "any1": { + "test": "abc" + }, + "any2": "string-value" + } } } diff --git a/tests/core/snapshots/test-scalars.md_5.snap b/tests/core/snapshots/test-scalars.md_5.snap index 8b0ea58a37..f05fab41e1 100644 --- a/tests/core/snapshots/test-scalars.md_5.snap +++ b/tests/core/snapshots/test-scalars.md_5.snap @@ -11,7 +11,7 @@ expression: response "data": null, "errors": [ { - "message": "internal: invalid value for scalar \"PhoneNumber\", expected \"FieldValue::Value\"", + "message": "internal: invalid value for scalar \"Email\", expected \"FieldValue::Value\"", "locations": [ { "line": 1, @@ -19,7 +19,7 @@ expression: response } ], "path": [ - "phone" + "email" ] } ] diff --git a/tests/core/snapshots/test-scalars.md_7.snap b/tests/core/snapshots/test-scalars.md_7.snap index 31a1a3502f..8b0ea58a37 100644 --- a/tests/core/snapshots/test-scalars.md_7.snap +++ b/tests/core/snapshots/test-scalars.md_7.snap @@ -11,7 +11,7 @@ expression: response "data": null, "errors": [ { - "message": "internal: invalid value for scalar \"Date\", expected \"FieldValue::Value\"", + "message": "internal: invalid value for scalar \"PhoneNumber\", expected \"FieldValue::Value\"", "locations": [ { "line": 1, @@ -19,7 +19,7 @@ expression: response } ], "path": [ - "date" + "phone" ] } ] diff --git a/tests/core/snapshots/test-scalars.md_8.snap b/tests/core/snapshots/test-scalars.md_8.snap index 4ab684e59e..31a1a3502f 100644 --- a/tests/core/snapshots/test-scalars.md_8.snap +++ b/tests/core/snapshots/test-scalars.md_8.snap @@ -11,7 +11,7 @@ expression: response "data": null, "errors": [ { - "message": "internal: invalid value for scalar \"Url\", expected \"FieldValue::Value\"", + "message": "internal: invalid value for scalar \"Date\", expected \"FieldValue::Value\"", "locations": [ { "line": 1, @@ -19,7 +19,7 @@ expression: response } ], "path": [ - "url" + "date" ] } ] diff --git a/tests/core/snapshots/test-scalars.md_9.snap b/tests/core/snapshots/test-scalars.md_9.snap new file mode 100644 index 0000000000..4ab684e59e --- /dev/null +++ b/tests/core/snapshots/test-scalars.md_9.snap @@ -0,0 +1,27 @@ +--- +source: tests/core/spec.rs +expression: response +--- +{ + "status": 200, + "headers": { + "content-type": "application/json" + }, + "body": { + "data": null, + "errors": [ + { + "message": "internal: invalid value for scalar \"Url\", expected \"FieldValue::Value\"", + "locations": [ + { + "line": 1, + "column": 3 + } + ], + "path": [ + "url" + ] + } + ] + } +} diff --git a/tests/core/snapshots/test-scalars.md_client.snap b/tests/core/snapshots/test-scalars.md_client.snap index 12653f5f33..1d0bcc5ebf 100644 --- a/tests/core/snapshots/test-scalars.md_client.snap +++ b/tests/core/snapshots/test-scalars.md_client.snap @@ -2,6 +2,8 @@ source: tests/core/spec.rs expression: client --- +scalar AnyScalar + scalar Date scalar Email @@ -13,6 +15,7 @@ scalar JSON scalar PhoneNumber type Query { + any(value: AnyScalar!): AnyScalar date(value: Date!): Date! email(value: Email!): Email! phone(value: PhoneNumber!): PhoneNumber! diff --git a/tests/core/snapshots/test-scalars.md_merged.snap b/tests/core/snapshots/test-scalars.md_merged.snap index 69c62c196e..716063f91c 100644 --- a/tests/core/snapshots/test-scalars.md_merged.snap +++ b/tests/core/snapshots/test-scalars.md_merged.snap @@ -2,19 +2,16 @@ source: tests/core/spec.rs expression: merged --- -schema @server(graphiql: true, hostname: "localhost", port: 8000) @upstream { +schema @server(hostname: "localhost", port: 8000) @upstream { query: Query } -scalar Date +scalar AnyScalar scalar Email -scalar PhoneNumber - -scalar Url - type Query { + any(value: AnyScalar!): AnyScalar @expr(body: "{{.args.value}}") date(value: Date!): Date! @expr(body: "{{.args.value}}") email(value: Email!): Email! @expr(body: "{{.args.value}}") phone(value: PhoneNumber!): PhoneNumber! @expr(body: "{{.args.value}}") diff --git a/tests/core/snapshots/test-set-cookie-headers.md_merged.snap b/tests/core/snapshots/test-set-cookie-headers.md_merged.snap index 8660783954..d3da154ead 100644 --- a/tests/core/snapshots/test-set-cookie-headers.md_merged.snap +++ b/tests/core/snapshots/test-set-cookie-headers.md_merged.snap @@ -2,7 +2,7 @@ source: tests/core/spec.rs expression: merged --- -schema @server(headers: {setCookies: true}, graphiql: true, hostname: "0.0.0.0", port: 8080) @upstream(baseURL: "http://jsonplaceholder.typicode.com") { +schema @server(headers: {setCookies: true}, hostname: "0.0.0.0", port: 8080) @upstream(baseURL: "http://jsonplaceholder.typicode.com") { query: Query } diff --git a/tests/core/spec.rs b/tests/core/spec.rs index 6b3e5b72af..e93aeb28dd 100644 --- a/tests/core/spec.rs +++ b/tests/core/spec.rs @@ -122,30 +122,27 @@ async fn check_server_config(spec: ExecutionSpec) -> Vec { // tests that were explicitly written with it in mind if spec.check_identity { if matches!(source, Source::GraphQL) { - let identity = config.to_sdl(); + let actual = config.to_sdl(); // \r is added automatically in windows, it's safe to replace it with \n let content = content.replace("\r\n", "\n"); let path_str = spec.path.display().to_string(); + let context = format!("path: {}", path_str); - let identity = tailcall_prettier::format( - identity, - tailcall_prettier::Parser::detect(path_str.as_str()).unwrap(), - ) - .await - .unwrap(); + let actual = tailcall_prettier::format(actual, &tailcall_prettier::Parser::Gql) + .await + .context(context.clone()) + .unwrap(); - let content = tailcall_prettier::format( - content, - tailcall_prettier::Parser::detect(path_str.as_str()).unwrap(), - ) - .await - .unwrap(); + let expected = tailcall_prettier::format(content, &tailcall_prettier::Parser::Gql) + .await + .context(context) + .unwrap(); pretty_assertions::assert_eq!( - identity, - content, + actual, + expected, "Identity check failed for {:#?}", spec.path, ); diff --git a/tests/execution/add-field-many-list.md b/tests/execution/add-field-many-list.md index c4dd3ba8f1..2efbae182d 100644 --- a/tests/execution/add-field-many-list.md +++ b/tests/execution/add-field-many-list.md @@ -19,7 +19,10 @@ type Query { u: U @http(baseURL: "http://jsonplaceholder.typicode.com", path: "/us/1") } -type U @addField(name: "b", path: ["a", "b"]) @addField(name: "c", path: ["a", "c"]) @addField(name: "d", path: ["a", "d"]) { +type U + @addField(name: "b", path: ["a", "b"]) + @addField(name: "c", path: ["a", "c"]) + @addField(name: "d", path: ["a", "d"]) { a: A e: String } diff --git a/tests/execution/add-field-many.md b/tests/execution/add-field-many.md index 146e7b17bd..74bd20d41d 100644 --- a/tests/execution/add-field-many.md +++ b/tests/execution/add-field-many.md @@ -9,7 +9,10 @@ schema @server @upstream { query: Query } -type Foo @addField(name: "a", path: ["x", "a"]) @addField(name: "b", path: ["x", "b"]) @addField(name: "c", path: ["x", "c"]) { +type Foo + @addField(name: "a", path: ["x", "a"]) + @addField(name: "b", path: ["x", "b"]) + @addField(name: "c", path: ["x", "c"]) { name: String x: X } diff --git a/tests/execution/apollo-tracing.md b/tests/execution/apollo-tracing.md index e534dc0780..72ffd9b01b 100644 --- a/tests/execution/apollo-tracing.md +++ b/tests/execution/apollo-tracing.md @@ -2,7 +2,7 @@ ```graphql @server schema - @server(port: 8000, graphiql: true, hostname: "0.0.0.0") + @server(port: 8000, hostname: "0.0.0.0") @telemetry(export: {apollo: {apiKey: "", graphRef: "tailcall-demo-3@current"}}) { query: Query } diff --git a/tests/execution/async-cache-disabled.md b/tests/execution/async-cache-disabled.md new file mode 100644 index 0000000000..03366d8033 --- /dev/null +++ b/tests/execution/async-cache-disabled.md @@ -0,0 +1,51 @@ +# Async Cache Disabled + +```graphql @server +schema @server(port: 8000, queryValidation: false) @upstream(baseURL: "http://jsonplaceholder.typicode.com") { + query: Query +} + +type Query { + posts: Post @http(path: "/post?id=1") +} + +type Post { + id: Int + title: String + body: String + userId: Int! + user: User @http(path: "/users/{{.value.userId}}") +} + +type User { + id: Int + name: String +} +``` + +```yml @mock +- request: + method: GET + url: http://jsonplaceholder.typicode.com/post?id=1 + response: + status: 200 + body: + id: 1 + userId: 1 +- request: + method: GET + url: http://jsonplaceholder.typicode.com/users/1 + expectedHits: 1 + response: + status: 200 + body: + id: 1 + name: Leanne Graham +``` + +```yml @test +- method: POST + url: http://localhost:8080/graphql + body: + query: query { posts { user { name } } } +``` diff --git a/tests/execution/async-cache-enable-multiple-resolvers.md b/tests/execution/async-cache-enable-multiple-resolvers.md new file mode 100644 index 0000000000..6d9838b1ed --- /dev/null +++ b/tests/execution/async-cache-enable-multiple-resolvers.md @@ -0,0 +1,67 @@ +# Async Cache Enabled + +```graphql @server +schema + @server(port: 8000, queryValidation: false) + @upstream(baseURL: "http://jsonplaceholder.typicode.com", dedupe: true) { + query: Query +} + +type Query { + posts: [Post] @http(path: "/posts?id=1") +} + +type Post { + id: Int! + title: String + body: String + userId: Int! + user: User @http(path: "/users/{{.value.userId}}") + taggedUsers: [User] @http(path: "/taggedUsers/{{.value.id}}") +} + +type User { + id: Int + name: String +} +``` + +```yml @mock +- request: + method: GET + url: http://jsonplaceholder.typicode.com/posts?id=1 + response: + status: 200 + body: + - id: 1 + userId: 1 + - id: 1 + userId: 1 +- request: + method: GET + url: http://jsonplaceholder.typicode.com/users/1 + expectedHits: 1 + response: + status: 200 + body: + id: 1 + name: Leanne Graham +- request: + method: GET + url: http://jsonplaceholder.typicode.com/taggedUsers/1 + expectedHits: 1 + response: + status: 200 + body: + - id: 2 + name: Ervin Howell + - id: 3 + name: Clementine Bach +``` + +```yml @test +- method: POST + url: http://localhost:8080/graphql + body: + query: query { posts { user { name } taggedUsers } } +``` diff --git a/tests/execution/async-cache-enabled.md b/tests/execution/async-cache-enabled.md new file mode 100644 index 0000000000..f048b3f3b0 --- /dev/null +++ b/tests/execution/async-cache-enabled.md @@ -0,0 +1,55 @@ +# Async Cache Enabled + +```graphql @server +schema + @server(port: 8000, queryValidation: false) + @upstream(baseURL: "http://jsonplaceholder.typicode.com", dedupe: true) { + query: Query +} + +type Query { + posts: [Post] @http(path: "/posts?id=1") +} + +type Post { + id: Int + title: String + body: String + userId: Int! + user: User @http(path: "/users/{{.value.userId}}") +} + +type User { + id: Int + name: String +} +``` + +```yml @mock +- request: + method: GET + url: http://jsonplaceholder.typicode.com/posts?id=1 + response: + status: 200 + body: + - id: 1 + userId: 1 + - id: 1 + userId: 1 +- request: + method: GET + url: http://jsonplaceholder.typicode.com/users/1 + expectedHits: 1 + response: + status: 200 + body: + id: 1 + name: Leanne Graham +``` + +```yml @test +- method: POST + url: http://localhost:8080/graphql + body: + query: query { posts { user { name } } } +``` diff --git a/tests/execution/auth-basic.md b/tests/execution/auth-basic.md index 95f413e2aa..b8af3de0b0 100644 --- a/tests/execution/auth-basic.md +++ b/tests/execution/auth-basic.md @@ -1,7 +1,7 @@ # Auth with BasicAuth ```graphql @server -schema @server(port: 8000, graphiql: true) @link(id: "htpasswd", type: Htpasswd, src: ".htpasswd") { +schema @server(port: 8000) @link(id: "htpasswd", type: Htpasswd, src: ".htpasswd") { query: Query mutation: Mutation } diff --git a/tests/execution/auth-jwt.md b/tests/execution/auth-jwt.md index d91aeb4f22..64f3b53ad8 100644 --- a/tests/execution/auth-jwt.md +++ b/tests/execution/auth-jwt.md @@ -1,7 +1,7 @@ # Auth with JWT loaded from expr ```graphql @server -schema @server(port: 8000, graphiql: true) @link(id: "jwks", type: Jwks, src: "jwks.json") { +schema @server(port: 8000) @link(id: "jwks", type: Jwks, src: "jwks.json") { query: Query mutation: Mutation } diff --git a/tests/execution/auth.md b/tests/execution/auth.md index 78cb3057e7..62c9664897 100644 --- a/tests/execution/auth.md +++ b/tests/execution/auth.md @@ -5,7 +5,11 @@ check_identity: true # auth ```graphql @server -schema @server @upstream @link(id: "htpasswd", src: ".htpasswd", type: Htpasswd) @link(id: "jwks", src: "jwks.json", type: Jwks) { +schema + @server + @upstream + @link(id: "htpasswd", src: ".htpasswd", type: Htpasswd) + @link(id: "jwks", src: "jwks.json", type: Jwks) { query: Query } diff --git a/tests/execution/batching.md b/tests/execution/batching.md index ebe91eecb9..df2813df5e 100644 --- a/tests/execution/batching.md +++ b/tests/execution/batching.md @@ -46,7 +46,7 @@ url: http://jsonplaceholder.typicode.com/users/1 headers: test: test - expectedHits: 2 + expectedHits: 3 response: status: 200 body: diff --git a/tests/execution/call-graphql-datasource.md b/tests/execution/call-graphql-datasource.md index df32c6d7ad..e033de3f63 100644 --- a/tests/execution/call-graphql-datasource.md +++ b/tests/execution/call-graphql-datasource.md @@ -2,7 +2,7 @@ ```graphql @server schema - @server(port: 8000, graphiql: true, hostname: "0.0.0.0") + @server(port: 8000, hostname: "0.0.0.0") @upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: true) { query: Query } @@ -54,7 +54,7 @@ type Post { method: POST url: http://upstream/graphql textBody: '{ "query": "query { user(id: 1) { name } }" }' - expectedHits: 1 + expectedHits: 2 response: status: 200 body: @@ -65,7 +65,7 @@ type Post { method: POST url: http://upstream/graphql textBody: '{ "query": "query { user(id: 2) { name } }" }' - expectedHits: 1 + expectedHits: 2 response: status: 200 body: diff --git a/tests/execution/call-operator.md b/tests/execution/call-operator.md index cfff8e49ce..404ad97a04 100644 --- a/tests/execution/call-operator.md +++ b/tests/execution/call-operator.md @@ -38,7 +38,7 @@ message NewsList { ```graphql @server schema - @server(port: 8000, graphiql: true, hostname: "0.0.0.0") + @server(port: 8000, hostname: "0.0.0.0") @upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: true) @link(id: "news", src: "news.proto", type: Protobuf) { query: Query diff --git a/tests/execution/grpc-batch.md b/tests/execution/grpc-batch.md index 61072baec6..f49eab5a35 100644 --- a/tests/execution/grpc-batch.md +++ b/tests/execution/grpc-batch.md @@ -38,7 +38,7 @@ message NewsList { ```graphql @server schema - @server(port: 8000, graphiql: true) + @server(port: 8000) @upstream(httpCache: true, batch: {delay: 10}) @link(id: "news", src: "news.proto", type: Protobuf) { query: Query diff --git a/tests/execution/grpc-error.md b/tests/execution/grpc-error.md index 2d0ebe2661..270ddbb4d0 100644 --- a/tests/execution/grpc-error.md +++ b/tests/execution/grpc-error.md @@ -38,7 +38,7 @@ message NewsList { ```graphql @server schema - @server(port: 8000, graphiql: true) + @server(port: 8000) @upstream(httpCache: true, batch: {delay: 10}) @link(id: "news", src: "news.proto", type: Protobuf) { query: Query diff --git a/tests/execution/grpc-override-url-from-upstream.md b/tests/execution/grpc-override-url-from-upstream.md index 718d5a04de..d8ba7b17dc 100644 --- a/tests/execution/grpc-override-url-from-upstream.md +++ b/tests/execution/grpc-override-url-from-upstream.md @@ -38,7 +38,7 @@ message NewsList { ```graphql @server schema - @server(port: 8000, graphiql: true) + @server(port: 8000) @upstream(httpCache: true, batch: {delay: 10}, baseURL: "http://not-a-valid-grpc-url.com") @link(id: "news", src: "news.proto", type: Protobuf) { query: Query diff --git a/tests/execution/grpc-proto-with-same-package.md b/tests/execution/grpc-proto-with-same-package.md index 67925aa28e..708864cae0 100644 --- a/tests/execution/grpc-proto-with-same-package.md +++ b/tests/execution/grpc-proto-with-same-package.md @@ -35,7 +35,7 @@ service BarService { ```graphql @server schema - @server(port: 8000, graphiql: true) + @server(port: 8000) @upstream(baseURL: "http://localhost:50051") @link(src: "foo.proto", type: Protobuf) @link(src: "bar.proto", type: Protobuf) { diff --git a/tests/execution/grpc-reflection.md b/tests/execution/grpc-reflection.md index d054bd8399..219432f345 100644 --- a/tests/execution/grpc-reflection.md +++ b/tests/execution/grpc-reflection.md @@ -2,7 +2,7 @@ ```graphql @server schema - @server(port: 8000, graphiql: true) + @server(port: 8000) @upstream(httpCache: true, baseURL: "http://localhost:50051") @link(src: "http://localhost:50051", type: Grpc) { query: Query diff --git a/tests/execution/grpc-simple.md b/tests/execution/grpc-simple.md index c38344903e..7954c246db 100644 --- a/tests/execution/grpc-simple.md +++ b/tests/execution/grpc-simple.md @@ -38,7 +38,7 @@ message NewsList { ```graphql @server schema - @server(port: 8000, graphiql: true) + @server(port: 8000) @upstream(httpCache: true, batch: {delay: 10}) @link(id: "news", src: "news.proto", type: Protobuf) { query: Query diff --git a/tests/execution/grpc-url-from-upstream.md b/tests/execution/grpc-url-from-upstream.md index db2c8ac4af..9437271ca5 100644 --- a/tests/execution/grpc-url-from-upstream.md +++ b/tests/execution/grpc-url-from-upstream.md @@ -38,7 +38,7 @@ message NewsList { ```graphql @server schema - @server(port: 8000, graphiql: true) + @server(port: 8000) @upstream(httpCache: true, batch: {delay: 10}, baseURL: "http://localhost:50051") @link(id: "news", src: "news.proto", type: Protobuf) { query: Query diff --git a/tests/execution/inline-many-list.md b/tests/execution/inline-many-list.md index 719cb48d56..67a140876a 100644 --- a/tests/execution/inline-many-list.md +++ b/tests/execution/inline-many-list.md @@ -19,7 +19,10 @@ type Query { u: U @http(baseURL: "http://jsonplaceholder.typicode.com", path: "/us/1") } -type U @addField(name: "b", path: ["a", "b"]) @addField(name: "c", path: ["a", "c"]) @addField(name: "d", path: ["a", "d"]) { +type U + @addField(name: "b", path: ["a", "b"]) + @addField(name: "c", path: ["a", "c"]) + @addField(name: "d", path: ["a", "d"]) { a: A @modify(omit: true) e: String } diff --git a/tests/execution/inline-many.md b/tests/execution/inline-many.md index 054187d179..b3d2a7d92e 100644 --- a/tests/execution/inline-many.md +++ b/tests/execution/inline-many.md @@ -19,7 +19,10 @@ type Query { user: User @http(baseURL: "http://jsonplaceholder.typicode.com", path: "/users/1") } -type User @addField(name: "city", path: ["address", "city"]) @addField(name: "street", path: ["address", "street"]) @addField(name: "zipcode", path: ["address", "zipcode"]) { +type User + @addField(name: "city", path: ["address", "city"]) + @addField(name: "street", path: ["address", "street"]) + @addField(name: "zipcode", path: ["address", "zipcode"]) { address: Address @modify(omit: true) name: String } diff --git a/tests/execution/io-cache.md b/tests/execution/io-cache.md index 16f3161e15..6bc7d8abb7 100644 --- a/tests/execution/io-cache.md +++ b/tests/execution/io-cache.md @@ -2,7 +2,7 @@ ```graphql @server schema - @server(port: 8000, graphiql: true, hostname: "0.0.0.0") + @server(port: 8000, hostname: "0.0.0.0") @upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: true) { query: Query } @@ -52,14 +52,14 @@ type Post { userId: 2 - request: url: http://jsonplaceholder.typicode.com/users/1 - expectedHits: 1 + expectedHits: 2 response: status: 200 body: name: Leanne Graham - request: url: http://jsonplaceholder.typicode.com/users/2 - expectedHits: 1 + expectedHits: 4 response: status: 200 body: diff --git a/tests/execution/jsonplaceholder-call-post.md b/tests/execution/jsonplaceholder-call-post.md index 0b4674de28..55156b6bd7 100644 --- a/tests/execution/jsonplaceholder-call-post.md +++ b/tests/execution/jsonplaceholder-call-post.md @@ -2,7 +2,7 @@ ```graphql @server schema - @server(port: 8000, graphiql: true, hostname: "0.0.0.0") + @server(port: 8000, hostname: "0.0.0.0") @upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: true, batch: {delay: 100}) { query: Query } diff --git a/tests/execution/omit-many.md b/tests/execution/omit-many.md index b39fc236d7..0a76858fec 100644 --- a/tests/execution/omit-many.md +++ b/tests/execution/omit-many.md @@ -20,7 +20,9 @@ type Query { user: User @http(baseURL: "http://jsonplaceholder.typicode.com", path: "/users/1") } -type User @addField(name: "zipcode", path: ["address", "zipcode"]) @addField(name: "complements", path: ["address", "complements"]) { +type User + @addField(name: "zipcode", path: ["address", "zipcode"]) + @addField(name: "complements", path: ["address", "complements"]) { address: Address @omit name: String } diff --git a/tests/execution/recursive-type-json.md b/tests/execution/recursive-type-json.md index 56848de283..917e3d307d 100644 --- a/tests/execution/recursive-type-json.md +++ b/tests/execution/recursive-type-json.md @@ -61,6 +61,7 @@ - request: method: GET url: https://jsonplaceholder.typicode.com/friends/1 + expectedHits: 2 response: status: 200 body: diff --git a/tests/execution/recursive-types.md b/tests/execution/recursive-types.md index ed21992989..2c55eba84a 100644 --- a/tests/execution/recursive-types.md +++ b/tests/execution/recursive-types.md @@ -28,6 +28,7 @@ type Query { - request: method: GET url: https://jsonplaceholder.typicode.com/friends/1 + expectedHits: 2 response: status: 200 body: diff --git a/tests/execution/ref-other-nested.md b/tests/execution/ref-other-nested.md index 7b104a3e3b..dc031156c7 100644 --- a/tests/execution/ref-other-nested.md +++ b/tests/execution/ref-other-nested.md @@ -66,7 +66,7 @@ - request: method: GET url: https://jsonplaceholder.typicode.com/users/1 - expectedHits: 1 + expectedHits: 2 response: status: 200 body: diff --git a/tests/execution/test-duplicated-link.md b/tests/execution/test-duplicated-link.md index 82440af786..858db7a67b 100644 --- a/tests/execution/test-duplicated-link.md +++ b/tests/execution/test-duplicated-link.md @@ -6,7 +6,7 @@ expect_validation_error: true ```graphql @file:jsonplaceholder.graphql schema - @server(port: 8000, graphiql: true, hostname: "0.0.0.0") + @server(port: 8000, hostname: "0.0.0.0") @upstream(baseURL: "http://jsonplaceholder.typicode.com", httpCache: true, batch: {delay: 100}) { query: Query } diff --git a/tests/execution/test-enum-default.md b/tests/execution/test-enum-default.md index 37c163fc68..ce8a1e161f 100644 --- a/tests/execution/test-enum-default.md +++ b/tests/execution/test-enum-default.md @@ -8,30 +8,30 @@ import "google/protobuf/empty.proto"; package news; enum Status { - PUBLISHED = 0; - DRAFT = 1; - ND = 2; + PUBLISHED = 0; + DRAFT = 1; + NOT_DEFINED = 2; } message News { - int32 id = 1; - Status foo = 5; + int32 id = 1; + Status foo = 5; } service NewsService { - rpc GetAllNews (google.protobuf.Empty) returns (NewsList) {} + rpc GetAllNews (google.protobuf.Empty) returns (NewsList) {} } message NewsList { - repeated News news = 1; + repeated News news = 1; } ``` ```graphql @server # for test upstream server see [repo](https://github.com/tailcallhq/rust-grpc) schema - @server(port: 8080, graphiql: true) + @server(port: 8080) @upstream(baseURL: "http://localhost:50051", httpCache: true, batch: {delay: 10}) @link(id: "news", src: "./service.proto", type: Protobuf) { query: Query @@ -44,7 +44,7 @@ type Query { enum Status { PUBLISHED DRAFT - ND + NOT_DEFINED } type News { diff --git a/tests/execution/test-grpc-group-by.md b/tests/execution/test-grpc-group-by.md index 368808f3b8..f9f95ac2d8 100644 --- a/tests/execution/test-grpc-group-by.md +++ b/tests/execution/test-grpc-group-by.md @@ -42,7 +42,7 @@ message NewsList { ```graphql @server schema - @server(port: 8000, graphiql: true) + @server(port: 8000) @upstream(httpCache: true, batch: {delay: 10}) @link(id: "news", src: "news.proto", type: Protobuf) { query: Query diff --git a/tests/execution/test-grpc-nested-data.md b/tests/execution/test-grpc-nested-data.md index 6a601f3e14..82ff8ee9ad 100644 --- a/tests/execution/test-grpc-nested-data.md +++ b/tests/execution/test-grpc-nested-data.md @@ -42,7 +42,7 @@ message NewsList { ```graphql @server schema - @server(port: 8000, graphiql: true) + @server(port: 8000) @upstream(httpCache: true, batch: {delay: 10}) @link(id: "news", src: "news.proto", type: Protobuf) { query: Query diff --git a/tests/execution/test-grpc.md b/tests/execution/test-grpc.md index 9394cae654..e456b7d424 100644 --- a/tests/execution/test-grpc.md +++ b/tests/execution/test-grpc.md @@ -41,7 +41,10 @@ message NewsList { ``` ```graphql @server -schema @server(port: 8000) @upstream(baseURL: "http://localhost:50051", batch: {delay: 10, headers: [], maxSize: 1000}) @link(id: "news", src: "news.proto", type: Protobuf) { +schema + @server(port: 8000) + @upstream(baseURL: "http://localhost:50051", batch: {delay: 10, headers: [], maxSize: 1000}) + @link(id: "news", src: "news.proto", type: Protobuf) { query: Query } @@ -66,6 +69,7 @@ type NewsData { type Query { news: NewsData! @grpc(method: "news.NewsService.GetAllNews") newsById(news: NewsInput!): News! @grpc(body: "{{.args.news}}", method: "news.NewsService.GetNews") - newsByIdBatch(news: NewsInput!): News! @grpc(body: "{{.args.news}}", batchKey: ["news", "id"], method: "news.NewsService.GetMultipleNews") + newsByIdBatch(news: NewsInput!): News! + @grpc(body: "{{.args.news}}", batchKey: ["news", "id"], method: "news.NewsService.GetMultipleNews") } ``` diff --git a/tests/execution/test-params-as-body.md b/tests/execution/test-params-as-body.md index f9d2c346b3..98aa8a661f 100644 --- a/tests/execution/test-params-as-body.md +++ b/tests/execution/test-params-as-body.md @@ -1,7 +1,7 @@ # Http with args as body ```graphql @server -schema @server(port: 8000, graphiql: true) @upstream(baseURL: "http://jsonplaceholder.typicode.com") { +schema @server(port: 8000) @upstream(baseURL: "http://jsonplaceholder.typicode.com") { query: Query } diff --git a/tests/execution/test-scalars.md b/tests/execution/test-scalars.md index ec54cd9ca5..50f585d165 100644 --- a/tests/execution/test-scalars.md +++ b/tests/execution/test-scalars.md @@ -1,12 +1,13 @@ # test-scalar-email ```graphql @server +# this is already defined scalars in tailcall scalar Email -scalar PhoneNumber -scalar Date -scalar Url -schema @server(port: 8000, graphiql: true, hostname: "localhost") { +# this is custom scalars in config +scalar AnyScalar + +schema @server(port: 8000, hostname: "localhost") { query: Query } @@ -15,6 +16,7 @@ type Query { phone(value: PhoneNumber!): PhoneNumber! @expr(body: "{{.args.value}}") date(value: Date!): Date! @expr(body: "{{.args.value}}") url(value: Url!): Url! @expr(body: "{{.args.value}}") + any(value: AnyScalar!): AnyScalar @expr(body: "{{.args.value}}") } ``` @@ -23,7 +25,7 @@ type Query { - method: POST url: http://localhost:8000/graphql body: - query: '{ email(value: "alo@valid.com") }' + query: '{ email(value: "hello@valid.com") }' - method: POST url: http://localhost:8000/graphql body: @@ -39,12 +41,17 @@ type Query { body: query: '{ url(value: "https://tailcall.run/") }' +- method: POST + url: http://localhost:8000/graphql + body: + query: '{ any1: any(value: { test: "abc" } ), any2: any(value: "string-value") }' + # Invalid value test - method: POST url: http://localhost:8000/graphql body: - query: '{ email(value: "alo@invalid") }' + query: '{ email(value: "hello@invalid") }' - method: POST url: http://localhost:8000/graphql body: @@ -61,5 +68,5 @@ type Query { - method: POST url: http://localhost:8000/graphql body: - query: '{ url(value: "invalidhost") }' + query: '{ url(value: "invalid_host") }' ``` diff --git a/tests/execution/test-set-cookie-headers.md b/tests/execution/test-set-cookie-headers.md index bd5a1e7344..7a0740a62b 100644 --- a/tests/execution/test-set-cookie-headers.md +++ b/tests/execution/test-set-cookie-headers.md @@ -2,7 +2,7 @@ ```graphql @server schema - @server(port: 8080, graphiql: true, hostname: "0.0.0.0", headers: {setCookies: true}) + @server(port: 8080, hostname: "0.0.0.0", headers: {setCookies: true}) @upstream(baseURL: "http://jsonplaceholder.typicode.com") { query: Query } diff --git a/tests/expression_spec.rs b/tests/expression_spec.rs index 45e07d2b3e..05b43aac4f 100644 --- a/tests/expression_spec.rs +++ b/tests/expression_spec.rs @@ -5,10 +5,12 @@ mod tests { use serde_json::json; use tailcall::blueprint::{Blueprint, DynamicValue}; use tailcall::http::RequestContext; - use tailcall::lambda::{Concurrent, EmptyResolverContext, Eval, EvaluationContext, Expression}; + use tailcall::lambda::{ + Concurrent, EmptyResolverContext, Eval, EvaluationContext, EvaluationError, Expression, + }; use tailcall::mustache::Mustache; - async fn eval(expr: &Expression) -> anyhow::Result { + async fn eval(expr: &Expression) -> Result { let runtime = tailcall::cli::runtime::init(&Blueprint::default()); let req_ctx = RequestContext::new(runtime); let res_ctx = EmptyResolverContext {};