From 630254a9e21ed06752f21b2ecff418ec85f5340e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 3 Jun 2024 05:37:18 +0000 Subject: [PATCH 01/19] chore(deps): update dependency tsx to v4.11.2 --- npm/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/npm/package-lock.json b/npm/package-lock.json index 06385852f7..b9b2fb3982 100644 --- a/npm/package-lock.json +++ b/npm/package-lock.json @@ -801,9 +801,9 @@ } }, "node_modules/tsx": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.11.0.tgz", - "integrity": "sha512-vzGGELOgAupsNVssAmZjbUDfdm/pWP4R+Kg8TVdsonxbXk0bEpE1qh0yV6/QxUVXaVlNemgcPajGdJJ82n3stg==", + "version": "4.11.2", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.11.2.tgz", + "integrity": "sha512-V5DL5v1BuItjsQ2FN9+4OjR7n5cr8hSgN+VGmm/fd2/0cgQdBIWHcQ3bFYm/5ZTmyxkTDBUIaRuW2divgfPe0A==", "dev": true, "license": "MIT", "dependencies": { From 977d168c3efb63824615aa16161be7767d9c0656 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 3 Jun 2024 15:38:15 +0000 Subject: [PATCH 02/19] fix(deps): update rust crate rustls to v0.23.9 --- Cargo.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5e609054a0..43dda5a482 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4409,9 +4409,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.8" +version = "0.23.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79adb16721f56eb2d843e67676896a61ce7a0fa622dc18d3e372477a029d2740" +checksum = "a218f0f6d05669de4eabfb24f31ce802035c952429d037507b4a4a39f0e60c5b" dependencies = [ "once_cell", "rustls-pki-types", @@ -5139,7 +5139,7 @@ dependencies = [ "reqwest-middleware", "resource", "rquickjs", - "rustls 0.23.8", + "rustls 0.23.9", "rustls-pemfile 1.0.4", "rustls-pki-types", "schemars", From c2ac96cd7970c46db30d36efba4d4aa3db2cf22d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 3 Jun 2024 19:10:16 +0000 Subject: [PATCH 03/19] fix(deps): update rust crate hyper to v0.14.29 --- Cargo.lock | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 43dda5a482..cfa5edc796 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -563,7 +563,7 @@ dependencies = [ "futures-util", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.28", + "hyper 0.14.29", "itoa", "matchit", "memchr", @@ -2209,7 +2209,7 @@ dependencies = [ "crossbeam-utils", "form_urlencoded", "futures-util", - "hyper 0.14.28", + "hyper 0.14.29", "lazy_static", "levenshtein", "log", @@ -2224,9 +2224,9 @@ dependencies = [ [[package]] name = "hyper" -version = "0.14.28" +version = "0.14.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33" dependencies = [ "bytes", "futures-channel", @@ -2239,7 +2239,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.10", + "socket2 0.5.7", "tokio", "tower-service", "tracing", @@ -2273,7 +2273,7 @@ checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", "http 0.2.12", - "hyper 0.14.28", + "hyper 0.14.29", "rustls 0.21.12", "tokio", "tokio-rustls 0.24.1", @@ -2287,7 +2287,7 @@ checksum = "399c78f9338483cb7e630c8474b07268983c6bd5acee012e4211f9f7bb21b070" dependencies = [ "futures-util", "http 0.2.12", - "hyper 0.14.28", + "hyper 0.14.29", "log", "rustls 0.22.4", "rustls-native-certs", @@ -2302,7 +2302,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ - "hyper 0.14.28", + "hyper 0.14.29", "pin-project-lite", "tokio", "tokio-io-timeout", @@ -4222,7 +4222,7 @@ dependencies = [ "h2", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.28", + "hyper 0.14.29", "hyper-rustls 0.24.2", "ipnet", "js-sys", @@ -5101,7 +5101,7 @@ dependencies = [ "htpasswd-verify", "http-cache-reqwest", "httpmock", - "hyper 0.14.28", + "hyper 0.14.29", "hyper-rustls 0.25.0", "indexmap 2.2.6", "inquire", @@ -5183,7 +5183,7 @@ dependencies = [ "async-graphql-value", "async-trait", "dotenvy", - "hyper 0.14.28", + "hyper 0.14.29", "lambda_http", "lambda_runtime", "reqwest", @@ -5202,7 +5202,7 @@ dependencies = [ "async-std", "async-trait", "console_error_panic_hook", - "hyper 0.14.28", + "hyper 0.14.29", "lazy_static", "protox", "reqwest", @@ -5303,7 +5303,7 @@ version = "0.1.0" dependencies = [ "anyhow", "http-body-util", - "hyper 0.14.28", + "hyper 0.14.29", "hyper-util", "once_cell", "opentelemetry 0.23.0", @@ -5641,7 +5641,7 @@ dependencies = [ "h2", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.28", + "hyper 0.14.29", "hyper-timeout", "percent-encoding", "pin-project", @@ -5707,7 +5707,7 @@ dependencies = [ "futures-util", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.28", + "hyper 0.14.29", "opentelemetry 0.22.0", "pin-project-lite", "tonic", From 2b643a40aeb93c0829e23a505ace4a0a391a3d2b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 3 Jun 2024 22:14:07 +0000 Subject: [PATCH 04/19] chore(deps): update dependency @cloudflare/workers-types to v4.20240603.0 --- tailcall-cloudflare/package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tailcall-cloudflare/package-lock.json b/tailcall-cloudflare/package-lock.json index b092f1efea..2adff9cc77 100644 --- a/tailcall-cloudflare/package-lock.json +++ b/tailcall-cloudflare/package-lock.json @@ -107,9 +107,9 @@ } }, "node_modules/@cloudflare/workers-types": { - "version": "4.20240529.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20240529.0.tgz", - "integrity": "sha512-W5obfjAwCNdYk3feUHtDfUxtTU6WIq83k6gmrLLJv+HkgCkOTwwrDNs+3w1Qln0tMj+FQx/fbwxw3ZuHIoyzGg==", + "version": "4.20240603.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20240603.0.tgz", + "integrity": "sha512-KmsjZcd/dPWM51FoT08cvUGCq9l3nFEriloQ12mcdJPoW911Gi5S/9Cq4xeFvTrtk9TJ2krvxP23IeobecRmOQ==", "dev": true }, "node_modules/@cspotcode/source-map-support": { @@ -2774,9 +2774,9 @@ "optional": true }, "@cloudflare/workers-types": { - "version": "4.20240529.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20240529.0.tgz", - "integrity": "sha512-W5obfjAwCNdYk3feUHtDfUxtTU6WIq83k6gmrLLJv+HkgCkOTwwrDNs+3w1Qln0tMj+FQx/fbwxw3ZuHIoyzGg==", + "version": "4.20240603.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20240603.0.tgz", + "integrity": "sha512-KmsjZcd/dPWM51FoT08cvUGCq9l3nFEriloQ12mcdJPoW911Gi5S/9Cq4xeFvTrtk9TJ2krvxP23IeobecRmOQ==", "dev": true }, "@cspotcode/source-map-support": { From 3dcecf256cd34b700261e0ca24bb06d2d5ef6d38 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 01:35:27 +0000 Subject: [PATCH 05/19] fix(deps): update rust crate opentelemetry-system-metrics to 0.2.0 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cfa5edc796..002c441360 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3356,9 +3356,9 @@ dependencies = [ [[package]] name = "opentelemetry-system-metrics" -version = "0.1.9" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d4b02ba3fc9a6e17b9c6f892f3963224e126a5424360f3fc0fc24c90b109ba5" +checksum = "5cc4b0cee8381e796b720f1e2c8c61b1b7a295ee835daab26dfff3c7137a7bbb" dependencies = [ "eyre", "indexmap 1.9.3", diff --git a/Cargo.toml b/Cargo.toml index ef35da12a7..440fbaf8ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,7 +48,7 @@ opentelemetry-otlp = { version = "0.16.0", features = [ # required to make grpc requests "tls-roots", ], optional = true } -opentelemetry-system-metrics = { version = "0.1.9", optional = true } +opentelemetry-system-metrics = { version = "0.2.0", optional = true } tailcall-http-cache = { path = "tailcall-http-cache", optional = true } tailcall-version = { path = "./tailcall-version", optional = true } From dc797bd5f7140353614ddc357877a8f4e19df99f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 11:02:52 +0000 Subject: [PATCH 06/19] fix(deps): update dependency type-fest to v4.19.0 --- npm/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/npm/package-lock.json b/npm/package-lock.json index b9b2fb3982..29cd51e200 100644 --- a/npm/package-lock.json +++ b/npm/package-lock.json @@ -821,9 +821,9 @@ } }, "node_modules/type-fest": { - "version": "4.18.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.18.3.tgz", - "integrity": "sha512-Q08/0IrpvM+NMY9PA2rti9Jb+JejTddwmwmVQGskAlhtcrw1wsRzoR6ode6mR+OAabNa75w/dxedSUY2mlphaQ==", + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.19.0.tgz", + "integrity": "sha512-CN2l+hWACRiejlnr68vY0/7734Kzu+9+TOslUXbSCQ1ruY9XIHDBSceVXCcHm/oXrdzhtLMMdJEKfemf1yXiZQ==", "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=16" From 05c7f1b6531b5e07bf5a2c059498f0f3a9a17391 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 18:07:09 +0000 Subject: [PATCH 07/19] chore(deps): update dependency miniflare to v3.20240524.2 --- tailcall-cloudflare/package-lock.json | 58 ++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 6 deletions(-) diff --git a/tailcall-cloudflare/package-lock.json b/tailcall-cloudflare/package-lock.json index 2adff9cc77..8d26e9a898 100644 --- a/tailcall-cloudflare/package-lock.json +++ b/tailcall-cloudflare/package-lock.json @@ -1439,9 +1439,9 @@ } }, "node_modules/miniflare": { - "version": "3.20240524.1", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20240524.1.tgz", - "integrity": "sha512-5d3pRxvd5pT7lX1SsBH9+AjXuyHJnChSNOnYhubfi7pxMek4ZfULwhnUmNUp1R7b2xKuzqdFDZa0fsZuUoFxlw==", + "version": "3.20240524.2", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20240524.2.tgz", + "integrity": "sha512-Js+8cB61KJG0z2HuQTPLT9S6FwTBuMZfjtml3azUarLYsCF91N+PhIkvNpbkwCXcfRvscdjJ0RlT6lBQYEuIwA==", "dev": true, "dependencies": { "@cspotcode/source-map-support": "0.8.1", @@ -2668,6 +2668,32 @@ "@esbuild/win32-x64": "0.17.19" } }, + "node_modules/wrangler/node_modules/miniflare": { + "version": "3.20240524.1", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20240524.1.tgz", + "integrity": "sha512-5d3pRxvd5pT7lX1SsBH9+AjXuyHJnChSNOnYhubfi7pxMek4ZfULwhnUmNUp1R7b2xKuzqdFDZa0fsZuUoFxlw==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "0.8.1", + "acorn": "^8.8.0", + "acorn-walk": "^8.2.0", + "capnp-ts": "^0.7.0", + "exit-hook": "^2.2.1", + "glob-to-regexp": "^0.4.1", + "stoppable": "^1.1.0", + "undici": "^5.28.2", + "workerd": "1.20240524.0", + "ws": "^8.11.0", + "youch": "^3.2.2", + "zod": "^3.20.6" + }, + "bin": { + "miniflare": "bootstrap.js" + }, + "engines": { + "node": ">=16.13" + } + }, "node_modules/ws": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", @@ -3612,9 +3638,9 @@ "dev": true }, "miniflare": { - "version": "3.20240524.1", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20240524.1.tgz", - "integrity": "sha512-5d3pRxvd5pT7lX1SsBH9+AjXuyHJnChSNOnYhubfi7pxMek4ZfULwhnUmNUp1R7b2xKuzqdFDZa0fsZuUoFxlw==", + "version": "3.20240524.2", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20240524.2.tgz", + "integrity": "sha512-Js+8cB61KJG0z2HuQTPLT9S6FwTBuMZfjtml3azUarLYsCF91N+PhIkvNpbkwCXcfRvscdjJ0RlT6lBQYEuIwA==", "dev": true, "requires": { "@cspotcode/source-map-support": "0.8.1", @@ -4355,6 +4381,26 @@ "@esbuild/win32-ia32": "0.17.19", "@esbuild/win32-x64": "0.17.19" } + }, + "miniflare": { + "version": "3.20240524.1", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20240524.1.tgz", + "integrity": "sha512-5d3pRxvd5pT7lX1SsBH9+AjXuyHJnChSNOnYhubfi7pxMek4ZfULwhnUmNUp1R7b2xKuzqdFDZa0fsZuUoFxlw==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "0.8.1", + "acorn": "^8.8.0", + "acorn-walk": "^8.2.0", + "capnp-ts": "^0.7.0", + "exit-hook": "^2.2.1", + "glob-to-regexp": "^0.4.1", + "stoppable": "^1.1.0", + "undici": "^5.28.2", + "workerd": "1.20240524.0", + "ws": "^8.11.0", + "youch": "^3.2.2", + "zod": "^3.20.6" + } } } }, From 795568694dff9a0d2b0b65ee266bc8fcabe71ea6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 5 Jun 2024 10:44:22 +0530 Subject: [PATCH 08/19] chore(deps): update actions/cache action to v4 (#2075) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5a75d065e0..ad57ad27eb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -166,7 +166,7 @@ jobs: - name: Cache NASM if: runner.os == 'Windows' - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | nasm-2.16.02 From 0137620290dfe46a4b6193c803a358c07bacc8f5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 18:07:21 +0000 Subject: [PATCH 09/19] chore(deps): update dependency wrangler to v3.59.0 --- tailcall-cloudflare/package-lock.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tailcall-cloudflare/package-lock.json b/tailcall-cloudflare/package-lock.json index 8d26e9a898..3ed32e6238 100644 --- a/tailcall-cloudflare/package-lock.json +++ b/tailcall-cloudflare/package-lock.json @@ -2240,9 +2240,9 @@ } }, "node_modules/wrangler": { - "version": "3.58.0", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.58.0.tgz", - "integrity": "sha512-h9gWER7LXLnmHABDNP1p3aqXtchlvSBN8Dp22ZurnkxaLMZ3L3H1Ze1ftiFSs0VRWv0BUnz7AWIUqZmzuBY4Nw==", + "version": "3.59.0", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.59.0.tgz", + "integrity": "sha512-MLKejazUJrekbD8EnQfN6d7fei+IGnq2aVXeILFDy0aTktVNXKvO+eC+mND1zOr+k0KvQN4sJo8vGwqYoY7btw==", "dev": true, "dependencies": { "@cloudflare/kv-asset-handler": "0.3.2", @@ -2251,7 +2251,7 @@ "blake3-wasm": "^2.1.5", "chokidar": "^3.5.3", "esbuild": "0.17.19", - "miniflare": "3.20240524.1", + "miniflare": "3.20240524.2", "nanoid": "^3.3.3", "path-to-regexp": "^6.2.0", "resolve": "^1.22.8", @@ -4176,9 +4176,9 @@ } }, "wrangler": { - "version": "3.58.0", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.58.0.tgz", - "integrity": "sha512-h9gWER7LXLnmHABDNP1p3aqXtchlvSBN8Dp22ZurnkxaLMZ3L3H1Ze1ftiFSs0VRWv0BUnz7AWIUqZmzuBY4Nw==", + "version": "3.59.0", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.59.0.tgz", + "integrity": "sha512-MLKejazUJrekbD8EnQfN6d7fei+IGnq2aVXeILFDy0aTktVNXKvO+eC+mND1zOr+k0KvQN4sJo8vGwqYoY7btw==", "dev": true, "requires": { "@cloudflare/kv-asset-handler": "0.3.2", @@ -4188,7 +4188,7 @@ "chokidar": "^3.5.3", "esbuild": "0.17.19", "fsevents": "~2.3.2", - "miniflare": "3.20240524.1", + "miniflare": "3.20240524.2", "nanoid": "^3.3.3", "path-to-regexp": "^6.2.0", "resolve": "^1.22.8", From d31b88d7686f7b8b5441cf7e8b8066ca342b4770 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 5 Jun 2024 05:11:19 +0000 Subject: [PATCH 10/19] fix(deps): update rust crate strum_macros to v0.26.4 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 002c441360..8c2bff4ff1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4968,9 +4968,9 @@ dependencies = [ [[package]] name = "strum_macros" -version = "0.26.3" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7993a8e3a9e88a00351486baae9522c91b123a088f76469e5bd5cc17198ea87" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ "heck", "proc-macro2", From d4d04ea86739611d9294615dd4c89c64a14ac744 Mon Sep 17 00:00:00 2001 From: Tushar Mathur Date: Wed, 5 Jun 2024 15:11:56 +0530 Subject: [PATCH 11/19] chore: add IO_ID in caches (#2115) --- benches/data_loader_bench.rs | 3 ++- src/core/graphql/request_template.rs | 6 +++--- src/core/grpc/request_template.rs | 6 +++--- src/core/http/request_context.rs | 8 ++++---- src/core/http/request_template.rs | 13 +++++++------ src/core/ir/cache.rs | 13 ++++++++++++- src/core/ir/io.rs | 4 ++-- src/core/mod.rs | 3 ++- src/core/runtime.rs | 3 ++- tailcall-cloudflare/src/cache.rs | 11 ++++++----- tailcall-cloudflare/src/runtime.rs | 3 ++- 11 files changed, 45 insertions(+), 28 deletions(-) diff --git a/benches/data_loader_bench.rs b/benches/data_loader_bench.rs index 3d7885e308..f2385df093 100644 --- a/benches/data_loader_bench.rs +++ b/benches/data_loader_bench.rs @@ -11,6 +11,7 @@ use hyper::body::Bytes; use reqwest::Request; use tailcall::core::config::Batch; use tailcall::core::http::{DataLoaderRequest, HttpDataLoader, Response}; +use tailcall::core::ir::IoId; use tailcall::core::runtime::TargetRuntime; use tailcall::core::{EnvIO, FileIO, HttpIO}; @@ -50,7 +51,7 @@ impl FileIO for File { struct Cache; #[async_trait::async_trait] impl tailcall::core::Cache for Cache { - type Key = u64; + type Key = IoId; type Value = ConstValue; async fn set<'a>(&'a self, _: Self::Key, _: Self::Value, _: NonZeroU64) -> anyhow::Result<()> { diff --git a/src/core/graphql/request_template.rs b/src/core/graphql/request_template.rs index ff4cd24f9c..525bcd4d19 100644 --- a/src/core/graphql/request_template.rs +++ b/src/core/graphql/request_template.rs @@ -11,7 +11,7 @@ use crate::core::config::{GraphQLOperationType, KeyValue}; use crate::core::has_headers::HasHeaders; use crate::core::helpers::headers::MustacheHeaders; use crate::core::http::Method::POST; -use crate::core::ir::{CacheKey, GraphQLOperationContext}; +use crate::core::ir::{CacheKey, GraphQLOperationContext, IoId}; use crate::core::mustache::Mustache; use crate::core::path::PathGraphql; @@ -127,11 +127,11 @@ impl RequestTemplate { } impl CacheKey for RequestTemplate { - fn cache_key(&self, ctx: &Ctx) -> Option { + fn cache_key(&self, ctx: &Ctx) -> Option { let mut hasher = TailcallHasher::default(); let graphql_query = self.render_graphql_query(ctx); graphql_query.hash(&mut hasher); - Some(hasher.finish()) + Some(IoId::new(hasher.finish())) } } diff --git a/src/core/grpc/request_template.rs b/src/core/grpc/request_template.rs index ed390b1f67..dc907b2598 100644 --- a/src/core/grpc/request_template.rs +++ b/src/core/grpc/request_template.rs @@ -13,7 +13,7 @@ use crate::core::config::GraphQLOperationType; use crate::core::grpc::protobuf::ProtobufOperation; use crate::core::has_headers::HasHeaders; use crate::core::helpers::headers::MustacheHeaders; -use crate::core::ir::CacheKey; +use crate::core::ir::{CacheKey, IoId}; use crate::core::mustache::Mustache; use crate::core::path::PathString; @@ -107,11 +107,11 @@ impl RenderedRequestTemplate { } impl CacheKey for RequestTemplate { - fn cache_key(&self, ctx: &Ctx) -> Option { + fn cache_key(&self, ctx: &Ctx) -> Option { let mut hasher = TailcallHasher::default(); let rendered_req = self.render(ctx).unwrap(); rendered_req.hash(&mut hasher); - Some(hasher.finish()) + Some(IoId::new(hasher.finish())) } } diff --git a/src/core/http/request_context.rs b/src/core/http/request_context.rs index b1c3153a7c..5d2382c1ea 100644 --- a/src/core/http/request_context.rs +++ b/src/core/http/request_context.rs @@ -15,7 +15,7 @@ use crate::core::graphql::GraphqlDataLoader; use crate::core::grpc; use crate::core::grpc::data_loader::GrpcDataLoader; use crate::core::http::{AppContext, DataLoaderRequest, HttpDataLoader}; -use crate::core::ir::EvaluationError; +use crate::core::ir::{EvaluationError, IoId}; use crate::core::runtime::TargetRuntime; #[derive(Setters)] @@ -34,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 { @@ -134,14 +134,14 @@ impl RequestContext { } } - pub async fn cache_get(&self, key: &u64) -> anyhow::Result> { + pub async fn cache_get(&self, key: &IoId) -> anyhow::Result> { self.runtime.cache.get(key).await } #[allow(clippy::too_many_arguments)] pub async fn cache_insert( &self, - key: u64, + key: IoId, value: ConstValue, ttl: NonZeroU64, ) -> anyhow::Result<()> { diff --git a/src/core/http/request_template.rs b/src/core/http/request_template.rs index 8b205b5b74..fb256fbe35 100644 --- a/src/core/http/request_template.rs +++ b/src/core/http/request_template.rs @@ -11,7 +11,7 @@ use crate::core::config::Encoding; use crate::core::endpoint::Endpoint; use crate::core::has_headers::HasHeaders; use crate::core::helpers::headers::MustacheHeaders; -use crate::core::ir::CacheKey; +use crate::core::ir::{CacheKey, IoId}; use crate::core::mustache::Mustache; use crate::core::path::PathString; @@ -225,7 +225,7 @@ impl TryFrom for RequestTemplate { } impl CacheKey for RequestTemplate { - fn cache_key(&self, ctx: &Ctx) -> Option { + fn cache_key(&self, ctx: &Ctx) -> Option { let mut hasher = TailcallHasher::default(); let state = &mut hasher; @@ -251,7 +251,7 @@ impl CacheKey for RequestTemplate { let url = self.create_url(ctx).unwrap(); url.hash(state); - Some(hasher.finish()) + Some(IoId::new(hasher.finish())) } } @@ -736,12 +736,13 @@ mod tests { use crate::core::http::request_template::tests::Context; use crate::core::http::RequestTemplate; - use crate::core::ir::CacheKey; + use crate::core::ir::{CacheKey, IoId}; use crate::core::mustache::Mustache; - fn assert_no_duplicate(arr: [Option; N]) { + fn assert_no_duplicate(arr: [Option; N]) { + let len = arr.len(); let set = HashSet::from(arr); - assert_eq!(arr.len(), set.len()); + assert_eq!(len, set.len()); } #[test] diff --git a/src/core/ir/cache.rs b/src/core/ir/cache.rs index c67286b756..c139c60146 100644 --- a/src/core/ir/cache.rs +++ b/src/core/ir/cache.rs @@ -7,8 +7,19 @@ use async_graphql_value::ConstValue; use super::{Eval, EvaluationContext, EvaluationError, ResolverContextLike, IR}; +#[derive(PartialEq, Eq, Clone, Hash, Debug)] +pub struct IoId(u64); +impl IoId { + pub fn new(id: u64) -> Self { + Self(id) + } + + pub fn as_u64(&self) -> u64 { + self.0 + } +} pub trait CacheKey { - fn cache_key(&self, ctx: &Ctx) -> Option; + fn cache_key(&self, ctx: &Ctx) -> Option; } #[derive(Clone, Debug)] diff --git a/src/core/ir/io.rs b/src/core/ir/io.rs index 7373344a2f..b52df36d30 100644 --- a/src/core/ir/io.rs +++ b/src/core/ir/io.rs @@ -6,7 +6,7 @@ use async_graphql::from_value; use async_graphql_value::ConstValue; use reqwest::Request; -use super::{CacheKey, Eval, EvaluationContext, ResolverContextLike}; +use super::{CacheKey, Eval, EvaluationContext, IoId, ResolverContextLike}; use crate::core::config::group_by::GroupBy; use crate::core::config::GraphQLOperationType; use crate::core::data_loader::{DataLoader, Loader}; @@ -160,7 +160,7 @@ impl IO { } impl<'a, Ctx: ResolverContextLike<'a> + Sync + Send> CacheKey> for IO { - fn cache_key(&self, ctx: &EvaluationContext<'a, Ctx>) -> Option { + fn cache_key(&self, ctx: &EvaluationContext<'a, Ctx>) -> Option { match self { IO::Http { req_template, .. } => req_template.cache_key(ctx), IO::Grpc { req_template, .. } => req_template.cache_key(ctx), diff --git a/src/core/mod.rs b/src/core/mod.rs index f715bc7c52..3e31f41f12 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -44,6 +44,7 @@ use std::num::NonZeroU64; use async_graphql_value::ConstValue; use http::Response; +use ir::IoId; pub use tailcall_macros as macros; pub trait EnvIO: Send + Sync + 'static { @@ -79,7 +80,7 @@ pub trait Cache: Send + Sync { fn hit_rate(&self) -> Option; } -pub type EntityCache = dyn Cache; +pub type EntityCache = dyn Cache; #[async_trait::async_trait] pub trait WorkerIO: Send + Sync + 'static { diff --git a/src/core/runtime.rs b/src/core/runtime.rs index e84c7aae9c..6dcbf0c0db 100644 --- a/src/core/runtime.rs +++ b/src/core/runtime.rs @@ -2,6 +2,7 @@ use std::sync::Arc; use async_graphql_value::ConstValue; +use super::ir::IoId; use crate::core::schema_extension::SchemaExtension; use crate::core::worker::{Command, Event}; use crate::core::{Cache, EnvIO, FileIO, HttpIO, WorkerIO}; @@ -23,7 +24,7 @@ pub struct TargetRuntime { pub file: Arc, /// Cache for storing and retrieving entity data, improving performance and /// reducing external calls. - pub cache: Arc>, + pub cache: Arc>, /// A list of extensions that can be used to extend the runtime's /// functionality or integrate additional features. pub extensions: Arc>, diff --git a/tailcall-cloudflare/src/cache.rs b/tailcall-cloudflare/src/cache.rs index 9cba4063af..53ed9da5bb 100644 --- a/tailcall-cloudflare/src/cache.rs +++ b/tailcall-cloudflare/src/cache.rs @@ -4,6 +4,7 @@ use std::rc::Rc; use anyhow::Result; use async_graphql_value::ConstValue; use serde_json::Value; +use tailcall::core::ir::IoId; use tailcall::core::Cache; use worker::kv::KvStore; @@ -28,14 +29,14 @@ impl CloudflareChronoCache { // TODO: Needs fix #[async_trait::async_trait] impl Cache for CloudflareChronoCache { - type Key = u64; + type Key = IoId; type Value = ConstValue; - async fn set<'a>(&'a self, key: u64, value: ConstValue, ttl: NonZeroU64) -> Result<()> { + async fn set<'a>(&'a self, key: IoId, value: ConstValue, ttl: NonZeroU64) -> Result<()> { let kv_store = self.get_kv()?; let ttl = ttl.get(); async_std::task::spawn_local(async move { kv_store - .put(&key.to_string(), value.to_string()) + .put(&key.as_u64().to_string(), value.to_string()) .map_err(to_anyhow)? .expiration_ttl(ttl) .execute() @@ -45,9 +46,9 @@ impl Cache for CloudflareChronoCache { .await } - async fn get<'a>(&'a self, key: &'a u64) -> Result> { + async fn get<'a>(&'a self, key: &'a IoId) -> Result> { let kv_store = self.get_kv()?; - let key = key.to_string(); + let key = key.as_u64().to_string(); async_std::task::spawn_local(async move { let val = kv_store .get(&key) diff --git a/tailcall-cloudflare/src/runtime.rs b/tailcall-cloudflare/src/runtime.rs index 3451962b30..a966ecfac0 100644 --- a/tailcall-cloudflare/src/runtime.rs +++ b/tailcall-cloudflare/src/runtime.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use anyhow::anyhow; use async_graphql_value::ConstValue; +use tailcall::core::ir::IoId; use tailcall::core::runtime::TargetRuntime; use tailcall::core::{EnvIO, FileIO, HttpIO}; @@ -22,7 +23,7 @@ fn init_http() -> Arc { fn init_cache( env: Rc, -) -> Arc> { +) -> Arc> { Arc::new(cache::CloudflareChronoCache::init(env)) } From 9cdf3ef8b8efcdd5b1c5934a94c6312c7087564e Mon Sep 17 00:00:00 2001 From: Bnchi <77180905+bnchi@users.noreply.github.com> Date: Wed, 5 Jun 2024 12:58:56 +0300 Subject: [PATCH 12/19] fix(config): overflow issue in get_all_used_type_names in case of cyclic type (#2109) --- src/core/config/config.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/core/config/config.rs b/src/core/config/config.rs index d3dc6882cf..ef2e003fa1 100644 --- a/src/core/config/config.rs +++ b/src/core/config/config.rs @@ -790,6 +790,10 @@ impl Config { } while let Some(type_name) = stack.pop() { if let Some(typ) = self.types.get(&type_name) { + if set.contains(&type_name) { + continue; + } + set.insert(type_name); for field in typ.fields.values() { stack.extend(field.args.values().map(|arg| arg.type_of.clone())); @@ -851,4 +855,30 @@ mod tests { )]); assert_eq!(actual, expected); } + + #[test] + fn test_unused_types_with_cyclic_types() { + let config = Config::from_sdl( + " + type Bar {a: Int} + type Foo {a: [Foo]} + + type Query { + foos: [Foo] + } + + schema { + query: Query + } + ", + ) + .to_result() + .unwrap(); + + let actual = config.unused_types(); + let mut expected = HashSet::new(); + expected.insert("Bar".to_string()); + + assert_eq!(actual, expected); + } } From 5fe4adc87439117313493190d9eaa7d237d6a592 Mon Sep 17 00:00:00 2001 From: laststylebender <43403528+laststylebender14@users.noreply.github.com> Date: Wed, 5 Jun 2024 23:23:17 +0530 Subject: [PATCH 13/19] feat: generate tailcall config from rest endpoint. (#1980) Co-authored-by: Ranjit Mahadik <43403528+ranjitmahadik@users.noreply.github.com> Co-authored-by: Tushar Mathur --- Cargo.toml | 5 + benches/from_json_bench.rs | 32 ++ benches/tailcall_benches.rs | 2 + src/cli/command.rs | 2 +- src/cli/tc.rs | 5 +- src/core/config/transformer/mod.rs | 5 + src/core/config/transformer/remove_unused.rs | 18 + ..._type_merger__test__cyclic_merge_case.snap | 20 ++ ...ormer__type_merger__test__type_merger.snap | 22 ++ .../config/transformer/type_merger/mod.rs | 6 + .../transformer/type_merger/pair_map.rs | 25 ++ .../transformer/type_merger/pair_set.rs | 20 ++ .../transformer/type_merger/similarity.rs | 316 ++++++++++++++++++ ..._type_merger__test__cyclic_merge_case.snap | 20 ++ ...erger__type_merger__test__type_merger.snap | 22 ++ .../transformer/type_merger/type_merger.rs | 266 +++++++++++++++ src/core/counter.rs | 15 + src/core/generator/from_json.rs | 45 +++ src/core/generator/generator.rs | 89 ++++- .../json/field_base_url_generator.rs | 152 +++++++++ .../json/http_directive_generator.rs | 172 ++++++++++ src/core/generator/json/mod.rs | 29 ++ src/core/generator/json/query_generator.rs | 96 ++++++ src/core/generator/json/schema_generator.rs | 42 +++ ...__should_add_base_url_for_http_fields.snap | 9 + ...t__should_add_base_url_if_not_present.snap | 9 + ...ator__test__list_json_query_generator.snap | 7 + ...uery_generator__test__query_generator.snap | 7 + ...__query_generator_with_path_variables.snap | 7 + ...st__query_generator_with_query_params.snap | 7 + ...or__test__schema_generator_with_query.snap | 7 + src/core/generator/json/types_generator.rs | 180 ++++++++++ src/core/generator/json/url_utils.rs | 45 +++ src/core/generator/mod.rs | 4 + ...rator__generator__test__dummyjson.com.snap | 64 ++++ ...r__test__jsonplaceholder.typicode.com.snap | 27 ++ ...ll_with_different_domain_rest_api_gen.snap | 81 +++++ ...tor__test__read_all_with_rest_api_gen.snap | 42 +++ src/core/generator/source.rs | 7 + .../tests/fixtures/json/boolean.json | 4 + .../tests/fixtures/json/empty_list.json | 4 + .../json/incompatible_properties.json | 14 + .../json/incompatible_root_object.json | 9 + .../generator/tests/fixtures/json/list.json | 9 + .../json/list_incompatible_object.json | 7 + .../fixtures/json/list_primitive_type.json | 4 + .../tests/fixtures/json/nested_list.json | 26 ++ .../fixtures/json/nested_same_properties.json | 14 + .../generator/tests/fixtures/json/null.json | 4 + .../generator/tests/fixtures/json/number.json | 4 + .../generator/tests/fixtures/json/string.json | 4 + .../generator/tests/json_to_config_spec.rs | 44 +++ .../json_to_config_spec__boolean.json.snap | 11 + .../json_to_config_spec__empty_list.json.snap | 13 + ...ig_spec__incompatible_properties.json.snap | 18 + ...g_spec__incompatible_root_object.json.snap | 13 + .../json_to_config_spec__list.json.snap | 17 + ...g_spec__list_incompatible_object.json.snap | 13 + ...config_spec__list_primitive_type.json.snap | 11 + ...json_to_config_spec__nested_list.json.snap | 26 ++ ...fig_spec__nested_same_properties.json.snap | 29 ++ .../json_to_config_spec__null.json.snap | 11 + .../json_to_config_spec__number.json.snap | 11 + .../json_to_config_spec__string.json.snap | 11 + src/core/helpers/gql_type.rs | 103 ++++++ src/core/helpers/mod.rs | 1 + src/core/mod.rs | 1 + 67 files changed, 2348 insertions(+), 17 deletions(-) create mode 100644 benches/from_json_bench.rs create mode 100644 src/core/config/transformer/remove_unused.rs create mode 100644 src/core/config/transformer/snapshots/tailcall__core__config__transformer__type_merger__test__cyclic_merge_case.snap create mode 100644 src/core/config/transformer/snapshots/tailcall__core__config__transformer__type_merger__test__type_merger.snap create mode 100644 src/core/config/transformer/type_merger/mod.rs create mode 100644 src/core/config/transformer/type_merger/pair_map.rs create mode 100644 src/core/config/transformer/type_merger/pair_set.rs create mode 100644 src/core/config/transformer/type_merger/similarity.rs create mode 100644 src/core/config/transformer/type_merger/snapshots/tailcall__core__config__transformer__type_merger__type_merger__test__cyclic_merge_case.snap create mode 100644 src/core/config/transformer/type_merger/snapshots/tailcall__core__config__transformer__type_merger__type_merger__test__type_merger.snap create mode 100644 src/core/config/transformer/type_merger/type_merger.rs create mode 100644 src/core/counter.rs create mode 100644 src/core/generator/from_json.rs create mode 100644 src/core/generator/json/field_base_url_generator.rs create mode 100644 src/core/generator/json/http_directive_generator.rs create mode 100644 src/core/generator/json/mod.rs create mode 100644 src/core/generator/json/query_generator.rs create mode 100644 src/core/generator/json/schema_generator.rs create mode 100644 src/core/generator/json/snapshots/tailcall__core__generator__json__field_base_url_generator__test__should_add_base_url_for_http_fields.snap create mode 100644 src/core/generator/json/snapshots/tailcall__core__generator__json__field_base_url_generator__test__should_add_base_url_if_not_present.snap create mode 100644 src/core/generator/json/snapshots/tailcall__core__generator__json__query_generator__test__list_json_query_generator.snap create mode 100644 src/core/generator/json/snapshots/tailcall__core__generator__json__query_generator__test__query_generator.snap create mode 100644 src/core/generator/json/snapshots/tailcall__core__generator__json__query_generator__test__query_generator_with_path_variables.snap create mode 100644 src/core/generator/json/snapshots/tailcall__core__generator__json__query_generator__test__query_generator_with_query_params.snap create mode 100644 src/core/generator/json/snapshots/tailcall__core__generator__json__schema_generator__test__schema_generator_with_query.snap create mode 100644 src/core/generator/json/types_generator.rs create mode 100644 src/core/generator/json/url_utils.rs create mode 100644 src/core/generator/snapshots/tailcall__core__generator__generator__test__dummyjson.com.snap create mode 100644 src/core/generator/snapshots/tailcall__core__generator__generator__test__jsonplaceholder.typicode.com.snap create mode 100644 src/core/generator/snapshots/tailcall__core__generator__generator__test__read_all_with_different_domain_rest_api_gen.snap create mode 100644 src/core/generator/snapshots/tailcall__core__generator__generator__test__read_all_with_rest_api_gen.snap create mode 100644 src/core/generator/tests/fixtures/json/boolean.json create mode 100644 src/core/generator/tests/fixtures/json/empty_list.json create mode 100644 src/core/generator/tests/fixtures/json/incompatible_properties.json create mode 100644 src/core/generator/tests/fixtures/json/incompatible_root_object.json create mode 100644 src/core/generator/tests/fixtures/json/list.json create mode 100644 src/core/generator/tests/fixtures/json/list_incompatible_object.json create mode 100644 src/core/generator/tests/fixtures/json/list_primitive_type.json create mode 100644 src/core/generator/tests/fixtures/json/nested_list.json create mode 100644 src/core/generator/tests/fixtures/json/nested_same_properties.json create mode 100644 src/core/generator/tests/fixtures/json/null.json create mode 100644 src/core/generator/tests/fixtures/json/number.json create mode 100644 src/core/generator/tests/fixtures/json/string.json create mode 100644 src/core/generator/tests/json_to_config_spec.rs create mode 100644 src/core/generator/tests/snapshots/json_to_config_spec__boolean.json.snap create mode 100644 src/core/generator/tests/snapshots/json_to_config_spec__empty_list.json.snap create mode 100644 src/core/generator/tests/snapshots/json_to_config_spec__incompatible_properties.json.snap create mode 100644 src/core/generator/tests/snapshots/json_to_config_spec__incompatible_root_object.json.snap create mode 100644 src/core/generator/tests/snapshots/json_to_config_spec__list.json.snap create mode 100644 src/core/generator/tests/snapshots/json_to_config_spec__list_incompatible_object.json.snap create mode 100644 src/core/generator/tests/snapshots/json_to_config_spec__list_primitive_type.json.snap create mode 100644 src/core/generator/tests/snapshots/json_to_config_spec__nested_list.json.snap create mode 100644 src/core/generator/tests/snapshots/json_to_config_spec__nested_same_properties.json.snap create mode 100644 src/core/generator/tests/snapshots/json_to_config_spec__null.json.snap create mode 100644 src/core/generator/tests/snapshots/json_to_config_spec__number.json.snap create mode 100644 src/core/generator/tests/snapshots/json_to_config_spec__string.json.snap create mode 100644 src/core/helpers/gql_type.rs diff --git a/Cargo.toml b/Cargo.toml index 440fbaf8ac..b036ec4471 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -250,3 +250,8 @@ harness = false [[test]] name = "execution_spec" harness = false + +[[test]] +name = "json_to_config_spec" +path = "src/core/generator/tests/json_to_config_spec.rs" +harness = false diff --git a/benches/from_json_bench.rs b/benches/from_json_bench.rs new file mode 100644 index 0000000000..4a0b69ca37 --- /dev/null +++ b/benches/from_json_bench.rs @@ -0,0 +1,32 @@ +use criterion::Criterion; +use hyper::Method; +use serde_json::Value; +use tailcall::cli::runtime::NativeHttp; +use tailcall::core::generator::{from_json, ConfigGenerationRequest}; +use tailcall::core::HttpIO; + +pub fn benchmark_from_json_method(c: &mut Criterion) { + let tokio_runtime = tokio::runtime::Runtime::new().unwrap(); + + let native_http = NativeHttp::init(&Default::default(), &Default::default()); + let request_url = String::from("http://jsonplaceholder.typicode.com/users"); + + let mut reqs = Vec::with_capacity(1); + tokio_runtime.block_on(async { + let request = reqwest::Request::new(Method::GET, request_url.parse().unwrap()); + let result = native_http.execute(request).await.unwrap(); + let body: Value = serde_json::from_slice(&result.body).unwrap(); + reqs.push(body); + }); + + let cfg_gen_reqs = [ConfigGenerationRequest::new( + request_url.parse().unwrap(), + reqs[0].clone(), + )]; + + c.bench_function("from_json_bench", |b| { + b.iter(|| { + let _ = from_json(&cfg_gen_reqs, "Query"); + }); + }); +} diff --git a/benches/tailcall_benches.rs b/benches/tailcall_benches.rs index 09336bcdc4..46066dca1a 100644 --- a/benches/tailcall_benches.rs +++ b/benches/tailcall_benches.rs @@ -1,6 +1,7 @@ use criterion::{criterion_group, criterion_main, Criterion}; mod data_loader_bench; +mod from_json_bench; mod handle_request_bench; mod http_execute_bench; mod impl_path_string_for_evaluation_context; @@ -17,6 +18,7 @@ fn all_benchmarks(c: &mut Criterion) { request_template_bench::benchmark_to_request(c); handle_request_bench::benchmark_handle_request(c); http_execute_bench::benchmark_http_execute_method(c); + from_json_bench::benchmark_from_json_method(c); } criterion_group! { diff --git a/src/cli/command.rs b/src/cli/command.rs index 8326f33ad9..9f652a5c9b 100644 --- a/src/cli/command.rs +++ b/src/cli/command.rs @@ -59,7 +59,7 @@ pub enum Command { Gen { /// Path of the source files separated by spaces if more than one #[arg(required = true)] - file_paths: Vec, + paths: Vec, /// Format of the input file #[clap(short, long)] diff --git a/src/cli/tc.rs b/src/cli/tc.rs index 522957e0aa..75e321223e 100644 --- a/src/cli/tc.rs +++ b/src/cli/tc.rs @@ -20,7 +20,6 @@ use crate::core::generator::Generator; use crate::core::http::API_URL_PREFIX; use crate::core::print_schema; use crate::core::rest::{EndpointSet, Unchecked}; - const FILE_NAME: &str = ".tailcallrc.graphql"; const YML_FILE_NAME: &str = ".graphqlrc.yml"; const JSON_FILE_NAME: &str = ".tailcallrc.schema.json"; @@ -82,10 +81,10 @@ pub async fn run() -> Result<()> { } } Command::Init { folder_path } => init(&folder_path).await, - Command::Gen { file_paths, input, output, query } => { + Command::Gen { paths, input, output, query } => { let generator = Generator::init(runtime); let cfg = generator - .read_all(input, file_paths.as_ref(), query.as_str()) + .read_all(input, paths.as_ref(), query.as_str()) .await?; let config = output.unwrap_or_default().encode(&cfg)?; diff --git a/src/core/config/transformer/mod.rs b/src/core/config/transformer/mod.rs index cb9a33ce61..82362d1ce8 100644 --- a/src/core/config/transformer/mod.rs +++ b/src/core/config/transformer/mod.rs @@ -1,5 +1,10 @@ mod ambiguous_type; +mod remove_unused; +mod type_merger; + pub use ambiguous_type::{AmbiguousType, Resolution}; +pub use remove_unused::RemoveUnused; +pub use type_merger::TypeMerger; use super::Config; use crate::core::valid::{Valid, Validator}; diff --git a/src/core/config/transformer/remove_unused.rs b/src/core/config/transformer/remove_unused.rs new file mode 100644 index 0000000000..0b0c61a361 --- /dev/null +++ b/src/core/config/transformer/remove_unused.rs @@ -0,0 +1,18 @@ +use super::Transform; +use crate::core::config::Config; +use crate::core::valid::Valid; + +/// `RemoveUnused` is responsible for removing unused types from a +/// configuration. +/// +/// It scans the configuration and identifies types that are not referenced +/// elsewhere, effectively cleaning up unused clutter from the configuration. +pub struct RemoveUnused; + +impl Transform for RemoveUnused { + fn transform(&self, mut config: Config) -> Valid { + let unused_types = config.unused_types(); + config = config.remove_types(unused_types); + Valid::succeed(config) + } +} diff --git a/src/core/config/transformer/snapshots/tailcall__core__config__transformer__type_merger__test__cyclic_merge_case.snap b/src/core/config/transformer/snapshots/tailcall__core__config__transformer__type_merger__test__cyclic_merge_case.snap new file mode 100644 index 0000000000..854e8bf2a5 --- /dev/null +++ b/src/core/config/transformer/snapshots/tailcall__core__config__transformer__type_merger__test__cyclic_merge_case.snap @@ -0,0 +1,20 @@ +--- +source: src/core/config/transformer/type_merger.rs +expression: config.to_sdl() +--- +schema @server @upstream { + query: Query +} + +type M1 { + body: String + id: Int + t1: M1 + title: Boolean + userId: Float +} + +type Query { + q1: M1 + q2: M1 +} diff --git a/src/core/config/transformer/snapshots/tailcall__core__config__transformer__type_merger__test__type_merger.snap b/src/core/config/transformer/snapshots/tailcall__core__config__transformer__type_merger__test__type_merger.snap new file mode 100644 index 0000000000..23b14d1c79 --- /dev/null +++ b/src/core/config/transformer/snapshots/tailcall__core__config__transformer__type_merger__test__type_merger.snap @@ -0,0 +1,22 @@ +--- +source: src/core/config/transformer/type_merger.rs +expression: config.to_sdl() +--- +schema @server @upstream { + query: Query +} + +type M1 { + f1: String + f2: Int + f3: Boolean + f4: Float + f5: ID +} + +type Query { + q1: M1 + q2: M1 + q3: M1 + q4: M1 +} diff --git a/src/core/config/transformer/type_merger/mod.rs b/src/core/config/transformer/type_merger/mod.rs new file mode 100644 index 0000000000..b0075e5da0 --- /dev/null +++ b/src/core/config/transformer/type_merger/mod.rs @@ -0,0 +1,6 @@ +mod pair_map; +mod pair_set; +mod similarity; +mod type_merger; + +pub use type_merger::TypeMerger; diff --git a/src/core/config/transformer/type_merger/pair_map.rs b/src/core/config/transformer/type_merger/pair_map.rs new file mode 100644 index 0000000000..06a74f82ce --- /dev/null +++ b/src/core/config/transformer/type_merger/pair_map.rs @@ -0,0 +1,25 @@ +use std::collections::HashMap; +use std::hash::Hash; + +/// +/// A special map that can hold two values of same type as key and any type of +/// value. +#[derive(Default)] +pub struct PairMap { + map: HashMap<(A, A), V>, +} + +impl PairMap { + pub fn add(&mut self, a1: A, a2: A, value: V) { + self.map.insert((a1, a2), value); + } + + pub fn get(&self, a1: &A, a2: &A) -> Option<&V> { + if self.map.contains_key(&(a1.to_owned(), a2.to_owned())) { + return self.map.get(&(a1.to_owned(), a2.to_owned())); + } else if self.map.contains_key(&(a2.to_owned(), a1.to_owned())) { + return self.map.get(&(a2.to_owned(), a1.to_owned())); + } + None + } +} diff --git a/src/core/config/transformer/type_merger/pair_set.rs b/src/core/config/transformer/type_merger/pair_set.rs new file mode 100644 index 0000000000..b20746913b --- /dev/null +++ b/src/core/config/transformer/type_merger/pair_set.rs @@ -0,0 +1,20 @@ +use std::collections::HashSet; +use std::hash::Hash; + +/// +/// A special hashset that can store two values of the same type as a pair. +#[derive(Default)] +pub struct PairSet { + visited: HashSet<(A, A)>, +} + +impl PairSet { + pub fn insert(&mut self, a1: A, a2: A) { + self.visited.insert((a1, a2)); + } + + pub fn contains(&self, a1: &A, a2: &A) -> bool { + self.visited.contains(&(a1.to_owned(), a2.to_owned())) + || self.visited.contains(&(a2.to_owned(), a1.to_owned())) + } +} diff --git a/src/core/config/transformer/type_merger/similarity.rs b/src/core/config/transformer/type_merger/similarity.rs new file mode 100644 index 0000000000..f0f8456899 --- /dev/null +++ b/src/core/config/transformer/type_merger/similarity.rs @@ -0,0 +1,316 @@ +use super::pair_map::PairMap; +use super::pair_set::PairSet; +use crate::core::config::{Config, Type}; + +/// Given Two types,it tells similarity between two types based on a specified +/// threshold. +pub struct Similarity<'a> { + config: &'a Config, + threshold: f32, + type_similarity_cache: PairMap, +} + +impl<'a> Similarity<'a> { + pub fn new(config: &'a Config, thresh: f32) -> Similarity { + Similarity { + config, + threshold: thresh, + type_similarity_cache: PairMap::default(), + } + } + + pub fn similarity( + &mut self, + (type_1_name, type_1): (&str, &Type), + (type_2_name, type_2): (&str, &Type), + ) -> bool { + self.similarity_inner( + (type_1_name, type_1), + (type_2_name, type_2), + &mut PairSet::default(), + ) + } + + fn similarity_inner( + &mut self, + (type_1_name, type_1): (&str, &Type), + (type_2_name, type_2): (&str, &Type), + visited_type: &mut PairSet, + ) -> bool { + if let Some(type_similarity_result) = self + .type_similarity_cache + .get(&type_1_name.to_string(), &type_2_name.to_string()) + { + *type_similarity_result + } else { + let config = self.config; + let mut same_field_count = 0; + + for (field_name_1, field_1) in type_1.fields.iter() { + if let Some(field_2) = type_2.fields.get(field_name_1) { + let field_1_type_of = field_1.type_of.to_owned(); + let field_2_type_of = field_2.type_of.to_owned(); + + if field_1_type_of == field_2_type_of { + same_field_count += 1; // 1 from field_1 + + // 1 from + // field_2 + } else if let Some(type_1) = config.types.get(field_1_type_of.as_str()) { + if let Some(type_2) = config.types.get(field_2_type_of.as_str()) { + if visited_type.contains(&field_1_type_of, &field_2_type_of) { + // it's cyclic type, return true as they're the same. + return true; + } + visited_type + .insert(field_1_type_of.to_owned(), field_2_type_of.to_owned()); + + let is_nested_type_similar = self.similarity_inner( + (&field_1_type_of, type_1), + (&field_2_type_of, type_2), + visited_type, + ); + + same_field_count += if is_nested_type_similar { 1 } else { 0 }; + } + } + } + } + + let total_field_count = + (type_1.fields.len() + type_2.fields.len()) as u32 - same_field_count; + + let is_similar = (same_field_count as f32 / total_field_count as f32) >= self.threshold; + + self.type_similarity_cache.add( + type_1_name.to_owned(), + type_2_name.to_owned(), + is_similar, + ); + + is_similar + } + } +} + +#[cfg(test)] +mod test { + use super::Similarity; + use crate::core::config::{Config, Field, Type}; + + #[test] + fn should_return_false_when_thresh_is_not_met() { + let mut foo1 = Type::default(); + foo1.fields.insert( + "a".to_owned(), + Field { type_of: "Int".to_owned(), ..Default::default() }, + ); + foo1.fields.insert( + "b".to_owned(), + Field { type_of: "String".to_owned(), ..Default::default() }, + ); + foo1.fields.insert( + "c".to_owned(), + Field { type_of: "Bar1".to_owned(), ..Default::default() }, + ); + + let mut foo2 = Type::default(); + foo2.fields.insert( + "a".to_owned(), + Field { type_of: "Int".to_owned(), ..Default::default() }, + ); + foo2.fields.insert( + "b".to_owned(), + Field { type_of: "Float".to_owned(), ..Default::default() }, + ); + foo2.fields.insert( + "c".to_owned(), + Field { type_of: "Bar2".to_owned(), ..Default::default() }, + ); + + let mut bar1 = Type::default(); + bar1.fields.insert( + "a".to_owned(), + Field { type_of: "Int".to_owned(), ..Default::default() }, + ); + bar1.fields.insert( + "c".to_owned(), + Field { type_of: "Float".to_owned(), ..Default::default() }, + ); + + let mut bar2 = Type::default(); + bar2.fields.insert( + "a".to_owned(), + Field { type_of: "Int".to_owned(), ..Default::default() }, + ); + bar2.fields.insert( + "c".to_owned(), + Field { type_of: "String".to_owned(), ..Default::default() }, + ); + + let mut cfg: Config = Config::default(); + cfg.types.insert("Foo1".to_owned(), foo1.to_owned()); + cfg.types.insert("Foo2".to_owned(), foo2.to_owned()); + cfg.types.insert("Bar1".to_owned(), bar1); + cfg.types.insert("Bar2".to_owned(), bar2); + + let mut gen = Similarity::new(&cfg, 0.5); + let is_similar = gen.similarity(("Foo1", &foo1), ("Foo2", &foo2)); + + assert!(!is_similar) + } + + #[test] + fn should_return_true_when_thresh_is_met() { + let mut foo1 = Type::default(); + foo1.fields.insert( + "a".to_owned(), + Field { type_of: "Int".to_owned(), ..Default::default() }, + ); + foo1.fields.insert( + "b".to_owned(), + Field { type_of: "String".to_owned(), ..Default::default() }, + ); + foo1.fields.insert( + "c".to_owned(), + Field { type_of: "Bar1".to_owned(), ..Default::default() }, + ); + + let mut foo2 = Type::default(); + foo2.fields.insert( + "a".to_owned(), + Field { type_of: "Int".to_owned(), ..Default::default() }, + ); + foo2.fields.insert( + "b".to_owned(), + Field { type_of: "Float".to_owned(), ..Default::default() }, + ); + foo2.fields.insert( + "c".to_owned(), + Field { type_of: "Bar2".to_owned(), ..Default::default() }, + ); + + let mut bar1 = Type::default(); + bar1.fields.insert( + "a".to_owned(), + Field { type_of: "Int".to_owned(), ..Default::default() }, + ); + bar1.fields.insert( + "c".to_owned(), + Field { type_of: "Float".to_owned(), ..Default::default() }, + ); + + let mut bar2 = Type::default(); + bar2.fields.insert( + "a".to_owned(), + Field { type_of: "Int".to_owned(), ..Default::default() }, + ); + bar2.fields.insert( + "c".to_owned(), + Field { type_of: "Float".to_owned(), ..Default::default() }, + ); + bar2.fields.insert( + "k".to_owned(), + Field { type_of: "Int".to_owned(), ..Default::default() }, + ); + + let mut cfg: Config = Config::default(); + cfg.types.insert("Foo1".to_owned(), foo1.to_owned()); + cfg.types.insert("Foo2".to_owned(), foo2.to_owned()); + cfg.types.insert("Bar1".to_owned(), bar1); + cfg.types.insert("Bar2".to_owned(), bar2); + + let mut gen = Similarity::new(&cfg, 0.5); + let is_similar = gen.similarity(("Foo1", &foo1), ("Foo2", &foo2)); + + assert!(is_similar) + } + + #[test] + fn test_cyclic_type() { + let mut foo1 = Type::default(); + foo1.fields.insert( + "a".to_owned(), + Field { type_of: "Bar1".to_owned(), ..Default::default() }, + ); + + let mut foo2 = Type::default(); + foo2.fields.insert( + "a".to_owned(), + Field { type_of: "Bar2".to_owned(), ..Default::default() }, + ); + + let mut bar1 = Type::default(); + bar1.fields.insert( + "a".to_owned(), + Field { type_of: "Foo1".to_owned(), ..Default::default() }, + ); + + let mut bar2 = Type::default(); + bar2.fields.insert( + "a".to_owned(), + Field { type_of: "Foo2".to_owned(), ..Default::default() }, + ); + + let mut cfg: Config = Config::default(); + cfg.types.insert("Foo1".to_owned(), foo1.to_owned()); + cfg.types.insert("Foo2".to_owned(), foo2.to_owned()); + cfg.types.insert("Bar1".to_owned(), bar1); + cfg.types.insert("Bar2".to_owned(), bar2); + + let mut gen = Similarity::new(&cfg, 0.8); + let is_similar = gen.similarity(("Foo1", &foo1), ("Foo2", &foo2)); + + assert!(is_similar) + } + + #[test] + fn test_nested_types() { + let mut foo1 = Type::default(); + foo1.fields.insert( + "a".to_owned(), + Field { type_of: "Bar1".to_owned(), ..Default::default() }, + ); + + let mut foo2 = Type::default(); + foo2.fields.insert( + "a".to_owned(), + Field { type_of: "Bar2".to_owned(), ..Default::default() }, + ); + + let mut bar1 = Type::default(); + bar1.fields.insert( + "a".to_owned(), + Field { type_of: "Far1".to_owned(), ..Default::default() }, + ); + + let mut bar2 = Type::default(); + bar2.fields.insert( + "a".to_owned(), + Field { type_of: "Far2".to_owned(), ..Default::default() }, + ); + + let mut far1 = Type::default(); + far1.fields.insert( + "a".to_owned(), + Field { type_of: "Int".to_owned(), ..Default::default() }, + ); + let mut far2 = Type::default(); + far2.fields.insert( + "a".to_owned(), + Field { type_of: "Int".to_owned(), ..Default::default() }, + ); + + let mut cfg: Config = Config::default(); + cfg.types.insert("Foo1".to_owned(), foo1.to_owned()); + cfg.types.insert("Foo2".to_owned(), foo2.to_owned()); + cfg.types.insert("Bar1".to_owned(), bar1); + cfg.types.insert("Bar2".to_owned(), bar2); + cfg.types.insert("Far1".to_owned(), far1); + cfg.types.insert("Far2".to_owned(), far2); + + let mut gen = Similarity::new(&cfg, 0.8); + let is_similar = gen.similarity(("Foo1", &foo1), ("Foo2", &foo2)); + + assert!(is_similar) + } +} diff --git a/src/core/config/transformer/type_merger/snapshots/tailcall__core__config__transformer__type_merger__type_merger__test__cyclic_merge_case.snap b/src/core/config/transformer/type_merger/snapshots/tailcall__core__config__transformer__type_merger__type_merger__test__cyclic_merge_case.snap new file mode 100644 index 0000000000..a72fce576c --- /dev/null +++ b/src/core/config/transformer/type_merger/snapshots/tailcall__core__config__transformer__type_merger__type_merger__test__cyclic_merge_case.snap @@ -0,0 +1,20 @@ +--- +source: src/core/config/transformer/type_merger/type_merger.rs +expression: config.to_sdl() +--- +schema @server @upstream { + query: Query +} + +type M1 { + body: String + id: Int + is_verified: Boolean + t1: M1 + userId: Int +} + +type Query { + q1: M1 + q2: M1 +} diff --git a/src/core/config/transformer/type_merger/snapshots/tailcall__core__config__transformer__type_merger__type_merger__test__type_merger.snap b/src/core/config/transformer/type_merger/snapshots/tailcall__core__config__transformer__type_merger__type_merger__test__type_merger.snap new file mode 100644 index 0000000000..55a3478d19 --- /dev/null +++ b/src/core/config/transformer/type_merger/snapshots/tailcall__core__config__transformer__type_merger__type_merger__test__type_merger.snap @@ -0,0 +1,22 @@ +--- +source: src/core/config/transformer/type_merger/type_merger.rs +expression: config.to_sdl() +--- +schema @server @upstream { + query: Query +} + +type M1 { + f1: String + f2: Int + f3: Boolean + f4: Float + f5: ID +} + +type Query { + q1: M1 + q2: M1 + q3: M1 + q4: M1 +} diff --git a/src/core/config/transformer/type_merger/type_merger.rs b/src/core/config/transformer/type_merger/type_merger.rs new file mode 100644 index 0000000000..09eb0ede6f --- /dev/null +++ b/src/core/config/transformer/type_merger/type_merger.rs @@ -0,0 +1,266 @@ +use std::collections::{HashMap, HashSet}; + +use super::similarity::Similarity; +use crate::core::config::transformer::Transform; +use crate::core::config::{Config, Type}; +use crate::core::valid::Valid; + +pub struct TypeMerger { + /// thresh required for the merging process. + thresh: f32, +} + +impl TypeMerger { + pub fn new(thresh: f32) -> Self { + let mut validated_thresh = thresh; + if !(0.0..=1.0).contains(&thresh) { + validated_thresh = 1.0; + tracing::warn!( + "Invalid threshold value ({:.2}), reverting to default threshold ({:.2}). allowed range is [0.0 - 1.0] inclusive", + thresh, + validated_thresh + ); + } + Self { thresh: validated_thresh } + } +} + +impl Default for TypeMerger { + fn default() -> Self { + Self { thresh: 1.0 } + } +} + +impl TypeMerger { + fn merger(&self, mut merge_counter: u32, mut config: Config) -> Config { + let mut type_to_merge_type_mapping = HashMap::new(); + let mut similar_type_group_list: Vec> = vec![]; + let mut visited_types = HashSet::new(); + let mut i = 0; + let mut stat_gen = Similarity::new(&config, self.thresh); + + // step 1: identify all the types that satisfies the thresh criteria and group + // them. + let query_name = config.schema.query.clone().unwrap_or_default(); + for (type_name_1, type_info_1) in config.types.iter() { + if visited_types.contains(type_name_1) || type_name_1 == query_name.as_str() { + continue; + } + + let mut type_1_sim = HashSet::new(); + type_1_sim.insert(type_name_1.to_string()); + + for (type_name_2, type_info_2) in config.types.iter().skip(i + 1) { + if visited_types.contains(type_name_2) + || type_name_1 == type_name_2 + || type_name_2 == query_name.as_str() + { + continue; + } + let is_similar = + stat_gen.similarity((type_name_1, type_info_1), (type_name_2, type_info_2)); + if is_similar { + visited_types.insert(type_name_2.clone()); + type_1_sim.insert(type_name_2.clone()); + } + } + if type_1_sim.len() > 1 { + similar_type_group_list.push(type_1_sim); + } + + i += 1; + } + + if similar_type_group_list.is_empty() { + return config; + } + + // step 2: merge similar types into single merged type. + for same_types in similar_type_group_list { + let mut merged_into = Type::default(); + let merged_type_name = format!("M{}", merge_counter); + let mut did_we_merge = false; + for type_name in same_types { + if let Some(type_) = config.types.get(type_name.as_str()) { + type_to_merge_type_mapping.insert(type_name.clone(), merged_type_name.clone()); + merged_into = merge_type(type_, merged_into); + did_we_merge = true; + } + } + + if did_we_merge { + config.types.insert(merged_type_name, merged_into); + merge_counter += 1; + } + } + + if type_to_merge_type_mapping.is_empty() { + return config; + } + + // step 3: replace typeof of fields with newly merged types. + for type_info in config.types.values_mut() { + for actual_field in type_info.fields.values_mut() { + if let Some(merged_into_type_name) = + type_to_merge_type_mapping.get(actual_field.type_of.as_str()) + { + actual_field.type_of = merged_into_type_name.to_string(); + } + } + } + + // step 4: remove all merged types. + let unused_types: HashSet<_> = type_to_merge_type_mapping.keys().cloned().collect(); + let repeat_merging = !unused_types.is_empty(); + config = config.remove_types(unused_types); + + if repeat_merging { + return self.merger(merge_counter, config); + } + config + } +} + +fn merge_type(type_: &Type, mut merge_into: Type) -> Type { + merge_into.fields.extend(type_.fields.clone()); + merge_into + .added_fields + .extend(type_.added_fields.iter().cloned()); + merge_into + .implements + .extend(type_.implements.iter().cloned()); + + merge_into +} + +impl Transform for TypeMerger { + fn transform(&self, config: Config) -> Valid { + let config = self.merger(1, config); + Valid::succeed(config) + } +} + +#[cfg(test)] +mod test { + use super::TypeMerger; + use crate::core::config::transformer::Transform; + use crate::core::config::{Config, Field, Type}; + use crate::core::valid::Validator; + + #[test] + fn test_validate_thresh() { + let ty_merger = TypeMerger::default(); + assert_eq!(ty_merger.thresh, 1.0); + + let ty_merger = TypeMerger::new(0.0); + assert_eq!(ty_merger.thresh, 0.0); + + let ty_merger = TypeMerger::new(1.2); + assert_eq!(ty_merger.thresh, 1.0); + + let ty_merger = TypeMerger::new(-0.5); + assert_eq!(ty_merger.thresh, 1.0); + + let ty_merger = TypeMerger::new(0.5); + assert_eq!(ty_merger.thresh, 0.5); + } + + #[test] + fn test_cyclic_merge_case() -> anyhow::Result<()> { + let str_field = Field { type_of: "String".to_owned(), ..Default::default() }; + let int_field = Field { type_of: "Int".to_owned(), ..Default::default() }; + let bool_field = Field { type_of: "Boolean".to_owned(), ..Default::default() }; + + let mut ty1 = Type::default(); + ty1.fields.insert("body".to_string(), str_field.clone()); + ty1.fields.insert("id".to_string(), int_field.clone()); + ty1.fields + .insert("is_verified".to_string(), bool_field.clone()); + ty1.fields.insert("userId".to_string(), int_field.clone()); + + let mut ty2 = Type::default(); + ty2.fields.insert( + "t1".to_string(), + Field { type_of: "T1".to_string(), ..Default::default() }, + ); + ty2.fields + .insert("is_verified".to_string(), bool_field.clone()); + ty2.fields.insert("userId".to_string(), int_field.clone()); + ty2.fields.insert("body".to_string(), str_field.clone()); + + let mut config = Config::default(); + + config.types.insert("T1".to_string(), ty1); + config.types.insert("T2".to_string(), ty2); + + let mut q_type = Type::default(); + q_type.fields.insert( + "q1".to_string(), + Field { type_of: "T1".to_string(), ..Default::default() }, + ); + q_type.fields.insert( + "q2".to_string(), + Field { type_of: "T2".to_string(), ..Default::default() }, + ); + + config.types.insert("Query".to_owned(), q_type); + config = config.query("Query"); + + config = TypeMerger::new(0.5).transform(config).to_result()?; + + insta::assert_snapshot!(config.to_sdl()); + + Ok(()) + } + + #[test] + fn test_type_merger() -> anyhow::Result<()> { + let str_field = Field { type_of: "String".to_owned(), ..Default::default() }; + let int_field = Field { type_of: "Int".to_owned(), ..Default::default() }; + let bool_field = Field { type_of: "Boolean".to_owned(), ..Default::default() }; + let float_field = Field { type_of: "Float".to_owned(), ..Default::default() }; + let id_field = Field { type_of: "ID".to_owned(), ..Default::default() }; + + let mut ty = Type::default(); + ty.fields.insert("f1".to_string(), str_field.clone()); + ty.fields.insert("f2".to_string(), int_field.clone()); + ty.fields.insert("f3".to_string(), bool_field.clone()); + ty.fields.insert("f4".to_string(), float_field.clone()); + ty.fields.insert("f5".to_string(), id_field.clone()); + + let mut config = Config::default(); + config.types.insert("T1".to_string(), ty.clone()); + config.types.insert("T2".to_string(), ty.clone()); + config.types.insert("T3".to_string(), ty.clone()); + config.types.insert("T4".to_string(), ty.clone()); + + let mut q_type = Type::default(); + q_type.fields.insert( + "q1".to_string(), + Field { type_of: "T1".to_string(), ..Default::default() }, + ); + q_type.fields.insert( + "q2".to_string(), + Field { type_of: "T2".to_string(), ..Default::default() }, + ); + q_type.fields.insert( + "q3".to_string(), + Field { type_of: "T3".to_string(), ..Default::default() }, + ); + q_type.fields.insert( + "q4".to_string(), + Field { type_of: "T4".to_string(), ..Default::default() }, + ); + + config.types.insert("Query".to_owned(), q_type); + config = config.query("Query"); + + assert_eq!(config.types.len(), 5); + + config = TypeMerger::new(1.0).transform(config).to_result()?; + + assert_eq!(config.types.len(), 2); + insta::assert_snapshot!(config.to_sdl()); + Ok(()) + } +} diff --git a/src/core/counter.rs b/src/core/counter.rs new file mode 100644 index 0000000000..afb7b2c138 --- /dev/null +++ b/src/core/counter.rs @@ -0,0 +1,15 @@ +use std::cell::Cell; + +#[allow(unused)] +#[derive(Default)] +pub struct Counter(Cell); +impl Counter { + pub fn new(start: usize) -> Self { + Self(Cell::new(start)) + } + pub fn next(&self) -> usize { + let curr = self.0.get(); + self.0.set(curr + 1); + curr + } +} diff --git a/src/core/generator/from_json.rs b/src/core/generator/from_json.rs new file mode 100644 index 0000000000..1ea9372b6e --- /dev/null +++ b/src/core/generator/from_json.rs @@ -0,0 +1,45 @@ +use serde_json::Value; +use url::Url; + +use super::json::{ + FieldBaseUrlGenerator, NameGenerator, QueryGenerator, SchemaGenerator, TypesGenerator, +}; +use crate::core::config::transformer::{RemoveUnused, Transform, TransformerOps, TypeMerger}; +use crate::core::config::Config; +use crate::core::valid::Validator; + +pub struct ConfigGenerationRequest { + url: Url, + resp: Value, +} + +impl ConfigGenerationRequest { + pub fn new(url: Url, resp: Value) -> Self { + Self { url, resp } + } +} + +pub fn from_json( + config_gen_req: &[ConfigGenerationRequest], + query: &str, +) -> anyhow::Result { + let mut config = Config::default(); + let field_name_gen = NameGenerator::new("f"); + let type_name_gen = NameGenerator::new("T"); + + for request in config_gen_req.iter() { + let field_name = field_name_gen.generate_name(); + let query_generator = + QueryGenerator::new(request.resp.is_array(), &request.url, query, &field_name); + + config = TypesGenerator::new(&request.resp, query_generator, &type_name_gen) + .pipe(SchemaGenerator::new(query.to_owned())) + .pipe(FieldBaseUrlGenerator::new(&request.url, query)) + .pipe(RemoveUnused) + .pipe(TypeMerger::new(0.8)) //TODO: take threshold value from user + .transform(config) + .to_result()?; + } + + Ok(config) +} diff --git a/src/core/generator/generator.rs b/src/core/generator/generator.rs index b4c9081284..426a202fcc 100644 --- a/src/core/generator/generator.rs +++ b/src/core/generator/generator.rs @@ -1,10 +1,13 @@ use anyhow::Result; +use futures_util::future::join_all; use prost_reflect::prost_types::FileDescriptorSet; use prost_reflect::DescriptorPool; +use reqwest::Method; +use url::Url; use crate::core::config::{Config, ConfigModule, Link, LinkType}; use crate::core::generator::from_proto::from_proto; -use crate::core::generator::Source; +use crate::core::generator::{from_json, ConfigGenerationRequest, Source}; use crate::core::merge_right::MergeRight; use crate::core::proto_reader::ProtoReader; use crate::core::resource_reader::ResourceReader; @@ -26,12 +29,26 @@ fn resolve_file_descriptor_set(descriptor_set: FileDescriptorSet) -> Result anyhow::Result { + let parsed_url = Url::parse(url)?; + let request = reqwest::Request::new(Method::GET, parsed_url.clone()); + let resp = runtime.http.execute(request).await?; + let body = serde_json::from_slice(&resp.body)?; + Ok(ConfigGenerationRequest::new(parsed_url, body)) +} + pub struct Generator { proto_reader: ProtoReader, + runtime: TargetRuntime, } impl Generator { pub fn init(runtime: TargetRuntime) -> Self { Self { + runtime: runtime.clone(), proto_reader: ProtoReader::init(ResourceReader::cached(runtime.clone()), runtime), } } @@ -39,26 +56,38 @@ impl Generator { pub async fn read_all>( &self, input_source: Source, - files: &[T], + paths: &[T], query: &str, ) -> Result { - let mut links = vec![]; - let proto_metadata = self.proto_reader.read_all(files).await?; + match input_source { + Source::Proto => { + let mut links = vec![]; + let proto_metadata = self.proto_reader.read_all(paths).await?; - let mut config = Config::default(); - for metadata in proto_metadata { - match input_source { - Source::Proto => { + let mut config = Config::default(); + for metadata in proto_metadata { links.push(Link { id: None, src: metadata.path, type_of: LinkType::Protobuf }); let descriptor_set = resolve_file_descriptor_set(metadata.descriptor_set)?; config = config.merge_right(from_proto(&[descriptor_set], query)?); } + + config.links = links; + Ok(ConfigModule::from(config)) + } + Source::Url => { + let results = join_all( + paths + .iter() + .map(|url| fetch_response(url.as_ref(), &self.runtime)), + ) + .await + .into_iter() + .collect::>>()?; + + let config = from_json(&results, query)?; + Ok(ConfigModule::from(config)) } } - - config.links = links; - - Ok(ConfigModule::from(config)) } } @@ -75,7 +104,7 @@ mod test { } #[tokio::test] - async fn test_read_all() { + async fn test_read_all_with_grpc_gen() { let server = start_mock_server(); let runtime = crate::core::runtime::test::init(None); let test_dir = PathBuf::from(tailcall_fixtures::protobuf::SELF); @@ -115,4 +144,38 @@ mod test { assert_eq!(config.links.len(), 3); assert_eq!(config.types.get("Query").unwrap().fields.len(), 8); } + + #[tokio::test] + async fn test_read_all_with_rest_api_gen() { + let runtime = crate::core::runtime::test::init(None); + let generator = Generator::init(runtime); + + let users = "http://jsonplaceholder.typicode.com/users".to_string(); + let user = "http://jsonplaceholder.typicode.com/users/1".to_string(); + + let config = generator + .read_all(Source::Url, &[users, user], "Query") + .await + .unwrap(); + + insta::assert_snapshot!(config.to_sdl()); + } + + #[tokio::test] + async fn test_read_all_with_different_domain_rest_api_gen() { + let runtime = crate::core::runtime::test::init(None); + + let generator = Generator::init(runtime); + + let user_comments = "https://jsonplaceholder.typicode.com/posts/1/comments".to_string(); + let post = "https://jsonplaceholder.typicode.com/posts/1".to_string(); + let laptops = "https://dummyjson.com/products/search?q=Laptop".to_string(); + + let config = generator + .read_all(Source::Url, &[user_comments, post, laptops], "Query") + .await + .unwrap(); + + insta::assert_snapshot!(config.to_sdl()); + } } diff --git a/src/core/generator/json/field_base_url_generator.rs b/src/core/generator/json/field_base_url_generator.rs new file mode 100644 index 0000000000..5ed4998f1a --- /dev/null +++ b/src/core/generator/json/field_base_url_generator.rs @@ -0,0 +1,152 @@ +use url::Url; + +use super::url_utils::extract_base_url; +use crate::core::config::transformer::Transform; +use crate::core::config::Config; +use crate::core::valid::Valid; + +pub struct FieldBaseUrlGenerator<'a> { + url: &'a Url, + query: &'a str, +} + +impl<'a> FieldBaseUrlGenerator<'a> { + pub fn new(url: &'a Url, query: &'a str) -> Self { + Self { url, query } + } +} + +impl Transform for FieldBaseUrlGenerator<'_> { + fn transform(&self, mut config: Config) -> Valid { + let base_url = match extract_base_url(self.url) { + Some(base_url) => base_url, + None => { + return Valid::fail(format!("failed to extract the host url from {} ", self.url)) + } + }; + + if let Some(query_type) = config.types.get_mut(self.query) { + for field in query_type.fields.values_mut() { + field.http = match field.http.clone() { + Some(mut http) => { + if http.base_url.is_none() { + http.base_url = Some(base_url.clone()); + } + Some(http) + } + None => None, + } + } + } + + Valid::succeed(config) + } +} + +#[cfg(test)] +mod test { + use anyhow::Ok; + use url::Url; + + use super::FieldBaseUrlGenerator; + use crate::core::config::transformer::Transform; + use crate::core::config::{Config, Field, Http, Type}; + use crate::core::valid::Validator; + + #[test] + fn should_add_base_url_for_http_fields() -> anyhow::Result<()> { + let url = Url::parse("https://example.com").unwrap(); + let query = "Query"; + let field_base_url_gen = FieldBaseUrlGenerator::new(&url, query); + + let mut config = Config::default(); + let mut query_type = Type::default(); + query_type.fields.insert( + "f1".to_string(), + Field { + type_of: "Int".to_string(), + http: Some(Http { path: "/day".to_string(), ..Default::default() }), + ..Default::default() + }, + ); + query_type.fields.insert( + "f2".to_string(), + Field { + type_of: "String".to_string(), + http: Some(Http { path: "/month".to_string(), ..Default::default() }), + ..Default::default() + }, + ); + query_type.fields.insert( + "f3".to_string(), + Field { + type_of: "String".to_string(), + http: Some(Http { path: "/status".to_string(), ..Default::default() }), + ..Default::default() + }, + ); + config.types.insert("Query".to_string(), query_type); + + config = field_base_url_gen.transform(config).to_result()?; + + insta::assert_snapshot!(config.to_sdl()); + Ok(()) + } + + #[test] + fn should_add_base_url_if_not_present() -> anyhow::Result<()> { + let url = Url::parse("http://localhost:8080").unwrap(); + let query = "Query"; + let field_base_url_gen = FieldBaseUrlGenerator::new(&url, query); + + let mut config = Config::default(); + let mut query_type = Type::default(); + query_type.fields.insert( + "f1".to_string(), + Field { + type_of: "Int".to_string(), + http: Some(Http { + base_url: Some("https://calender.com/api/v1/".to_string()), + path: "/day".to_string(), + ..Default::default() + }), + ..Default::default() + }, + ); + query_type.fields.insert( + "f2".to_string(), + Field { + type_of: "String".to_string(), + http: Some(Http { path: "/month".to_string(), ..Default::default() }), + ..Default::default() + }, + ); + query_type.fields.insert( + "f3".to_string(), + Field { + type_of: "String".to_string(), + http: None, + ..Default::default() + }, + ); + config.types.insert("Query".to_string(), query_type); + + config = field_base_url_gen.transform(config).to_result()?; + + insta::assert_snapshot!(config.to_sdl()); + Ok(()) + } + + #[test] + fn should_not_add_base_url_when_query_not_present() -> anyhow::Result<()> { + let url = Url::parse("https://example.com").unwrap(); + let query = "Query"; + let field_base_url_gen = FieldBaseUrlGenerator::new(&url, query); + assert!(field_base_url_gen + .transform(Default::default()) + .to_result()? + .to_sdl() + .is_empty()); + Ok(()) + } +} diff --git a/src/core/generator/json/http_directive_generator.rs b/src/core/generator/json/http_directive_generator.rs new file mode 100644 index 0000000000..2e33328474 --- /dev/null +++ b/src/core/generator/json/http_directive_generator.rs @@ -0,0 +1,172 @@ +use std::collections::HashSet; + +use regex::Regex; +use url::Url; + +use crate::core::config::{Arg, Field, Http, KeyValue}; +use crate::core::helpers::gql_type::detect_gql_data_type; + +#[derive(Debug)] +struct QueryParamInfo { + key: String, + data_type: String, + is_list: bool, +} + +#[derive(Debug)] +struct UrlUtility<'a>(&'a Url); + +impl<'a> UrlUtility<'a> { + fn new(url: &'a Url) -> Self { + Self(url) + } + + pub fn get_query_params(&self) -> Vec { + let mut queries: Vec = Vec::new(); + let mut seen_keys = HashSet::new(); + let url = self.0; + for (query, value) in url.query_pairs() { + let key = query.to_string(); + let value_str = value.to_string(); + + if seen_keys.contains(&key) { + // Find the existing query and mark it as a list + if let Some(existing_query) = queries.iter_mut().find(|q| q.key == key) { + existing_query.is_list = true; + } + } else { + queries.push(QueryParamInfo { + key: key.clone(), + data_type: detect_gql_data_type(&value_str), + is_list: value_str.contains(','), + }); + seen_keys.insert(key); + } + } + + queries + } +} + +pub struct HttpDirectiveGenerator<'a> { + url: &'a Url, + http: Http, +} + +impl<'a> HttpDirectiveGenerator<'a> { + pub fn new(url: &'a Url) -> Self { + Self { url, http: Http::default() } + } + + fn add_path_variables(&mut self, field: &mut Field) { + let re = Regex::new(r"/(\d+)").unwrap(); + let mut arg_index = 1; + let path_url = self.url.path(); + + let mustache_compatible_url = re.replace_all(path_url, |_: ®ex::Captures| { + let arg_key = format!("p{}", arg_index); + let placeholder = format!("/{{{{.args.{}}}}}", arg_key); + + let arg = Arg { + type_of: "Int".to_string(), + required: true, + ..Default::default() + }; + + field.args.insert(arg_key, arg); + + arg_index += 1; + placeholder + }); + + // add path in http directive. + self.http.path = mustache_compatible_url.to_string(); + } + + fn add_query_variables(&mut self, field: &mut Field) { + let url_utility = UrlUtility::new(self.url); + + for query in url_utility.get_query_params() { + let arg = Arg { + list: query.is_list, + type_of: query.data_type, + required: false, + ..Default::default() + }; + + let value: String = format!("{{{{.args.{}}}}}", query.key); + self.http + .query + .push(KeyValue { key: query.key.clone(), value }); + field.args.insert(query.key, arg); + } + } + + pub fn generate_http_directive(mut self, field: &mut Field) -> Http { + self.add_path_variables(field); + self.add_query_variables(field); + + self.http + } +} + +#[cfg(test)] +mod test { + use url::Url; + + use crate::core::generator::json::http_directive_generator::UrlUtility; + + #[test] + fn test_new_url_query_parser() { + let url = Url::parse( + "http://example.com/path?query1=value1&query2=12&query3=12.3&query4=1,2,4&query5=true", + ) + .unwrap(); + let url_utility = UrlUtility::new(&url); + let query_param_list = url_utility.get_query_params(); + + assert_eq!(query_param_list.len(), 5); + + assert_eq!(query_param_list[0].key, "query1"); + assert_eq!(query_param_list[0].data_type, "String"); + assert!(!query_param_list[0].is_list); + + assert_eq!(query_param_list[1].key, "query2"); + assert_eq!(query_param_list[1].data_type, "Int"); + assert!(!query_param_list[1].is_list); + + assert_eq!(query_param_list[2].key, "query3"); + assert_eq!(query_param_list[2].data_type, "Float"); + assert!(!query_param_list[2].is_list); + + assert_eq!(query_param_list[3].key, "query4"); + assert_eq!(query_param_list[3].data_type, "Int"); + assert!(query_param_list[3].is_list); + + assert_eq!(query_param_list[4].key, "query5"); + assert_eq!(query_param_list[4].data_type, "Boolean"); + assert!(!query_param_list[4].is_list); + + let url = + Url::parse("http://example.com/path?q=1&q=2&q=3&ids=1,2,4&userids[]=1&userids[]=2") + .unwrap(); + let url_utility = UrlUtility::new(&url); + let query_param_list = url_utility.get_query_params(); + + assert_eq!(query_param_list[0].key, "q"); + assert!(query_param_list[0].is_list); + + assert_eq!(query_param_list[1].key, "ids"); + assert!(query_param_list[1].is_list); + + assert_eq!(query_param_list[2].key, "userids[]"); + assert!(query_param_list[2].is_list); + } + + #[test] + fn test_new_url_query_parser_empty() { + let url = Url::parse("http://example.com/path").unwrap(); + let parser = UrlUtility::new(&url); + assert_eq!(parser.get_query_params().len(), 0); + } +} diff --git a/src/core/generator/json/mod.rs b/src/core/generator/json/mod.rs new file mode 100644 index 0000000000..a67776fe3b --- /dev/null +++ b/src/core/generator/json/mod.rs @@ -0,0 +1,29 @@ +mod field_base_url_generator; +mod http_directive_generator; +mod query_generator; +mod schema_generator; +mod types_generator; +mod url_utils; + +pub use field_base_url_generator::FieldBaseUrlGenerator; +pub use query_generator::QueryGenerator; +pub use schema_generator::SchemaGenerator; +pub use types_generator::TypesGenerator; + +use crate::core::counter::Counter; + +pub struct NameGenerator { + counter: Counter, + prefix: String, +} + +impl NameGenerator { + pub fn new(prefix: &str) -> Self { + Self { counter: Counter::new(1), prefix: prefix.to_string() } + } + + pub fn generate_name(&self) -> String { + let id = self.counter.next(); + format!("{}{}", self.prefix, id) + } +} diff --git a/src/core/generator/json/query_generator.rs b/src/core/generator/json/query_generator.rs new file mode 100644 index 0000000000..e3a76b46c9 --- /dev/null +++ b/src/core/generator/json/query_generator.rs @@ -0,0 +1,96 @@ +use url::Url; + +use super::http_directive_generator::HttpDirectiveGenerator; +use super::types_generator::OperationGenerator; +use crate::core::config::{Config, Field, Type}; +use crate::core::valid::Valid; + +pub struct QueryGenerator<'a> { + is_json_list: bool, + url: &'a Url, + query: &'a str, + field_name: &'a str, +} + +impl<'a> QueryGenerator<'a> { + pub fn new(is_json_list: bool, url: &'a Url, query: &'a str, field_name: &'a str) -> Self { + Self { is_json_list, url, query, field_name } + } +} + +impl OperationGenerator for QueryGenerator<'_> { + fn generate(&self, root_type: &str, mut config: Config) -> Valid { + let mut field = Field { + list: self.is_json_list, + type_of: root_type.to_owned(), + ..Default::default() + }; + + // generate required http directive. + let http_directive_gen = HttpDirectiveGenerator::new(self.url); + field.http = Some(http_directive_gen.generate_http_directive(&mut field)); + + // if type is already present, then append the new field to it else create one. + if let Some(type_) = config.types.get_mut(self.query) { + type_.fields.insert(self.field_name.to_owned(), field); + } else { + let mut ty = Type::default(); + ty.fields.insert(self.field_name.to_owned(), field); + config.types.insert(self.query.to_owned(), ty); + } + Valid::succeed(config) + } +} + +#[cfg(test)] +mod test { + use url::Url; + + use super::QueryGenerator; + use crate::core::generator::json::types_generator::OperationGenerator; + use crate::core::valid::Validator; + + #[test] + fn test_list_json_query_generator() -> anyhow::Result<()> { + let url = Url::parse("http://example.com/path").unwrap(); + let query_generator = QueryGenerator::new(true, &url, "Query", "f1"); + let config = query_generator + .generate("T1", Default::default()) + .to_result()?; + insta::assert_snapshot!(config.to_sdl()); + Ok(()) + } + + #[test] + fn test_query_generator() -> anyhow::Result<()> { + let url = Url::parse("http://example.com/path").unwrap(); + let query_generator = QueryGenerator::new(false, &url, "Query", "f1"); + let config = query_generator + .generate("T1", Default::default()) + .to_result()?; + insta::assert_snapshot!(config.to_sdl()); + Ok(()) + } + + #[test] + fn test_query_generator_with_query_params() -> anyhow::Result<()> { + let url = Url::parse("http://example.com/path?q=12&is_verified=true").unwrap(); + let query_generator = QueryGenerator::new(false, &url, "Query", "f1"); + let config = query_generator + .generate("T1", Default::default()) + .to_result()?; + insta::assert_snapshot!(config.to_sdl()); + Ok(()) + } + + #[test] + fn test_query_generator_with_path_variables() -> anyhow::Result<()> { + let url = Url::parse("http://example.com/users/12").unwrap(); + let query_generator = QueryGenerator::new(false, &url, "Query", "f1"); + let config = query_generator + .generate("T1", Default::default()) + .to_result()?; + insta::assert_snapshot!(config.to_sdl()); + Ok(()) + } +} diff --git a/src/core/generator/json/schema_generator.rs b/src/core/generator/json/schema_generator.rs new file mode 100644 index 0000000000..9d1c8ee892 --- /dev/null +++ b/src/core/generator/json/schema_generator.rs @@ -0,0 +1,42 @@ +use crate::core::config::transformer::Transform; +use crate::core::config::Config; +use crate::core::valid::Valid; + +pub struct SchemaGenerator { + query_type: String, +} + +impl SchemaGenerator { + pub fn new(query_type: String) -> Self { + Self { query_type } + } + + pub fn generate_schema(&self, config: &mut Config) { + config.schema.query = Some(self.query_type.to_owned()); + // TODO: add support for mutation and subscription. + } +} + +impl Transform for SchemaGenerator { + fn transform(&self, mut config: Config) -> Valid { + self.generate_schema(&mut config); + Valid::succeed(config) + } +} + +#[cfg(test)] +mod test { + use anyhow::Ok; + + use super::SchemaGenerator; + use crate::core::config::transformer::Transform; + use crate::core::valid::Validator; + + #[test] + fn test_schema_generator_with_query() -> anyhow::Result<()> { + let schema_gen = SchemaGenerator::new("Query".to_owned()); + let config = schema_gen.transform(Default::default()).to_result()?; + insta::assert_snapshot!(config.to_sdl()); + Ok(()) + } +} diff --git a/src/core/generator/json/snapshots/tailcall__core__generator__json__field_base_url_generator__test__should_add_base_url_for_http_fields.snap b/src/core/generator/json/snapshots/tailcall__core__generator__json__field_base_url_generator__test__should_add_base_url_for_http_fields.snap new file mode 100644 index 0000000000..b68924ab19 --- /dev/null +++ b/src/core/generator/json/snapshots/tailcall__core__generator__json__field_base_url_generator__test__should_add_base_url_for_http_fields.snap @@ -0,0 +1,9 @@ +--- +source: src/core/generator/json/field_base_url_generator.rs +expression: config.to_sdl() +--- +type Query { + f1: Int @http(baseURL: "https://example.com", path: "/day") + f2: String @http(baseURL: "https://example.com", path: "/month") + f3: String @http(baseURL: "https://example.com", path: "/status") +} diff --git a/src/core/generator/json/snapshots/tailcall__core__generator__json__field_base_url_generator__test__should_add_base_url_if_not_present.snap b/src/core/generator/json/snapshots/tailcall__core__generator__json__field_base_url_generator__test__should_add_base_url_if_not_present.snap new file mode 100644 index 0000000000..b99bf63db4 --- /dev/null +++ b/src/core/generator/json/snapshots/tailcall__core__generator__json__field_base_url_generator__test__should_add_base_url_if_not_present.snap @@ -0,0 +1,9 @@ +--- +source: src/core/generator/json/field_base_url_generator.rs +expression: config.to_sdl() +--- +type Query { + f1: Int @http(baseURL: "https://calender.com/api/v1/", path: "/day") + f2: String @http(baseURL: "http://localhost:8080", path: "/month") + f3: String +} diff --git a/src/core/generator/json/snapshots/tailcall__core__generator__json__query_generator__test__list_json_query_generator.snap b/src/core/generator/json/snapshots/tailcall__core__generator__json__query_generator__test__list_json_query_generator.snap new file mode 100644 index 0000000000..50a4f150e5 --- /dev/null +++ b/src/core/generator/json/snapshots/tailcall__core__generator__json__query_generator__test__list_json_query_generator.snap @@ -0,0 +1,7 @@ +--- +source: src/core/generator/json/query_generator.rs +expression: config.to_sdl() +--- +type Query { + f1: [T1] @http(path: "/path") +} diff --git a/src/core/generator/json/snapshots/tailcall__core__generator__json__query_generator__test__query_generator.snap b/src/core/generator/json/snapshots/tailcall__core__generator__json__query_generator__test__query_generator.snap new file mode 100644 index 0000000000..c474e39a5a --- /dev/null +++ b/src/core/generator/json/snapshots/tailcall__core__generator__json__query_generator__test__query_generator.snap @@ -0,0 +1,7 @@ +--- +source: src/core/generator/json/query_generator.rs +expression: config.to_sdl() +--- +type Query { + f1: T1 @http(path: "/path") +} diff --git a/src/core/generator/json/snapshots/tailcall__core__generator__json__query_generator__test__query_generator_with_path_variables.snap b/src/core/generator/json/snapshots/tailcall__core__generator__json__query_generator__test__query_generator_with_path_variables.snap new file mode 100644 index 0000000000..7f967ab700 --- /dev/null +++ b/src/core/generator/json/snapshots/tailcall__core__generator__json__query_generator__test__query_generator_with_path_variables.snap @@ -0,0 +1,7 @@ +--- +source: src/core/generator/json/query_generator.rs +expression: config.to_sdl() +--- +type Query { + f1(p1: Int!): T1 @http(path: "/users/{{.args.p1}}") +} diff --git a/src/core/generator/json/snapshots/tailcall__core__generator__json__query_generator__test__query_generator_with_query_params.snap b/src/core/generator/json/snapshots/tailcall__core__generator__json__query_generator__test__query_generator_with_query_params.snap new file mode 100644 index 0000000000..91432b8d4d --- /dev/null +++ b/src/core/generator/json/snapshots/tailcall__core__generator__json__query_generator__test__query_generator_with_query_params.snap @@ -0,0 +1,7 @@ +--- +source: src/core/generator/json/query_generator.rs +expression: config.to_sdl() +--- +type Query { + f1(is_verified: Boolean, q: Int): T1 @http(path: "/path", query: [{key: "q", value: "{{.args.q}}"}, {key: "is_verified", value: "{{.args.is_verified}}"}]) +} diff --git a/src/core/generator/json/snapshots/tailcall__core__generator__json__schema_generator__test__schema_generator_with_query.snap b/src/core/generator/json/snapshots/tailcall__core__generator__json__schema_generator__test__schema_generator_with_query.snap new file mode 100644 index 0000000000..4d97dc91ab --- /dev/null +++ b/src/core/generator/json/snapshots/tailcall__core__generator__json__schema_generator__test__schema_generator_with_query.snap @@ -0,0 +1,7 @@ +--- +source: src/core/generator/json/schema_generator.rs +expression: config.to_sdl() +--- +schema @server @upstream { + query: Query +} diff --git a/src/core/generator/json/types_generator.rs b/src/core/generator/json/types_generator.rs new file mode 100644 index 0000000000..794612a2b6 --- /dev/null +++ b/src/core/generator/json/types_generator.rs @@ -0,0 +1,180 @@ +use serde_json::{Map, Value}; + +use super::NameGenerator; +use crate::core::config::transformer::Transform; +use crate::core::config::{Config, Field, Type}; +use crate::core::helpers::gql_type::{is_primitive, is_valid_field_name, to_gql_type}; +use crate::core::valid::Valid; + +struct JSONValidator; + +impl JSONValidator { + /// checks if given json value is graphql compatible or not. + fn is_graphql_compatible(value: &Value) -> bool { + match value { + Value::Array(json_array) => !json_array.is_empty(), + Value::Object(json_object) => { + !json_object.is_empty() + && !json_object + .keys() + .any(|json_property| !is_valid_field_name(json_property)) + } + _ => true, + } + } +} + +struct TypeMerger; + +impl TypeMerger { + /// given a list of types, merges all fields into single type. + fn merge_fields(type_list: Vec) -> Type { + let mut ty = Type::default(); + + for current_type in type_list { + for (key, new_field) in current_type.fields { + if let Some(existing_field) = ty.fields.get(&key) { + if existing_field.type_of.is_empty() + || existing_field.type_of == "Empty" + || (existing_field.type_of == "Any" && new_field.type_of != "Empty") + { + ty.fields.insert(key, new_field); + } + } else { + ty.fields.insert(key, new_field); + } + } + } + ty + } +} + +pub struct TypesGenerator<'a, T1: OperationGenerator> { + json_value: &'a Value, + operation_generator: T1, + type_name_generator: &'a NameGenerator, +} + +impl<'a, T1> TypesGenerator<'a, T1> +where + T1: OperationGenerator, +{ + pub fn new( + json_value: &'a Value, + operation_generator: T1, + type_name_generator: &'a NameGenerator, + ) -> Self { + Self { json_value, operation_generator, type_name_generator } + } +} + +impl<'a, T1> TypesGenerator<'a, T1> +where + T1: OperationGenerator, +{ + fn generate_scalar(&self, config: &mut Config) -> String { + let any_scalar = "Any"; + if config.types.contains_key(any_scalar) { + return any_scalar.to_string(); + } + config.types.insert(any_scalar.to_string(), Type::default()); + any_scalar.to_string() + } + + fn create_type_from_object( + &self, + json_object: &'a Map, + config: &mut Config, + ) -> Type { + let mut ty = Type::default(); + for (json_property, json_val) in json_object { + let field = if !JSONValidator::is_graphql_compatible(json_val) { + // if object, array is empty or object has in-compatible fields then + // generate scalar for it. + Field { + type_of: self.generate_scalar(config), + list: json_val.is_array(), + ..Default::default() + } + } else { + let mut field = Field::default(); + if is_primitive(json_val) { + field.type_of = to_gql_type(json_val); + } else { + let type_name = self.generate_types(json_val, config); + field.type_of = type_name; + field.list = json_val.is_array() + } + field + }; + ty.fields.insert(json_property.to_string(), field); + } + ty + } + + fn generate_types(&self, json_value: &'a Value, config: &mut Config) -> String { + match json_value { + Value::Array(json_arr) => { + let vec_capacity = json_arr.first().map_or(0, |json_item| { + if json_item.is_object() { + json_arr.len() + } else { + 0 + } + }); + let mut object_types = Vec::<_>::with_capacity(vec_capacity); + for json_item in json_arr { + if let Value::Object(json_obj) = json_item { + if !JSONValidator::is_graphql_compatible(json_item) { + return self.generate_scalar(config); + } + object_types.push(self.create_type_from_object(json_obj, config)); + } else { + return self.generate_types(json_item, config); + } + } + + if !object_types.is_empty() { + // merge the generated types of list into single concrete type. + let merged_type = TypeMerger::merge_fields(object_types); + let generate_type_name = self.type_name_generator.generate_name(); + config + .types + .insert(generate_type_name.to_owned(), merged_type); + return generate_type_name; + } + + // generate a scalar if array is empty. + self.generate_scalar(config) + } + Value::Object(json_obj) => { + if !JSONValidator::is_graphql_compatible(json_value) { + return self.generate_scalar(config); + } + let ty = self.create_type_from_object(json_obj, config); + let generate_type_name = self.type_name_generator.generate_name(); + config.types.insert(generate_type_name.to_owned(), ty); + generate_type_name + } + other => to_gql_type(other), + } + } +} + +impl Transform for TypesGenerator<'_, T1> +where + T1: OperationGenerator, +{ + fn transform(&self, mut config: Config) -> Valid { + let root_type_name = self.generate_types(self.json_value, &mut config); + self.operation_generator + .generate(root_type_name.as_str(), config) + } +} + +/// For generated types we also have to generate the appropriate operation type. +/// OperationGenerator should be implemented by Query, Subscription and +/// Mutation. +pub trait OperationGenerator { + fn generate(&self, root_type: &str, config: Config) -> Valid; +} diff --git a/src/core/generator/json/url_utils.rs b/src/core/generator/json/url_utils.rs new file mode 100644 index 0000000000..8aed633187 --- /dev/null +++ b/src/core/generator/json/url_utils.rs @@ -0,0 +1,45 @@ +use url::Url; + +pub fn extract_base_url(url: &Url) -> Option { + match url.host_str() { + Some(host) => match url.port() { + Some(port) => Some(format!("{}://{}:{}", url.scheme(), host, port)), + None => Some(format!("{}://{}", url.scheme(), host)), + }, + None => None, + } +} + +#[cfg(test)] +mod test { + use url::Url; + + use super::*; + + #[test] + fn test_extract_base_url_with_port() { + let url = Url::parse("http://example.com:8080/path/to/resource").unwrap(); + assert_eq!( + extract_base_url(&url), + Some("http://example.com:8080".to_string()) + ); + } + + #[test] + fn test_extract_base_url_without_port() { + let url = Url::parse("https://subdomain.example.org").unwrap(); + assert_eq!( + extract_base_url(&url), + Some("https://subdomain.example.org".to_string()) + ); + } + + #[test] + fn test_extract_base_url_with_ip_address() { + let url = Url::parse("http://192.168.1.1:8080/path/to/resource").unwrap(); + assert_eq!( + extract_base_url(&url), + Some("http://192.168.1.1:8080".to_string()) + ); + } +} diff --git a/src/core/generator/mod.rs b/src/core/generator/mod.rs index a33e55ace7..ceee1a300a 100644 --- a/src/core/generator/mod.rs +++ b/src/core/generator/mod.rs @@ -1,7 +1,11 @@ +mod from_json; mod from_proto; mod generator; mod graphql_type; +mod json; mod proto; mod source; + +pub use from_json::{from_json, ConfigGenerationRequest}; pub use generator::Generator; pub use source::Source; diff --git a/src/core/generator/snapshots/tailcall__core__generator__generator__test__dummyjson.com.snap b/src/core/generator/snapshots/tailcall__core__generator__generator__test__dummyjson.com.snap new file mode 100644 index 0000000000..5e259f1aee --- /dev/null +++ b/src/core/generator/snapshots/tailcall__core__generator__generator__test__dummyjson.com.snap @@ -0,0 +1,64 @@ +--- +source: src/core/generator/generator.rs +expression: cfg.to_sdl() +--- +schema @server @upstream(baseURL: "https://dummyjson.com") { + query: Query +} + +type Query { + f1(q: String!): T17 @http(path: "/products/search", query: [{key: "q", value: "{{.args.q}}"}]) +} + +type T1 { + depth: Int + height: Int + width: Int +} + +type T16 { + availabilityStatus: String + brand: String + category: String + description: String + dimensions: T1 + discountPercentage: Int + id: Int + images: [String] + meta: T3 + minimumOrderQuantity: Int + price: Int + rating: Int + returnPolicy: String + reviews: [T2] + shippingInformation: String + sku: String + stock: Int + tags: [String] + thumbnail: String + title: String + warrantyInformation: String + weight: Int +} + +type T17 { + limit: Int + products: [T16] + skip: Int + total: Int +} + +type T2 { + comment: String + date: String + rating: Int + reviewerEmail: String + reviewerName: String +} + +type T3 { + barcode: String + createdAt: String + qrCode: String + updatedAt: String +} diff --git a/src/core/generator/snapshots/tailcall__core__generator__generator__test__jsonplaceholder.typicode.com.snap b/src/core/generator/snapshots/tailcall__core__generator__generator__test__jsonplaceholder.typicode.com.snap new file mode 100644 index 0000000000..91b82c90ad --- /dev/null +++ b/src/core/generator/snapshots/tailcall__core__generator__generator__test__jsonplaceholder.typicode.com.snap @@ -0,0 +1,27 @@ +--- +source: src/core/generator/generator.rs +expression: cfg.to_sdl() +--- +schema @server @upstream(baseURL: "https://jsonplaceholder.typicode.com") { + query: Query +} + +type Query { + f1(p1: Int!): [T1] @http(path: "/posts/{{.args.p1}}/comments") + f2(p1: Int!): T2 @http(path: "/posts/{{.args.p1}}") +} + +type T1 { + body: String + email: String + id: Int + name: String + postId: Int +} + +type T2 { + body: String + id: Int + title: String + userId: Int +} diff --git a/src/core/generator/snapshots/tailcall__core__generator__generator__test__read_all_with_different_domain_rest_api_gen.snap b/src/core/generator/snapshots/tailcall__core__generator__generator__test__read_all_with_different_domain_rest_api_gen.snap new file mode 100644 index 0000000000..52766cde5a --- /dev/null +++ b/src/core/generator/snapshots/tailcall__core__generator__generator__test__read_all_with_different_domain_rest_api_gen.snap @@ -0,0 +1,81 @@ +--- +source: src/core/generator/generator.rs +expression: config.to_sdl() +--- +schema @server @upstream { + query: Query +} + +type Query { + f1(p1: Int!): [T1] @http(baseURL: "https://jsonplaceholder.typicode.com", path: "/posts/{{.args.p1}}/comments") + f2(p1: Int!): T2 @http(baseURL: "https://jsonplaceholder.typicode.com", path: "/posts/{{.args.p1}}") + f3(q: String): T19 @http(baseURL: "https://dummyjson.com", path: "/products/search", query: [{key: "q", value: "{{.args.q}}"}]) +} + +type T1 { + body: String + email: String + id: Int + name: String + postId: Int +} + +type T18 { + availabilityStatus: String + brand: String + category: String + description: String + dimensions: T3 + discountPercentage: Int + id: Int + images: [String] + meta: T5 + minimumOrderQuantity: Int + price: Int + rating: Int + returnPolicy: String + reviews: [T4] + shippingInformation: String + sku: String + stock: Int + tags: [String] + thumbnail: String + title: String + warrantyInformation: String + weight: Int +} + +type T19 { + limit: Int + products: [T18] + skip: Int + total: Int +} + +type T2 { + body: String + id: Int + title: String + userId: Int +} + +type T3 { + depth: Int + height: Int + width: Int +} + +type T4 { + comment: String + date: String + rating: Int + reviewerEmail: String + reviewerName: String +} + +type T5 { + barcode: String + createdAt: String + qrCode: String + updatedAt: String +} diff --git a/src/core/generator/snapshots/tailcall__core__generator__generator__test__read_all_with_rest_api_gen.snap b/src/core/generator/snapshots/tailcall__core__generator__generator__test__read_all_with_rest_api_gen.snap new file mode 100644 index 0000000000..9cd0da8269 --- /dev/null +++ b/src/core/generator/snapshots/tailcall__core__generator__generator__test__read_all_with_rest_api_gen.snap @@ -0,0 +1,42 @@ +--- +source: src/core/generator/generator.rs +expression: config.to_sdl() +--- +schema @server @upstream { + query: Query +} + +type M1 { + lat: String + lng: String +} + +type M2 { + city: String + geo: M1 + street: String + suite: String + zipcode: String +} + +type M3 { + bs: String + catchPhrase: String + name: String +} + +type M4 { + address: M2 + company: M3 + email: String + id: Int + name: String + phone: String + username: String + website: String +} + +type Query { + f1: [M4] @http(baseURL: "http://jsonplaceholder.typicode.com", path: "/users") + f2(p1: Int!): M4 @http(baseURL: "http://jsonplaceholder.typicode.com", path: "/users/{{.args.p1}}") +} diff --git a/src/core/generator/source.rs b/src/core/generator/source.rs index 1b815cf0fc..28e601e7a6 100644 --- a/src/core/generator/source.rs +++ b/src/core/generator/source.rs @@ -6,6 +6,7 @@ use thiserror::Error; pub enum Source { #[default] Proto, + Url, } #[derive(Debug, Error, PartialEq)] @@ -18,6 +19,7 @@ impl std::str::FromStr for Source { fn from_str(s: &str) -> Result { match s.to_lowercase().as_str() { "proto" => Ok(Source::Proto), + "url" => Ok(Source::Url), _ => Err(UnsupportedFileFormat(s.to_string())), } } @@ -32,6 +34,11 @@ mod tests { #[test] fn test_from_str() { assert_eq!(Source::from_str("proto"), Ok(Source::Proto)); + assert_eq!(Source::from_str("PROTO"), Ok(Source::Proto)); + + assert_eq!(Source::from_str("url"), Ok(Source::Url)); + assert_eq!(Source::from_str("URL"), Ok(Source::Url)); + assert!(Source::from_str("foo").is_err()); } } diff --git a/src/core/generator/tests/fixtures/json/boolean.json b/src/core/generator/tests/fixtures/json/boolean.json new file mode 100644 index 0000000000..05489b8508 --- /dev/null +++ b/src/core/generator/tests/fixtures/json/boolean.json @@ -0,0 +1,4 @@ +{ + "url": "https://example.com/user/12/online", + "body": true +} diff --git a/src/core/generator/tests/fixtures/json/empty_list.json b/src/core/generator/tests/fixtures/json/empty_list.json new file mode 100644 index 0000000000..c4ff04c276 --- /dev/null +++ b/src/core/generator/tests/fixtures/json/empty_list.json @@ -0,0 +1,4 @@ +{ + "url": "https://example.com/users", + "body": [] +} diff --git a/src/core/generator/tests/fixtures/json/incompatible_properties.json b/src/core/generator/tests/fixtures/json/incompatible_properties.json new file mode 100644 index 0000000000..e363dff9ad --- /dev/null +++ b/src/core/generator/tests/fixtures/json/incompatible_properties.json @@ -0,0 +1,14 @@ +{ + "url": "https://example.com", + "body": { + "colors": [], + "campaignTemplates": { + "10": { + "name": "test" + }, + "15": { + "name": "test" + } + } + } +} diff --git a/src/core/generator/tests/fixtures/json/incompatible_root_object.json b/src/core/generator/tests/fixtures/json/incompatible_root_object.json new file mode 100644 index 0000000000..c600d786ba --- /dev/null +++ b/src/core/generator/tests/fixtures/json/incompatible_root_object.json @@ -0,0 +1,9 @@ +{ + "url": "https://example.com/", + "body": { + "color_names": ["red", "blue"], + "10": ["a", "b", "c"], + "content_type": "application/json", + "other content": "not available" + } +} diff --git a/src/core/generator/tests/fixtures/json/list.json b/src/core/generator/tests/fixtures/json/list.json new file mode 100644 index 0000000000..c66b6f88ce --- /dev/null +++ b/src/core/generator/tests/fixtures/json/list.json @@ -0,0 +1,9 @@ +{ + "url": "https://example.com/users", + "body": [ + {"name": "test", "age": 12}, + {"name": "test-1", "age": 19}, + {"name": "test-3", "age": 21, "adult": true}, + {"name": "test-3", "age": 21} + ] +} diff --git a/src/core/generator/tests/fixtures/json/list_incompatible_object.json b/src/core/generator/tests/fixtures/json/list_incompatible_object.json new file mode 100644 index 0000000000..2839aeb3ee --- /dev/null +++ b/src/core/generator/tests/fixtures/json/list_incompatible_object.json @@ -0,0 +1,7 @@ +{ + "url": "https://example.com/api/v2/users", + "body": [ + {"name": "test-1", "age": 21, "is adult": true}, + {"name": "test-1", "age": 17, "is adult": false} + ] +} diff --git a/src/core/generator/tests/fixtures/json/list_primitive_type.json b/src/core/generator/tests/fixtures/json/list_primitive_type.json new file mode 100644 index 0000000000..c4d7b52c1b --- /dev/null +++ b/src/core/generator/tests/fixtures/json/list_primitive_type.json @@ -0,0 +1,4 @@ +{ + "url": "https://example.com/users", + "body": ["test-1", "test-2", "test-3", "test-4"] +} diff --git a/src/core/generator/tests/fixtures/json/nested_list.json b/src/core/generator/tests/fixtures/json/nested_list.json new file mode 100644 index 0000000000..eb984aac65 --- /dev/null +++ b/src/core/generator/tests/fixtures/json/nested_list.json @@ -0,0 +1,26 @@ +{ + "url": "https://example.com/users?children=true", + "body": { + "people": [ + { + "name": "Alice", + "age": 30, + "children": null + }, + { + "name": "Bob", + "age": 25, + "children": [ + { + "name": "Charlie", + "age": 5 + }, + { + "name": "Diana", + "age": 8 + } + ] + } + ] + } +} diff --git a/src/core/generator/tests/fixtures/json/nested_same_properties.json b/src/core/generator/tests/fixtures/json/nested_same_properties.json new file mode 100644 index 0000000000..ab94b2efee --- /dev/null +++ b/src/core/generator/tests/fixtures/json/nested_same_properties.json @@ -0,0 +1,14 @@ +{ + "url": "https://example.com", + "body": { + "container": { + "name": "Testing", + "container": { + "name": "Testing", + "container": { + "age": 16 + } + } + } + } +} diff --git a/src/core/generator/tests/fixtures/json/null.json b/src/core/generator/tests/fixtures/json/null.json new file mode 100644 index 0000000000..798274ce93 --- /dev/null +++ b/src/core/generator/tests/fixtures/json/null.json @@ -0,0 +1,4 @@ +{ + "url": "https://example.com/users?age=12", + "body": null +} diff --git a/src/core/generator/tests/fixtures/json/number.json b/src/core/generator/tests/fixtures/json/number.json new file mode 100644 index 0000000000..10aa1fc962 --- /dev/null +++ b/src/core/generator/tests/fixtures/json/number.json @@ -0,0 +1,4 @@ +{ + "url": "https://example.com/users?verified_user=true", + "body": 12 +} diff --git a/src/core/generator/tests/fixtures/json/string.json b/src/core/generator/tests/fixtures/json/string.json new file mode 100644 index 0000000000..6b260222a7 --- /dev/null +++ b/src/core/generator/tests/fixtures/json/string.json @@ -0,0 +1,4 @@ +{ + "url": "https://example.com/login/status", + "body": "successfully" +} diff --git a/src/core/generator/tests/json_to_config_spec.rs b/src/core/generator/tests/json_to_config_spec.rs new file mode 100644 index 0000000000..6f3655f12b --- /dev/null +++ b/src/core/generator/tests/json_to_config_spec.rs @@ -0,0 +1,44 @@ +use std::fs; +use std::path::Path; + +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use tailcall::core::generator::{from_json, ConfigGenerationRequest}; +use url::Url; + +#[derive(Serialize, Deserialize)] +struct JsonFixture { + url: String, + body: Value, +} + +datatest_stable::harness!( + run_json_to_config_spec, + "src/core/generator/tests/fixtures/json", + r"^.*\.json" +); + +pub fn run_json_to_config_spec(path: &Path) -> datatest_stable::Result<()> { + let (url, body) = load_json(path)?; + let parsed_url = Url::parse(url.as_str()).unwrap_or_else(|_| { + panic!( + "Failed to parse the url. url: {}, test file: {:?}", + url, path + ) + }); + test_spec(path, parsed_url, body)?; + Ok(()) +} + +fn load_json(path: &Path) -> anyhow::Result<(String, Value)> { + let contents = fs::read_to_string(path)?; + let json_data: JsonFixture = serde_json::from_str(&contents).unwrap(); + Ok((json_data.url, json_data.body)) +} + +fn test_spec(path: &Path, url: Url, body: Value) -> anyhow::Result<()> { + let config = from_json(&[ConfigGenerationRequest::new(url, body)], "Query")?; + let snapshot_name = path.file_name().unwrap().to_str().unwrap(); + insta::assert_snapshot!(snapshot_name, config.to_sdl()); + Ok(()) +} diff --git a/src/core/generator/tests/snapshots/json_to_config_spec__boolean.json.snap b/src/core/generator/tests/snapshots/json_to_config_spec__boolean.json.snap new file mode 100644 index 0000000000..450c3e79cb --- /dev/null +++ b/src/core/generator/tests/snapshots/json_to_config_spec__boolean.json.snap @@ -0,0 +1,11 @@ +--- +source: src/core/generator/tests/json_to_config_spec.rs +expression: config.to_sdl() +--- +schema @server @upstream { + query: Query +} + +type Query { + f1(p1: Int!): Boolean @http(baseURL: "https://example.com", path: "/user/{{.args.p1}}/online") +} diff --git a/src/core/generator/tests/snapshots/json_to_config_spec__empty_list.json.snap b/src/core/generator/tests/snapshots/json_to_config_spec__empty_list.json.snap new file mode 100644 index 0000000000..54213c4fef --- /dev/null +++ b/src/core/generator/tests/snapshots/json_to_config_spec__empty_list.json.snap @@ -0,0 +1,13 @@ +--- +source: src/core/generator/tests/json_to_config_spec.rs +expression: config.to_sdl() +--- +schema @server @upstream { + query: Query +} + +scalar Any + +type Query { + f1: [Any] @http(baseURL: "https://example.com", path: "/users") +} diff --git a/src/core/generator/tests/snapshots/json_to_config_spec__incompatible_properties.json.snap b/src/core/generator/tests/snapshots/json_to_config_spec__incompatible_properties.json.snap new file mode 100644 index 0000000000..35164aee89 --- /dev/null +++ b/src/core/generator/tests/snapshots/json_to_config_spec__incompatible_properties.json.snap @@ -0,0 +1,18 @@ +--- +source: src/core/generator/tests/json_to_config_spec.rs +expression: config.to_sdl() +--- +schema @server @upstream { + query: Query +} + +scalar Any + +type Query { + f1: T1 @http(baseURL: "https://example.com", path: "/") +} + +type T1 { + campaignTemplates: Any + colors: [Any] +} diff --git a/src/core/generator/tests/snapshots/json_to_config_spec__incompatible_root_object.json.snap b/src/core/generator/tests/snapshots/json_to_config_spec__incompatible_root_object.json.snap new file mode 100644 index 0000000000..bdf1a1fccc --- /dev/null +++ b/src/core/generator/tests/snapshots/json_to_config_spec__incompatible_root_object.json.snap @@ -0,0 +1,13 @@ +--- +source: src/core/generator/tests/json_to_config_spec.rs +expression: config.to_sdl() +--- +schema @server @upstream { + query: Query +} + +scalar Any + +type Query { + f1: Any @http(baseURL: "https://example.com", path: "/") +} diff --git a/src/core/generator/tests/snapshots/json_to_config_spec__list.json.snap b/src/core/generator/tests/snapshots/json_to_config_spec__list.json.snap new file mode 100644 index 0000000000..4a2482f33b --- /dev/null +++ b/src/core/generator/tests/snapshots/json_to_config_spec__list.json.snap @@ -0,0 +1,17 @@ +--- +source: src/core/generator/tests/json_to_config_spec.rs +expression: config.to_sdl() +--- +schema @server @upstream { + query: Query +} + +type Query { + f1: [T1] @http(baseURL: "https://example.com", path: "/users") +} + +type T1 { + adult: Boolean + age: Int + name: String +} diff --git a/src/core/generator/tests/snapshots/json_to_config_spec__list_incompatible_object.json.snap b/src/core/generator/tests/snapshots/json_to_config_spec__list_incompatible_object.json.snap new file mode 100644 index 0000000000..24a0e44a69 --- /dev/null +++ b/src/core/generator/tests/snapshots/json_to_config_spec__list_incompatible_object.json.snap @@ -0,0 +1,13 @@ +--- +source: src/core/generator/tests/json_to_config_spec.rs +expression: config.to_sdl() +--- +schema @server @upstream { + query: Query +} + +scalar Any + +type Query { + f1: [Any] @http(baseURL: "https://example.com", path: "/api/v2/users") +} diff --git a/src/core/generator/tests/snapshots/json_to_config_spec__list_primitive_type.json.snap b/src/core/generator/tests/snapshots/json_to_config_spec__list_primitive_type.json.snap new file mode 100644 index 0000000000..672e9338fc --- /dev/null +++ b/src/core/generator/tests/snapshots/json_to_config_spec__list_primitive_type.json.snap @@ -0,0 +1,11 @@ +--- +source: src/core/generator/tests/json_to_config_spec.rs +expression: config.to_sdl() +--- +schema @server @upstream { + query: Query +} + +type Query { + f1: [String] @http(baseURL: "https://example.com", path: "/users") +} diff --git a/src/core/generator/tests/snapshots/json_to_config_spec__nested_list.json.snap b/src/core/generator/tests/snapshots/json_to_config_spec__nested_list.json.snap new file mode 100644 index 0000000000..c094f624fb --- /dev/null +++ b/src/core/generator/tests/snapshots/json_to_config_spec__nested_list.json.snap @@ -0,0 +1,26 @@ +--- +source: src/core/generator/tests/json_to_config_spec.rs +expression: config.to_sdl() +--- +schema @server @upstream { + query: Query +} + +type Query { + f1(children: Boolean): T3 @http(baseURL: "https://example.com", path: "/users", query: [{key: "children", value: "{{.args.children}}"}]) +} + +type T1 { + age: Int + name: String +} + +type T2 { + age: Int + children: [T1] + name: String +} + +type T3 { + people: [T2] +} diff --git a/src/core/generator/tests/snapshots/json_to_config_spec__nested_same_properties.json.snap b/src/core/generator/tests/snapshots/json_to_config_spec__nested_same_properties.json.snap new file mode 100644 index 0000000000..a4e95e0e1d --- /dev/null +++ b/src/core/generator/tests/snapshots/json_to_config_spec__nested_same_properties.json.snap @@ -0,0 +1,29 @@ +--- +source: src/core/generator/tests/json_to_config_spec.rs +expression: config.to_sdl() +--- +schema @server @upstream { + query: Query +} + +type Query { + f1: T4 @http(baseURL: "https://example.com", path: "/") +} + +type T1 { + age: Int +} + +type T2 { + container: T1 + name: String +} + +type T3 { + container: T2 + name: String +} + +type T4 { + container: T3 +} diff --git a/src/core/generator/tests/snapshots/json_to_config_spec__null.json.snap b/src/core/generator/tests/snapshots/json_to_config_spec__null.json.snap new file mode 100644 index 0000000000..84405b9aa4 --- /dev/null +++ b/src/core/generator/tests/snapshots/json_to_config_spec__null.json.snap @@ -0,0 +1,11 @@ +--- +source: src/core/generator/tests/json_to_config_spec.rs +expression: config.to_sdl() +--- +schema @server @upstream { + query: Query +} + +type Query { + f1(age: Int): Empty @http(baseURL: "https://example.com", path: "/users", query: [{key: "age", value: "{{.args.age}}"}]) +} diff --git a/src/core/generator/tests/snapshots/json_to_config_spec__number.json.snap b/src/core/generator/tests/snapshots/json_to_config_spec__number.json.snap new file mode 100644 index 0000000000..bfd297cbe0 --- /dev/null +++ b/src/core/generator/tests/snapshots/json_to_config_spec__number.json.snap @@ -0,0 +1,11 @@ +--- +source: src/core/generator/tests/json_to_config_spec.rs +expression: config.to_sdl() +--- +schema @server @upstream { + query: Query +} + +type Query { + f1(verified_user: Boolean): Int @http(baseURL: "https://example.com", path: "/users", query: [{key: "verified_user", value: "{{.args.verified_user}}"}]) +} diff --git a/src/core/generator/tests/snapshots/json_to_config_spec__string.json.snap b/src/core/generator/tests/snapshots/json_to_config_spec__string.json.snap new file mode 100644 index 0000000000..fda13e7d27 --- /dev/null +++ b/src/core/generator/tests/snapshots/json_to_config_spec__string.json.snap @@ -0,0 +1,11 @@ +--- +source: src/core/generator/tests/json_to_config_spec.rs +expression: config.to_sdl() +--- +schema @server @upstream { + query: Query +} + +type Query { + f1: String @http(baseURL: "https://example.com", path: "/login/status") +} diff --git a/src/core/helpers/gql_type.rs b/src/core/helpers/gql_type.rs new file mode 100644 index 0000000000..46bc24b32f --- /dev/null +++ b/src/core/helpers/gql_type.rs @@ -0,0 +1,103 @@ +use regex::Regex; +use serde_json::Value; + +pub fn detect_gql_data_type(value: &str) -> String { + let trimmed_value = value.trim(); + + if trimmed_value.parse::().is_ok() { + "Int".to_string() + } else if trimmed_value.parse::().is_ok() { + "Float".to_string() + } else if trimmed_value.parse::().is_ok() { + "Boolean".to_string() + } else if trimmed_value.contains(',') { + let first_value = trimmed_value.split(',').next().unwrap_or(""); + detect_gql_data_type(first_value) + } else { + "String".to_string() + } +} + +pub fn is_valid_field_name(property_name: &str) -> bool { + let gql_field_name_validator: Regex = Regex::new(r"^[a-zA-Z][a-zA-Z0-9_]*$").unwrap(); + gql_field_name_validator.is_match(property_name) +} + +pub fn to_gql_type(value: &Value) -> String { + match value { + Value::Null => "Empty", + Value::Bool(_) => "Boolean", + Value::Number(_) => "Int", + Value::String(_) => "String", + Value::Array(_) => "List", + Value::Object(_) => "Object", + } + .to_string() +} + +pub fn is_primitive(value: &Value) -> bool { + let value_type = to_gql_type(value); + value_type != "List" && value_type != "Object" +} + +#[cfg(test)] +mod test { + use serde_json::{json, Value}; + + use super::{detect_gql_data_type, is_primitive, is_valid_field_name, to_gql_type}; + #[test] + fn test_detect_gql_data_type() { + assert_eq!(detect_gql_data_type("42"), "Int"); + assert_eq!(detect_gql_data_type("3.14"), "Float"); + assert_eq!(detect_gql_data_type("true"), "Boolean"); + assert_eq!(detect_gql_data_type("false"), "Boolean"); + assert_eq!(detect_gql_data_type("1,2,3"), "Int"); + assert_eq!(detect_gql_data_type("a,b,c"), "String"); + assert_eq!(detect_gql_data_type("hello"), "String"); + } + + #[test] + fn test_is_valid_field_name() { + assert!(!is_valid_field_name("first name")); + assert!(!is_valid_field_name("10")); + assert!(!is_valid_field_name("$10")); + assert!(!is_valid_field_name("#10")); + + assert!(is_valid_field_name("firstName")); + assert!(is_valid_field_name("lastname")); + assert!(is_valid_field_name("lastname1")); + assert!(is_valid_field_name("lastname2")); + assert!(is_valid_field_name("last_name")); + } + + #[test] + fn test_to_gql_type() { + assert_eq!(to_gql_type(&json!("Testing")), "String"); + assert_eq!(to_gql_type(&json!(12)), "Int"); + assert_eq!(to_gql_type(&json!(12.3)), "Int"); + assert_eq!(to_gql_type(&json!(-12)), "Int"); + assert_eq!(to_gql_type(&json!(-12.2)), "Int"); + assert_eq!(to_gql_type(&json!(true)), "Boolean"); + assert_eq!(to_gql_type(&json!(false)), "Boolean"); + assert_eq!(to_gql_type(&json!([1, 2, 3])), "List"); + assert_eq!(to_gql_type(&json!({"name":"test", "age": 12})), "Object"); + assert_eq!(to_gql_type(&Value::Null), "Empty"); + + assert_eq!(to_gql_type(&json!([])), "List"); + assert_eq!(to_gql_type(&json!({})), "Object"); + } + + #[test] + fn test_is_primitive() { + assert!(is_primitive(&json!("Testing"))); + assert!(is_primitive(&json!(12))); + assert!(is_primitive(&json!(12.3))); + assert!(is_primitive(&json!(-12))); + assert!(is_primitive(&json!(-12.2))); + assert!(is_primitive(&json!(true))); + assert!(is_primitive(&json!(false))); + + assert!(!is_primitive(&json!([1, 2, 3]))); + assert!(!is_primitive(&json!({"name":"test", "age": 12}))); + } +} diff --git a/src/core/helpers/mod.rs b/src/core/helpers/mod.rs index 1d82ddf4a8..cb961846d2 100644 --- a/src/core/helpers/mod.rs +++ b/src/core/helpers/mod.rs @@ -1,4 +1,5 @@ pub mod body; +pub mod gql_type; pub mod headers; pub mod url; pub mod value; diff --git a/src/core/mod.rs b/src/core/mod.rs index 3e31f41f12..de4bd1dc72 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -8,6 +8,7 @@ mod auth; pub mod blueprint; pub mod cache; pub mod config; +mod counter; pub mod data_loader; pub mod directive; pub mod document; From cf474352cef9b1a8568c74767acacf39820dea9a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 6 Jun 2024 06:53:55 +0000 Subject: [PATCH 14/19] chore(deps): update dependency tsx to v4.12.0 --- npm/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/npm/package-lock.json b/npm/package-lock.json index 29cd51e200..a232d3e649 100644 --- a/npm/package-lock.json +++ b/npm/package-lock.json @@ -801,9 +801,9 @@ } }, "node_modules/tsx": { - "version": "4.11.2", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.11.2.tgz", - "integrity": "sha512-V5DL5v1BuItjsQ2FN9+4OjR7n5cr8hSgN+VGmm/fd2/0cgQdBIWHcQ3bFYm/5ZTmyxkTDBUIaRuW2divgfPe0A==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.12.0.tgz", + "integrity": "sha512-642NAWAbDqPZINjmL32Lh/B+pd8vbVj6LHPsWm09IIHqQuWhCrNfcPTjRlHFWvv3FfM4vt9NLReBIjUNj5ZhDg==", "dev": true, "license": "MIT", "dependencies": { From 4e8b92d3f2304f2973a1050efb13cb1fc1e91286 Mon Sep 17 00:00:00 2001 From: Tushar Mathur Date: Thu, 6 Jun 2024 18:44:54 +0530 Subject: [PATCH 15/19] delete older tests --- ..._type_merger__test__cyclic_merge_case.snap | 20 ------ ...ormer__type_merger__test__type_merger.snap | 22 ------- ...rator__generator__test__dummyjson.com.snap | 64 ------------------- ...r__test__jsonplaceholder.typicode.com.snap | 27 -------- .../test-dbl-usage-many.md_client.snap | 44 ------------- .../test-dbl-usage-many.md_merged.snap | 32 ---------- .../snapshots/test-dbl-usage.md_client.snap | 33 ---------- .../snapshots/test-dbl-usage.md_merged.snap | 21 ------ 8 files changed, 263 deletions(-) delete mode 100644 src/core/config/transformer/snapshots/tailcall__core__config__transformer__type_merger__test__cyclic_merge_case.snap delete mode 100644 src/core/config/transformer/snapshots/tailcall__core__config__transformer__type_merger__test__type_merger.snap delete mode 100644 src/core/generator/snapshots/tailcall__core__generator__generator__test__dummyjson.com.snap delete mode 100644 src/core/generator/snapshots/tailcall__core__generator__generator__test__jsonplaceholder.typicode.com.snap delete mode 100644 tests/core/snapshots/test-dbl-usage-many.md_client.snap delete mode 100644 tests/core/snapshots/test-dbl-usage-many.md_merged.snap delete mode 100644 tests/core/snapshots/test-dbl-usage.md_client.snap delete mode 100644 tests/core/snapshots/test-dbl-usage.md_merged.snap diff --git a/src/core/config/transformer/snapshots/tailcall__core__config__transformer__type_merger__test__cyclic_merge_case.snap b/src/core/config/transformer/snapshots/tailcall__core__config__transformer__type_merger__test__cyclic_merge_case.snap deleted file mode 100644 index 854e8bf2a5..0000000000 --- a/src/core/config/transformer/snapshots/tailcall__core__config__transformer__type_merger__test__cyclic_merge_case.snap +++ /dev/null @@ -1,20 +0,0 @@ ---- -source: src/core/config/transformer/type_merger.rs -expression: config.to_sdl() ---- -schema @server @upstream { - query: Query -} - -type M1 { - body: String - id: Int - t1: M1 - title: Boolean - userId: Float -} - -type Query { - q1: M1 - q2: M1 -} diff --git a/src/core/config/transformer/snapshots/tailcall__core__config__transformer__type_merger__test__type_merger.snap b/src/core/config/transformer/snapshots/tailcall__core__config__transformer__type_merger__test__type_merger.snap deleted file mode 100644 index 23b14d1c79..0000000000 --- a/src/core/config/transformer/snapshots/tailcall__core__config__transformer__type_merger__test__type_merger.snap +++ /dev/null @@ -1,22 +0,0 @@ ---- -source: src/core/config/transformer/type_merger.rs -expression: config.to_sdl() ---- -schema @server @upstream { - query: Query -} - -type M1 { - f1: String - f2: Int - f3: Boolean - f4: Float - f5: ID -} - -type Query { - q1: M1 - q2: M1 - q3: M1 - q4: M1 -} diff --git a/src/core/generator/snapshots/tailcall__core__generator__generator__test__dummyjson.com.snap b/src/core/generator/snapshots/tailcall__core__generator__generator__test__dummyjson.com.snap deleted file mode 100644 index 5e259f1aee..0000000000 --- a/src/core/generator/snapshots/tailcall__core__generator__generator__test__dummyjson.com.snap +++ /dev/null @@ -1,64 +0,0 @@ ---- -source: src/core/generator/generator.rs -expression: cfg.to_sdl() ---- -schema @server @upstream(baseURL: "https://dummyjson.com") { - query: Query -} - -type Query { - f1(q: String!): T17 @http(path: "/products/search", query: [{key: "q", value: "{{.args.q}}"}]) -} - -type T1 { - depth: Int - height: Int - width: Int -} - -type T16 { - availabilityStatus: String - brand: String - category: String - description: String - dimensions: T1 - discountPercentage: Int - id: Int - images: [String] - meta: T3 - minimumOrderQuantity: Int - price: Int - rating: Int - returnPolicy: String - reviews: [T2] - shippingInformation: String - sku: String - stock: Int - tags: [String] - thumbnail: String - title: String - warrantyInformation: String - weight: Int -} - -type T17 { - limit: Int - products: [T16] - skip: Int - total: Int -} - -type T2 { - comment: String - date: String - rating: Int - reviewerEmail: String - reviewerName: String -} - -type T3 { - barcode: String - createdAt: String - qrCode: String - updatedAt: String -} diff --git a/src/core/generator/snapshots/tailcall__core__generator__generator__test__jsonplaceholder.typicode.com.snap b/src/core/generator/snapshots/tailcall__core__generator__generator__test__jsonplaceholder.typicode.com.snap deleted file mode 100644 index 91b82c90ad..0000000000 --- a/src/core/generator/snapshots/tailcall__core__generator__generator__test__jsonplaceholder.typicode.com.snap +++ /dev/null @@ -1,27 +0,0 @@ ---- -source: src/core/generator/generator.rs -expression: cfg.to_sdl() ---- -schema @server @upstream(baseURL: "https://jsonplaceholder.typicode.com") { - query: Query -} - -type Query { - f1(p1: Int!): [T1] @http(path: "/posts/{{.args.p1}}/comments") - f2(p1: Int!): T2 @http(path: "/posts/{{.args.p1}}") -} - -type T1 { - body: String - email: String - id: Int - name: String - postId: Int -} - -type T2 { - body: String - id: Int - title: String - userId: Int -} diff --git a/tests/core/snapshots/test-dbl-usage-many.md_client.snap b/tests/core/snapshots/test-dbl-usage-many.md_client.snap deleted file mode 100644 index 448b5db7f0..0000000000 --- a/tests/core/snapshots/test-dbl-usage-many.md_client.snap +++ /dev/null @@ -1,44 +0,0 @@ ---- -source: tests/core/spec.rs -expression: formatted ---- -scalar Date - -scalar Email - -scalar Empty - -scalar JSON - -scalar PhoneNumber - -type Post { - id: ID! - title: String! -} - -input PostInput { - id: ID! - title: String! -} - -type Query { - post(input: PostInput!): Post! - user(input: UserInput!): User! -} - -scalar Url - -type User { - id: ID! - name: String! -} - -input UserInput { - id: ID! - name: String! -} - -schema { - query: Query -} diff --git a/tests/core/snapshots/test-dbl-usage-many.md_merged.snap b/tests/core/snapshots/test-dbl-usage-many.md_merged.snap deleted file mode 100644 index b1f141f1f6..0000000000 --- a/tests/core/snapshots/test-dbl-usage-many.md_merged.snap +++ /dev/null @@ -1,32 +0,0 @@ ---- -source: tests/core/spec.rs -expression: formatter ---- -schema @server @upstream { - query: Query -} - -input PostInput { - id: ID! - title: String! -} - -input UserInput { - id: ID! - name: String! -} - -type Post { - id: ID! - title: String! -} - -type Query { - post(input: PostInput!): Post! @http(baseURL: "http://localhost:8080", path: "/user/{{.args.input.id}}") - user(input: UserInput!): User! @http(baseURL: "http://localhost:8080", path: "/user/{{.args.input.id}}") -} - -type User { - id: ID! - name: String! -} diff --git a/tests/core/snapshots/test-dbl-usage.md_client.snap b/tests/core/snapshots/test-dbl-usage.md_client.snap deleted file mode 100644 index 752627f96c..0000000000 --- a/tests/core/snapshots/test-dbl-usage.md_client.snap +++ /dev/null @@ -1,33 +0,0 @@ ---- -source: tests/core/spec.rs -expression: formatted ---- -scalar Date - -scalar Email - -scalar Empty - -scalar JSON - -scalar PhoneNumber - -type Query { - user(input: UserInput!): User! -} - -scalar Url - -type User { - id: ID! - name: String! -} - -input UserInput { - id: ID! - name: String! -} - -schema { - query: Query -} diff --git a/tests/core/snapshots/test-dbl-usage.md_merged.snap b/tests/core/snapshots/test-dbl-usage.md_merged.snap deleted file mode 100644 index 2d9845154d..0000000000 --- a/tests/core/snapshots/test-dbl-usage.md_merged.snap +++ /dev/null @@ -1,21 +0,0 @@ ---- -source: tests/core/spec.rs -expression: formatter ---- -schema @server @upstream { - query: Query -} - -input UserInput { - id: ID! - name: String! -} - -type Query { - user(input: UserInput!): User! @http(baseURL: "http://localhost:8080", path: "/user/{{.args.input.id}}") -} - -type User { - id: ID! - name: String! -} From 5f54bca8b589c88a257c2f759808c7a3b453e88d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 6 Jun 2024 13:17:55 +0000 Subject: [PATCH 16/19] chore(deps): update dependency @cloudflare/workers-types to v4.20240605.0 --- tailcall-cloudflare/package-lock.json | 58 +++------------------------ 1 file changed, 6 insertions(+), 52 deletions(-) diff --git a/tailcall-cloudflare/package-lock.json b/tailcall-cloudflare/package-lock.json index 3ed32e6238..3be8b64562 100644 --- a/tailcall-cloudflare/package-lock.json +++ b/tailcall-cloudflare/package-lock.json @@ -107,9 +107,9 @@ } }, "node_modules/@cloudflare/workers-types": { - "version": "4.20240603.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20240603.0.tgz", - "integrity": "sha512-KmsjZcd/dPWM51FoT08cvUGCq9l3nFEriloQ12mcdJPoW911Gi5S/9Cq4xeFvTrtk9TJ2krvxP23IeobecRmOQ==", + "version": "4.20240605.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20240605.0.tgz", + "integrity": "sha512-zJw4Q6CnkaQ5JZmHRkNiSs5GfiRgUIUL8BIHPQkd2XUHZkIBv9M9yc0LKEwMYGpCFC+oSOltet6c9RjP9uQ99g==", "dev": true }, "node_modules/@cspotcode/source-map-support": { @@ -2668,32 +2668,6 @@ "@esbuild/win32-x64": "0.17.19" } }, - "node_modules/wrangler/node_modules/miniflare": { - "version": "3.20240524.1", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20240524.1.tgz", - "integrity": "sha512-5d3pRxvd5pT7lX1SsBH9+AjXuyHJnChSNOnYhubfi7pxMek4ZfULwhnUmNUp1R7b2xKuzqdFDZa0fsZuUoFxlw==", - "dev": true, - "dependencies": { - "@cspotcode/source-map-support": "0.8.1", - "acorn": "^8.8.0", - "acorn-walk": "^8.2.0", - "capnp-ts": "^0.7.0", - "exit-hook": "^2.2.1", - "glob-to-regexp": "^0.4.1", - "stoppable": "^1.1.0", - "undici": "^5.28.2", - "workerd": "1.20240524.0", - "ws": "^8.11.0", - "youch": "^3.2.2", - "zod": "^3.20.6" - }, - "bin": { - "miniflare": "bootstrap.js" - }, - "engines": { - "node": ">=16.13" - } - }, "node_modules/ws": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", @@ -2800,9 +2774,9 @@ "optional": true }, "@cloudflare/workers-types": { - "version": "4.20240603.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20240603.0.tgz", - "integrity": "sha512-KmsjZcd/dPWM51FoT08cvUGCq9l3nFEriloQ12mcdJPoW911Gi5S/9Cq4xeFvTrtk9TJ2krvxP23IeobecRmOQ==", + "version": "4.20240605.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20240605.0.tgz", + "integrity": "sha512-zJw4Q6CnkaQ5JZmHRkNiSs5GfiRgUIUL8BIHPQkd2XUHZkIBv9M9yc0LKEwMYGpCFC+oSOltet6c9RjP9uQ99g==", "dev": true }, "@cspotcode/source-map-support": { @@ -4381,26 +4355,6 @@ "@esbuild/win32-ia32": "0.17.19", "@esbuild/win32-x64": "0.17.19" } - }, - "miniflare": { - "version": "3.20240524.1", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20240524.1.tgz", - "integrity": "sha512-5d3pRxvd5pT7lX1SsBH9+AjXuyHJnChSNOnYhubfi7pxMek4ZfULwhnUmNUp1R7b2xKuzqdFDZa0fsZuUoFxlw==", - "dev": true, - "requires": { - "@cspotcode/source-map-support": "0.8.1", - "acorn": "^8.8.0", - "acorn-walk": "^8.2.0", - "capnp-ts": "^0.7.0", - "exit-hook": "^2.2.1", - "glob-to-regexp": "^0.4.1", - "stoppable": "^1.1.0", - "undici": "^5.28.2", - "workerd": "1.20240524.0", - "ws": "^8.11.0", - "youch": "^3.2.2", - "zod": "^3.20.6" - } } } }, From 896913f7707ab0fb25c69ac7135f615dbe04371b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 6 Jun 2024 23:26:33 +0000 Subject: [PATCH 17/19] fix(deps): update rust crate clap to v4.5.6 --- Cargo.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8c2bff4ff1..176ba1c18d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -881,9 +881,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.4" +version = "4.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" +checksum = "a9689a29b593160de5bc4aacab7b5d54fb52231de70122626c178e6a368994c7" dependencies = [ "clap_builder", "clap_derive", @@ -891,9 +891,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.2" +version = "4.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +checksum = "2e5387378c84f6faa26890ebf9f0a92989f8873d4d380467bcd0d8d8620424df" dependencies = [ "anstream", "anstyle", @@ -903,9 +903,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.4" +version = "4.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" +checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6" dependencies = [ "heck", "proc-macro2", From aaeb4081b14eed511785583dbb925f139c7e2a13 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 7 Jun 2024 04:21:56 +0000 Subject: [PATCH 18/19] chore(deps): update dependency tsx to v4.13.0 --- npm/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/npm/package-lock.json b/npm/package-lock.json index a232d3e649..f29cf42d4c 100644 --- a/npm/package-lock.json +++ b/npm/package-lock.json @@ -801,9 +801,9 @@ } }, "node_modules/tsx": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.12.0.tgz", - "integrity": "sha512-642NAWAbDqPZINjmL32Lh/B+pd8vbVj6LHPsWm09IIHqQuWhCrNfcPTjRlHFWvv3FfM4vt9NLReBIjUNj5ZhDg==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.13.0.tgz", + "integrity": "sha512-kNY70P2aLMdVBii1Err5ENxDhQ6Vz2PbQGX68DcvzY2/PWK5NLBO6vI7lPr1/2xG3IKSt2MN+KOAyWDQSRlbCA==", "dev": true, "license": "MIT", "dependencies": { From bdd16df01140eb85c3012f567d614b4718e44597 Mon Sep 17 00:00:00 2001 From: Tushar Mathur Date: Fri, 7 Jun 2024 11:53:33 +0530 Subject: [PATCH 19/19] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ad31252335..7d898c9686 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,7 @@ Head out to [docs] to learn about other powerful tailcall features. Your contributions are invaluable! Kindly go through our [contribution guidelines] if you are a first time contributor. -[contribution guidelines]: https://tailcall.run/docs/contributors/ +[contribution guidelines]: https://tailcall.run/developers/ ### Support Us