diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ae6824a6960..7d1dd45b33b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -120,6 +120,15 @@ jobs: - name: Install run: yarn install + - uses: bufbuild/buf-setup-action@v1.34.0 + + - uses: actions/setup-go@v5 + with: + go-version: 'stable' + + - name: Install protoc-gen-openapi + run: go install github.com/google/gnostic/cmd/protoc-gen-openapi@v0.7.0 + - name: Run tests run: yarn test --maxWorkers 15 --ci --silent diff --git a/.github/workflows/fern.yml b/.github/workflows/fern.yml index fb16d516a5c..711ba207805 100644 --- a/.github/workflows/fern.yml +++ b/.github/workflows/fern.yml @@ -14,10 +14,23 @@ jobs: - name: Checkout repo uses: actions/checkout@v4 - - name: Install Fern - run: npm install -g fern-api + - uses: actions/setup-node@v3 + with: + node-version: 18 + cache: "yarn" + + - name: Install + run: yarn install - name: Check API definition is valid env: FORCE_COLOR: "2" - run: fern check + FERN_TOKEN: ${{ secrets.FERN_TOKEN }} + AUTH0_DOMAIN: ${{ secrets.AUTH0_DOMAIN }} + AUTH0_CLIENT_ID: ${{ secrets.AUTH0_CLIENT_ID }} + POSTHOG_API_KEY: ${{ secrets.POSTHOG_PROJECT_API_KEY }} + run: | + yarn workspace @fern-api/cli dist:cli:prod + cli_path="$(yarn workspace @fern-api/cli bin fern:prod)" + node $cli_path check + diff --git a/.github/workflows/preview-docs.yml b/.github/workflows/preview-docs.yml index 0a460770532..26dedd37a00 100644 --- a/.github/workflows/preview-docs.yml +++ b/.github/workflows/preview-docs.yml @@ -23,15 +23,25 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - - name: Install Fern - run: npm install -g fern-api + - uses: actions/setup-node@v3 + with: + node-version: 18 + cache: "yarn" + + - name: Install + run: yarn install - name: Generate preview URL id: generate-docs env: FERN_TOKEN: ${{ secrets.FERN_TOKEN }} + AUTH0_DOMAIN: ${{ secrets.AUTH0_DOMAIN }} + AUTH0_CLIENT_ID: ${{ secrets.AUTH0_CLIENT_ID }} + POSTHOG_API_KEY: ${{ secrets.POSTHOG_PROJECT_API_KEY }} run: | - OUTPUT=$(fern generate --docs --preview --instance fern.docs.buildwithfern.com/learn 2>&1) || true + yarn workspace @fern-api/cli dist:cli:prod + cli_path="$(yarn workspace @fern-api/cli bin fern:prod)" + OUTPUT=$(node $cli_path generate --docs --preview --instance fern.docs.buildwithfern.com/learn 2>&1) || true echo "$OUTPUT" URL=$(echo "$OUTPUT" | grep -oP 'Published docs to \K.*(?= \()') echo "Preview URL: $URL" diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index ae1176006f6..9987a9d0a8d 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -15,20 +15,31 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - - name: Install Fern - run: npm install -g fern-api + - uses: actions/setup-node@v3 + with: + node-version: 18 + cache: "yarn" + + - name: Install + run: yarn install - name: Publish Docs env: FERN_TOKEN: ${{ secrets.FERN_TOKEN }} - run: fern generate --docs --log-level debug --instance fern.docs.buildwithfern.com/learn - - - name: Install fern-dev CLI + AUTH0_DOMAIN: ${{ secrets.AUTH0_DOMAIN }} + AUTH0_CLIENT_ID: ${{ secrets.AUTH0_CLIENT_ID }} + POSTHOG_API_KEY: ${{ secrets.POSTHOG_PROJECT_API_KEY }} run: | - npm config set //registry.npmjs.org/:_authToken ${{ secrets.YARN_NPM_AUTH_TOKEN }} - npm install -g @fern-api/fern-api-dev --prefix=$HOME/.local + yarn workspace @fern-api/cli dist:cli:prod + cli_path="$(yarn workspace @fern-api/cli bin fern:prod)" + node $cli_path generate --docs --log-level debug --instance fern.docs.buildwithfern.com/learn + + # - name: Install fern-dev CLI + # run: | + # npm config set //registry.npmjs.org/:_authToken ${{ secrets.YARN_NPM_AUTH_TOKEN }} + # npm install -g @fern-api/fern-api-dev --prefix=$HOME/.local - - name: Publish Docs to dev - env: - FERN_TOKEN: ${{ secrets.FERN_ORG_TOKEN_DEV }} - run: fern-dev generate --docs --log-level debug --instance fern.docs.dev.buildwithfern.com/learn + # - name: Publish Docs to dev + # env: + # FERN_TOKEN: ${{ secrets.FERN_ORG_TOKEN_DEV }} + # run: fern-dev generate --docs --log-level debug --instance fern.docs.dev.buildwithfern.com/learn diff --git a/.github/workflows/seed.yml b/.github/workflows/seed.yml index 1aa2de54a8a..ef4d1238e49 100644 --- a/.github/workflows/seed.yml +++ b/.github/workflows/seed.yml @@ -89,6 +89,15 @@ jobs: - name: Yarn Install run: yarn install + - uses: bufbuild/buf-setup-action@v1.34.0 + + - uses: actions/setup-go@v5 + with: + go-version: 'stable' + + - name: Install protoc-gen-openapi + run: go install github.com/google/gnostic/cmd/protoc-gen-openapi@v0.7.0 + - name: Seed Test env: FORCE_COLOR: "2" @@ -114,6 +123,15 @@ jobs: - name: Yarn Install run: yarn install + - uses: bufbuild/buf-setup-action@v1.34.0 + + - uses: actions/setup-go@v5 + with: + go-version: 'stable' + + - name: Install protoc-gen-openapi + run: go install github.com/google/gnostic/cmd/protoc-gen-openapi@v0.7.0 + - name: Seed Test env: FORCE_COLOR: "2" @@ -139,6 +157,15 @@ jobs: - name: Yarn Install run: yarn install + - uses: bufbuild/buf-setup-action@v1.34.0 + + - uses: actions/setup-go@v5 + with: + go-version: 'stable' + + - name: Install protoc-gen-openapi + run: go install github.com/google/gnostic/cmd/protoc-gen-openapi@v0.7.0 + - name: Install Poetry uses: snok/install-poetry@v1 @@ -171,6 +198,15 @@ jobs: - name: Yarn Install run: yarn install + - uses: bufbuild/buf-setup-action@v1.34.0 + + - uses: actions/setup-go@v5 + with: + go-version: 'stable' + + - name: Install protoc-gen-openapi + run: go install github.com/google/gnostic/cmd/protoc-gen-openapi@v0.7.0 + - name: Install Poetry uses: snok/install-poetry@v1 @@ -203,6 +239,15 @@ jobs: - name: Yarn Install run: yarn install + - uses: bufbuild/buf-setup-action@v1.34.0 + + - uses: actions/setup-go@v5 + with: + go-version: 'stable' + + - name: Install protoc-gen-openapi + run: go install github.com/google/gnostic/cmd/protoc-gen-openapi@v0.7.0 + - name: Install Poetry uses: snok/install-poetry@v1 @@ -235,6 +280,15 @@ jobs: - name: Yarn Install run: yarn install + - uses: bufbuild/buf-setup-action@v1.34.0 + + - uses: actions/setup-go@v5 + with: + go-version: 'stable' + + - name: Install protoc-gen-openapi + run: go install github.com/google/gnostic/cmd/protoc-gen-openapi@v0.7.0 + - name: Seed Test env: FORCE_COLOR: "2" @@ -260,6 +314,15 @@ jobs: - name: Yarn Install run: yarn install + - uses: bufbuild/buf-setup-action@v1.34.0 + + - uses: actions/setup-go@v5 + with: + go-version: 'stable' + + - name: Install protoc-gen-openapi + run: go install github.com/google/gnostic/cmd/protoc-gen-openapi@v0.7.0 + - name: Seed Test env: FORCE_COLOR: "2" @@ -285,6 +348,15 @@ jobs: - name: Yarn Install run: yarn install + - uses: bufbuild/buf-setup-action@v1.34.0 + + - uses: actions/setup-go@v5 + with: + go-version: 'stable' + + - name: Install protoc-gen-openapi + run: go install github.com/google/gnostic/cmd/protoc-gen-openapi@v0.7.0 + - name: Seed Test env: FORCE_COLOR: "2" @@ -310,6 +382,15 @@ jobs: - name: Yarn Install run: yarn install + - uses: bufbuild/buf-setup-action@v1.34.0 + + - uses: actions/setup-go@v5 + with: + go-version: 'stable' + + - name: Install protoc-gen-openapi + run: go install github.com/google/gnostic/cmd/protoc-gen-openapi@v0.7.0 + - name: Seed Test env: FORCE_COLOR: "2" @@ -335,6 +416,15 @@ jobs: - name: Yarn Install run: yarn install + - uses: bufbuild/buf-setup-action@v1.34.0 + + - uses: actions/setup-go@v5 + with: + go-version: 'stable' + + - name: Install protoc-gen-openapi + run: go install github.com/google/gnostic/cmd/protoc-gen-openapi@v0.7.0 + - name: Seed Test env: FORCE_COLOR: "2" @@ -360,6 +450,15 @@ jobs: - name: Yarn Install run: yarn install + - uses: bufbuild/buf-setup-action@v1.34.0 + + - uses: actions/setup-go@v5 + with: + go-version: 'stable' + + - name: Install protoc-gen-openapi + run: go install github.com/google/gnostic/cmd/protoc-gen-openapi@v0.7.0 + - name: Seed Test env: FORCE_COLOR: "2" @@ -385,6 +484,15 @@ jobs: - name: Yarn Install run: yarn install + - uses: bufbuild/buf-setup-action@v1.34.0 + + - uses: actions/setup-go@v5 + with: + go-version: 'stable' + + - name: Install protoc-gen-openapi + run: go install github.com/google/gnostic/cmd/protoc-gen-openapi@v0.7.0 + - name: Seed Test env: FORCE_COLOR: "2" @@ -410,6 +518,15 @@ jobs: - name: Yarn Install run: yarn install + - uses: bufbuild/buf-setup-action@v1.34.0 + + - uses: actions/setup-go@v5 + with: + go-version: 'stable' + + - name: Install protoc-gen-openapi + run: go install github.com/google/gnostic/cmd/protoc-gen-openapi@v0.7.0 + - name: Seed Test env: FORCE_COLOR: "2" @@ -435,6 +552,15 @@ jobs: - name: Yarn Install run: yarn install + - uses: bufbuild/buf-setup-action@v1.34.0 + + - uses: actions/setup-go@v5 + with: + go-version: 'stable' + + - name: Install protoc-gen-openapi + run: go install github.com/google/gnostic/cmd/protoc-gen-openapi@v0.7.0 + - name: Seed Test env: FORCE_COLOR: "2" @@ -460,6 +586,15 @@ jobs: - name: Yarn Install run: yarn install + - uses: bufbuild/buf-setup-action@v1.34.0 + + - uses: actions/setup-go@v5 + with: + go-version: 'stable' + + - name: Install protoc-gen-openapi + run: go install github.com/google/gnostic/cmd/protoc-gen-openapi@v0.7.0 + - name: Seed Test env: FORCE_COLOR: "2" @@ -485,6 +620,15 @@ jobs: - name: Yarn Install run: yarn install + - uses: bufbuild/buf-setup-action@v1.34.0 + + - uses: actions/setup-go@v5 + with: + go-version: 'stable' + + - name: Install protoc-gen-openapi + run: go install github.com/google/gnostic/cmd/protoc-gen-openapi@v0.7.0 + - name: Seed Test env: FORCE_COLOR: "2" @@ -510,6 +654,15 @@ jobs: - name: Yarn Install run: yarn install + - uses: bufbuild/buf-setup-action@v1.34.0 + + - uses: actions/setup-go@v5 + with: + go-version: 'stable' + + - name: Install protoc-gen-openapi + run: go install github.com/google/gnostic/cmd/protoc-gen-openapi@v0.7.0 + - name: Seed Test env: FORCE_COLOR: "2" diff --git a/.github/workflows/setup-seed.yml b/.github/workflows/setup-seed.yml new file mode 100644 index 00000000000..fbd7e3a62dd --- /dev/null +++ b/.github/workflows/setup-seed.yml @@ -0,0 +1,24 @@ +name: Setup Seed + +on: workflow_call + +jobs: + setup: + runs-on: ubuntu-latest + steps: + - uses: actions/setup-node@v3 + with: + node-version: 18 + cache: "yarn" + + - name: Yarn Install + run: yarn install + + - uses: bufbuild/buf-setup-action@v1.34.0 + + - uses: actions/setup-go@v5 + with: + go-version: 'stable' + + - name: Install protoc-gen-openapi + run: go install github.com/google/gnostic/cmd/protoc-gen-openapi@v0.7.0 diff --git a/.github/workflows/test-definitions.yml b/.github/workflows/test-definitions.yml index 7a6e7015d2f..a87e25f15ca 100644 --- a/.github/workflows/test-definitions.yml +++ b/.github/workflows/test-definitions.yml @@ -36,6 +36,15 @@ jobs: - name: Install Deps run: yarn install + - uses: bufbuild/buf-setup-action@v1.34.0 + + - uses: actions/setup-go@v5 + with: + go-version: 'stable' + + - name: Install protoc-gen-openapi + run: go install github.com/google/gnostic/cmd/protoc-gen-openapi@v0.7.0 + - name: Fern check env: FORCE_COLOR: "2" diff --git a/.pnp.cjs b/.pnp.cjs index 196ae4bc9c7..2be30335afa 100644 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -652,6 +652,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["chalk", "npm:2.4.2"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:7.24.7", {\ + "packageLocation": "./.yarn/cache/@babel-code-frame-npm-7.24.7-315a600a58-830e62cd38.zip/node_modules/@babel/code-frame/",\ + "packageDependencies": [\ + ["@babel/code-frame", "npm:7.24.7"],\ + ["@babel/highlight", "npm:7.24.7"],\ + ["picocolors", "npm:1.0.0"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["@babel/compat-data", [\ @@ -682,6 +691,13 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@babel/compat-data", "npm:7.24.1"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:7.25.2", {\ + "packageLocation": "./.yarn/cache/@babel-compat-data-npm-7.25.2-119057710e-b61bc9da7c.zip/node_modules/@babel/compat-data/",\ + "packageDependencies": [\ + ["@babel/compat-data", "npm:7.25.2"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["@babel/core", [\ @@ -750,6 +766,28 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["semver", "npm:6.3.1"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:7.25.2", {\ + "packageLocation": "./.yarn/cache/@babel-core-npm-7.25.2-341930f809-9a1ef604a7.zip/node_modules/@babel/core/",\ + "packageDependencies": [\ + ["@babel/core", "npm:7.25.2"],\ + ["@ampproject/remapping", "npm:2.2.1"],\ + ["@babel/code-frame", "npm:7.24.7"],\ + ["@babel/generator", "npm:7.25.0"],\ + ["@babel/helper-compilation-targets", "npm:7.25.2"],\ + ["@babel/helper-module-transforms", "virtual:341930f80996f4b1e479f0ee33257969b2165bf70992bcc76aa889af20d1c39a2bfc637461175a3ea65d6c75949d04c5fd87140f3b91c8912352de080c45e357#npm:7.25.2"],\ + ["@babel/helpers", "npm:7.25.0"],\ + ["@babel/parser", "npm:7.25.3"],\ + ["@babel/template", "npm:7.25.0"],\ + ["@babel/traverse", "npm:7.23.2"],\ + ["@babel/types", "npm:7.25.2"],\ + ["convert-source-map", "npm:2.0.0"],\ + ["debug", "virtual:4758feee42453c0e31b0d2032a7b1362d6b06281699830d2da9a056f2cca72bd2c5cfdb74005fdf03a64876be8eaca2dd7b0fc2dc59d14318badf19cb22ba18e#npm:4.3.4"],\ + ["gensync", "npm:1.0.0-beta.2"],\ + ["json5", "npm:2.2.2"],\ + ["semver", "npm:6.3.1"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["@babel/generator", [\ @@ -784,6 +822,17 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["jsesc", "npm:2.5.2"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:7.25.0", {\ + "packageLocation": "./.yarn/cache/@babel-generator-npm-7.25.0-4bba208756-bf25649dde.zip/node_modules/@babel/generator/",\ + "packageDependencies": [\ + ["@babel/generator", "npm:7.25.0"],\ + ["@babel/types", "npm:7.25.2"],\ + ["@jridgewell/gen-mapping", "npm:0.3.5"],\ + ["@jridgewell/trace-mapping", "npm:0.3.25"],\ + ["jsesc", "npm:2.5.2"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["@babel/helper-annotate-as-pure", [\ @@ -846,6 +895,18 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ],\ "linkType": "HARD"\ }],\ + ["npm:7.25.2", {\ + "packageLocation": "./.yarn/cache/@babel-helper-compilation-targets-npm-7.25.2-27e0232144-aed33c5496.zip/node_modules/@babel/helper-compilation-targets/",\ + "packageDependencies": [\ + ["@babel/helper-compilation-targets", "npm:7.25.2"],\ + ["@babel/compat-data", "npm:7.25.2"],\ + ["@babel/helper-validator-option", "npm:7.24.8"],\ + ["browserslist", "npm:4.23.3"],\ + ["lru-cache", "npm:5.1.1"],\ + ["semver", "npm:6.3.1"]\ + ],\ + "linkType": "HARD"\ + }],\ ["virtual:e85812e66ba223122af683f94d3033aa87bfc6e95528fffa756224a61d64c70d5cdb56cddb8bbc1d436f500a6528d6de6d1b7969ada524b1987d1f44bf6e6476#npm:7.19.1", {\ "packageLocation": "./.yarn/__virtual__/@babel-helper-compilation-targets-virtual-25a7884c54/0/cache/@babel-helper-compilation-targets-npm-7.19.1-4f8302bda9-c2d3039265.zip/node_modules/@babel/helper-compilation-targets/",\ "packageDependencies": [\ @@ -1149,6 +1210,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@babel/types", "npm:7.24.0"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:7.24.7", {\ + "packageLocation": "./.yarn/cache/@babel-helper-module-imports-npm-7.24.7-f60e66adbf-8ac15d96d2.zip/node_modules/@babel/helper-module-imports/",\ + "packageDependencies": [\ + ["@babel/helper-module-imports", "npm:7.24.7"],\ + ["@babel/traverse", "npm:7.23.2"],\ + ["@babel/types", "npm:7.25.2"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["@babel/helper-module-transforms", [\ @@ -1181,6 +1251,30 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ],\ "linkType": "SOFT"\ }],\ + ["npm:7.25.2", {\ + "packageLocation": "./.yarn/cache/@babel-helper-module-transforms-npm-7.25.2-2c8d511580-282d4e3308.zip/node_modules/@babel/helper-module-transforms/",\ + "packageDependencies": [\ + ["@babel/helper-module-transforms", "npm:7.25.2"]\ + ],\ + "linkType": "SOFT"\ + }],\ + ["virtual:341930f80996f4b1e479f0ee33257969b2165bf70992bcc76aa889af20d1c39a2bfc637461175a3ea65d6c75949d04c5fd87140f3b91c8912352de080c45e357#npm:7.25.2", {\ + "packageLocation": "./.yarn/__virtual__/@babel-helper-module-transforms-virtual-b14538d1e7/0/cache/@babel-helper-module-transforms-npm-7.25.2-2c8d511580-282d4e3308.zip/node_modules/@babel/helper-module-transforms/",\ + "packageDependencies": [\ + ["@babel/helper-module-transforms", "virtual:341930f80996f4b1e479f0ee33257969b2165bf70992bcc76aa889af20d1c39a2bfc637461175a3ea65d6c75949d04c5fd87140f3b91c8912352de080c45e357#npm:7.25.2"],\ + ["@babel/core", "npm:7.25.2"],\ + ["@babel/helper-module-imports", "npm:7.24.7"],\ + ["@babel/helper-simple-access", "npm:7.24.7"],\ + ["@babel/helper-validator-identifier", "npm:7.24.7"],\ + ["@babel/traverse", "npm:7.23.2"],\ + ["@types/babel__core", null]\ + ],\ + "packagePeers": [\ + "@babel/core",\ + "@types/babel__core"\ + ],\ + "linkType": "HARD"\ + }],\ ["virtual:4987baf09b82c48eb8b5413aa2ba462ba316c95c50a367748f9bb0d09a052cbd6508a65052d6692401be2eab1d0720262e16f715ed4f30858ba5040402f5e3fe#npm:7.23.3", {\ "packageLocation": "./.yarn/__virtual__/@babel-helper-module-transforms-virtual-57776750bf/0/cache/@babel-helper-module-transforms-npm-7.23.3-69078a931c-5d0895cfba.zip/node_modules/@babel/helper-module-transforms/",\ "packageDependencies": [\ @@ -1415,6 +1509,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@babel/types", "npm:7.22.15"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:7.24.7", {\ + "packageLocation": "./.yarn/cache/@babel-helper-simple-access-npm-7.24.7-beddd00b0e-ddbf55f9de.zip/node_modules/@babel/helper-simple-access/",\ + "packageDependencies": [\ + ["@babel/helper-simple-access", "npm:7.24.7"],\ + ["@babel/traverse", "npm:7.23.2"],\ + ["@babel/types", "npm:7.25.2"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["@babel/helper-skip-transparent-expression-wrappers", [\ @@ -1466,6 +1569,13 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@babel/helper-string-parser", "npm:7.23.4"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:7.24.8", {\ + "packageLocation": "./.yarn/cache/@babel-helper-string-parser-npm-7.24.8-133b2e71e1-39b03c5119.zip/node_modules/@babel/helper-string-parser/",\ + "packageDependencies": [\ + ["@babel/helper-string-parser", "npm:7.24.8"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["@babel/helper-validator-identifier", [\ @@ -1489,6 +1599,13 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@babel/helper-validator-identifier", "npm:7.22.20"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:7.24.7", {\ + "packageLocation": "./.yarn/cache/@babel-helper-validator-identifier-npm-7.24.7-748889c8d2-6799ab117c.zip/node_modules/@babel/helper-validator-identifier/",\ + "packageDependencies": [\ + ["@babel/helper-validator-identifier", "npm:7.24.7"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["@babel/helper-validator-option", [\ @@ -1512,6 +1629,13 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@babel/helper-validator-option", "npm:7.23.5"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:7.24.8", {\ + "packageLocation": "./.yarn/cache/@babel-helper-validator-option-npm-7.24.8-e093ef5016-a52442dfa7.zip/node_modules/@babel/helper-validator-option/",\ + "packageDependencies": [\ + ["@babel/helper-validator-option", "npm:7.24.8"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["@babel/helper-wrap-function", [\ @@ -1556,6 +1680,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@babel/types", "npm:7.23.9"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:7.25.0", {\ + "packageLocation": "./.yarn/cache/@babel-helpers-npm-7.25.0-f552d9aaf3-739e3704ff.zip/node_modules/@babel/helpers/",\ + "packageDependencies": [\ + ["@babel/helpers", "npm:7.25.0"],\ + ["@babel/template", "npm:7.25.0"],\ + ["@babel/types", "npm:7.25.2"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["@babel/highlight", [\ @@ -1588,6 +1721,17 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["js-tokens", "npm:4.0.0"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:7.24.7", {\ + "packageLocation": "./.yarn/cache/@babel-highlight-npm-7.24.7-d792bd8d9f-5cd3a89f14.zip/node_modules/@babel/highlight/",\ + "packageDependencies": [\ + ["@babel/highlight", "npm:7.24.7"],\ + ["@babel/helper-validator-identifier", "npm:7.24.7"],\ + ["chalk", "npm:2.4.2"],\ + ["js-tokens", "npm:4.0.0"],\ + ["picocolors", "npm:1.0.0"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["@babel/parser", [\ @@ -1638,6 +1782,14 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@babel/types", "npm:7.19.0"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:7.25.3", {\ + "packageLocation": "./.yarn/cache/@babel-parser-npm-7.25.3-e33bb4a0e6-b55aba6421.zip/node_modules/@babel/parser/",\ + "packageDependencies": [\ + ["@babel/parser", "npm:7.25.3"],\ + ["@babel/types", "npm:7.25.2"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression", [\ @@ -5609,6 +5761,14 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["regenerator-runtime", "npm:0.13.11"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:7.25.0", {\ + "packageLocation": "./.yarn/cache/@babel-runtime-npm-7.25.0-a7bca33687-4a2a374a58.zip/node_modules/@babel/runtime/",\ + "packageDependencies": [\ + ["@babel/runtime", "npm:7.25.0"],\ + ["regenerator-runtime", "npm:0.14.1"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["@babel/template", [\ @@ -5651,6 +5811,16 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@babel/types", "npm:7.24.0"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:7.25.0", {\ + "packageLocation": "./.yarn/cache/@babel-template-npm-7.25.0-2c6ddcb43a-3f2db56871.zip/node_modules/@babel/template/",\ + "packageDependencies": [\ + ["@babel/template", "npm:7.25.0"],\ + ["@babel/code-frame", "npm:7.24.7"],\ + ["@babel/parser", "npm:7.25.3"],\ + ["@babel/types", "npm:7.25.2"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["@babel/traverse", [\ @@ -5722,6 +5892,16 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["to-fast-properties", "npm:2.0.0"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:7.25.2", {\ + "packageLocation": "./.yarn/cache/@babel-types-npm-7.25.2-7d3fc0ed1e-f73f66ba90.zip/node_modules/@babel/types/",\ + "packageDependencies": [\ + ["@babel/types", "npm:7.25.2"],\ + ["@babel/helper-string-parser", "npm:7.24.8"],\ + ["@babel/helper-validator-identifier", "npm:7.24.7"],\ + ["to-fast-properties", "npm:2.0.0"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["@balena/dockerignore", [\ @@ -6507,7 +6687,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "packageDependencies": [\ ["@fern-api/configuration", "workspace:packages/cli/configuration"],\ ["@fern-api/core-utils", "workspace:packages/commons/core-utils"],\ - ["@fern-api/fdr-sdk", "npm:0.98.16-3955e989a"],\ + ["@fern-api/fdr-sdk", "npm:0.98.18-aaf13f7f5"],\ ["@fern-api/fs-utils", "workspace:packages/commons/fs-utils"],\ ["@fern-api/task-context", "workspace:packages/cli/task-context"],\ ["@fern-fern/fiddle-sdk", "npm:0.0.584"],\ @@ -6538,7 +6718,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "packageLocation": "./packages/core/",\ "packageDependencies": [\ ["@fern-api/core", "workspace:packages/core"],\ - ["@fern-api/fdr-sdk", "npm:0.98.16-3955e989a"],\ + ["@fern-api/fdr-sdk", "npm:0.98.18-aaf13f7f5"],\ ["@fern-api/venus-api-sdk", "npm:0.0.38"],\ ["@fern-fern/fdr-test-sdk", "npm:0.0.5297"],\ ["@fern-fern/fiddle-sdk", "npm:0.0.584"],\ @@ -6627,7 +6807,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "packageLocation": "./packages/cli/docs-markdown-utils/",\ "packageDependencies": [\ ["@fern-api/docs-markdown-utils", "workspace:packages/cli/docs-markdown-utils"],\ - ["@fern-api/fdr-sdk", "npm:0.98.16-3955e989a"],\ + ["@fern-api/fdr-sdk", "npm:0.98.18-aaf13f7f5"],\ ["@fern-api/fs-utils", "workspace:packages/commons/fs-utils"],\ ["@fern-api/task-context", "workspace:packages/cli/task-context"],\ ["@types/diff", "npm:5.2.1"],\ @@ -6655,7 +6835,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "packageDependencies": [\ ["@fern-api/docs-preview", "workspace:packages/cli/docs-preview"],\ ["@fern-api/docs-resolver", "workspace:packages/cli/docs-resolver"],\ - ["@fern-api/fdr-sdk", "npm:0.98.16-3955e989a"],\ + ["@fern-api/fdr-sdk", "npm:0.98.18-aaf13f7f5"],\ ["@fern-api/fs-utils", "workspace:packages/commons/fs-utils"],\ ["@fern-api/ir-sdk", "workspace:packages/ir-sdk"],\ ["@fern-api/logger", "workspace:packages/cli/logger"],\ @@ -6697,7 +6877,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@fern-api/configuration", "workspace:packages/cli/configuration"],\ ["@fern-api/core-utils", "workspace:packages/commons/core-utils"],\ ["@fern-api/docs-markdown-utils", "workspace:packages/cli/docs-markdown-utils"],\ - ["@fern-api/fdr-sdk", "npm:0.98.16-3955e989a"],\ + ["@fern-api/fdr-sdk", "npm:0.98.18-aaf13f7f5"],\ ["@fern-api/fs-utils", "workspace:packages/commons/fs-utils"],\ ["@fern-api/ir-generator", "workspace:packages/cli/generation/ir-generator"],\ ["@fern-api/ir-sdk", "workspace:packages/ir-sdk"],\ @@ -6740,10 +6920,12 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["eslint", "npm:8.56.0"],\ ["file-type", "npm:19.0.0"],\ ["jest", "virtual:816fb67d993b0978271f762d4ccbec7209ef2546c234ca6e241662d44336c8e32c1c3c07189cfe14b67974a4840e1ed140408a7403bf9deb68c1953445072efe#npm:29.7.0"],\ - ["next-mdx-remote", "virtual:2434b322786904da39177ff03bdc5c7dbbf348dd60dac92e23ac99d71edb460312c4755faf7215e21192d941b8070d96ed0843e8723d06d3b608bb1250bd6880#npm:4.4.1"],\ + ["next-mdx-remote", "virtual:2434b322786904da39177ff03bdc5c7dbbf348dd60dac92e23ac99d71edb460312c4755faf7215e21192d941b8070d96ed0843e8723d06d3b608bb1250bd6880#npm:5.0.0"],\ ["organize-imports-cli", "npm:0.10.0"],\ ["prettier", "npm:2.7.1"],\ - ["remark-gfm", "npm:3.0.1"],\ + ["rehype-katex", "npm:7.0.0"],\ + ["remark-gfm", "npm:4.0.0"],\ + ["remark-math", "npm:6.0.0"],\ ["tinycolor2", "npm:1.6.0"],\ ["typescript", "patch:typescript@npm%3A4.6.4#~builtin::version=4.6.4&hash=5d3a66"],\ ["zod", "npm:3.22.4"]\ @@ -6757,7 +6939,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "packageDependencies": [\ ["@fern-api/ete-tests", "workspace:packages/cli/ete-tests"],\ ["@fern-api/configuration", "workspace:packages/cli/configuration"],\ - ["@fern-api/fdr-sdk", "npm:0.98.16-3955e989a"],\ + ["@fern-api/fdr-sdk", "npm:0.98.18-aaf13f7f5"],\ ["@fern-api/fs-utils", "workspace:packages/commons/fs-utils"],\ ["@fern-api/logging-execa", "workspace:packages/commons/logging-execa"],\ ["@fern-typescript/fetcher", "workspace:generators/typescript/utils/core-utilities/fetcher"],\ @@ -6783,10 +6965,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["@fern-api/fdr-sdk", [\ - ["npm:0.98.16-3955e989a", {\ - "packageLocation": "./.yarn/cache/@fern-api-fdr-sdk-npm-0.98.16-3955e989a-9e631fdef0-d3a2269365.zip/node_modules/@fern-api/fdr-sdk/",\ + ["npm:0.98.18-aaf13f7f5", {\ + "packageLocation": "./.yarn/cache/@fern-api-fdr-sdk-npm-0.98.18-aaf13f7f5-28d1ceb312-2cab59acf6.zip/node_modules/@fern-api/fdr-sdk/",\ "packageDependencies": [\ - ["@fern-api/fdr-sdk", "npm:0.98.16-3955e989a"],\ + ["@fern-api/fdr-sdk", "npm:0.98.18-aaf13f7f5"],\ ["dayjs", "npm:1.11.11"],\ ["fast-deep-equal", "npm:3.1.3"],\ ["form-data", "npm:4.0.0"],\ @@ -6796,7 +6978,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["qs", "npm:6.12.0"],\ ["tinycolor2", "npm:1.6.0"],\ ["title", "npm:3.5.3"],\ - ["ts-essentials", "virtual:9e631fdef068254621a19a5302702e20281cda5a60fe764a6b88e820d77693736ea4cdf3665f0d998ae66032ecb486659cfa24e2f50bc69f276ae549c0bf9a05#npm:10.0.1"],\ + ["ts-essentials", "virtual:28d1ceb312fdf30b3889763086eb43ef1fa907b02f19c69ce6ef14f15fd5dcd8fec7e8749fffde28c437ad670bcaad63cb76004dc0b461741f0ead797d2fa6aa#npm:10.0.1"],\ ["url-join", "npm:5.0.0"]\ ],\ "linkType": "HARD"\ @@ -7479,7 +7661,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@fern-api/configuration", "workspace:packages/cli/configuration"],\ ["@fern-api/core", "workspace:packages/core"],\ ["@fern-api/core-utils", "workspace:packages/commons/core-utils"],\ - ["@fern-api/fdr-sdk", "npm:0.98.16-3955e989a"],\ + ["@fern-api/fdr-sdk", "npm:0.98.18-aaf13f7f5"],\ ["@fern-api/ir-generator", "workspace:packages/cli/generation/ir-generator"],\ ["@fern-api/ir-sdk", "workspace:packages/ir-sdk"],\ ["@fern-api/task-context", "workspace:packages/cli/task-context"],\ @@ -7508,7 +7690,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@fern-api/core", "workspace:packages/core"],\ ["@fern-api/core-utils", "workspace:packages/commons/core-utils"],\ ["@fern-api/docs-resolver", "workspace:packages/cli/docs-resolver"],\ - ["@fern-api/fdr-sdk", "npm:0.98.16-3955e989a"],\ + ["@fern-api/fdr-sdk", "npm:0.98.18-aaf13f7f5"],\ ["@fern-api/fs-utils", "workspace:packages/commons/fs-utils"],\ ["@fern-api/ir-generator", "workspace:packages/cli/generation/ir-generator"],\ ["@fern-api/ir-migrations", "workspace:packages/cli/generation/ir-migrations"],\ @@ -7802,6 +7984,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@fern-api/core-utils", "workspace:packages/commons/core-utils"],\ ["@fern-api/fs-utils", "workspace:packages/commons/fs-utils"],\ ["@fern-api/logger", "workspace:packages/cli/logger"],\ + ["@fern-api/logging-execa", "workspace:packages/commons/logging-execa"],\ ["@fern-api/openapi-ir-to-fern", "workspace:packages/cli/openapi-ir-to-fern"],\ ["@fern-api/openapi-parser", "workspace:packages/cli/openapi-parser"],\ ["@fern-api/semver-utils", "workspace:packages/cli/semver-utils"],\ @@ -7812,6 +7995,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@types/js-yaml", "npm:4.0.8"],\ ["@types/lodash-es", "npm:4.17.12"],\ ["@types/node", "npm:18.7.18"],\ + ["@types/object-hash", "npm:3.0.6"],\ ["@types/tar", "npm:6.1.11"],\ ["axios", "npm:0.28.0"],\ ["chalk", "npm:5.3.0"],\ @@ -7820,6 +8004,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["jest", "virtual:816fb67d993b0978271f762d4ccbec7209ef2546c234ca6e241662d44336c8e32c1c3c07189cfe14b67974a4840e1ed140408a7403bf9deb68c1953445072efe#npm:29.7.0"],\ ["js-yaml", "npm:4.1.0"],\ ["lodash-es", "npm:4.17.21"],\ + ["object-hash", "npm:3.0.0"],\ ["organize-imports-cli", "npm:0.10.0"],\ ["prettier", "npm:2.7.1"],\ ["tar", "npm:6.2.1"],\ @@ -8007,17 +8192,17 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ],\ "linkType": "HARD"\ }],\ - ["npm:53.1.0", {\ - "packageLocation": "./.yarn/cache/@fern-fern-ir-sdk-npm-53.1.0-2c91c594fe-813f60b02d.zip/node_modules/@fern-fern/ir-sdk/",\ + ["npm:53.2.0", {\ + "packageLocation": "./.yarn/cache/@fern-fern-ir-sdk-npm-53.2.0-4380a0e637-73e8180071.zip/node_modules/@fern-fern/ir-sdk/",\ "packageDependencies": [\ - ["@fern-fern/ir-sdk", "npm:53.1.0"]\ + ["@fern-fern/ir-sdk", "npm:53.2.0"]\ ],\ "linkType": "HARD"\ }],\ - ["npm:53.2.0", {\ - "packageLocation": "./.yarn/cache/@fern-fern-ir-sdk-npm-53.2.0-4380a0e637-73e8180071.zip/node_modules/@fern-fern/ir-sdk/",\ + ["npm:53.4.0", {\ + "packageLocation": "./.yarn/cache/@fern-fern-ir-sdk-npm-53.4.0-36a127e1df-1201f9f39c.zip/node_modules/@fern-fern/ir-sdk/",\ "packageDependencies": [\ - ["@fern-fern/ir-sdk", "npm:53.2.0"]\ + ["@fern-fern/ir-sdk", "npm:53.4.0"]\ ],\ "linkType": "HARD"\ }]\ @@ -8554,7 +8739,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@fern-api/generator-commons", "workspace:generators/commons"],\ ["@fern-api/logger", "workspace:packages/cli/logger"],\ ["@fern-fern/generator-exec-sdk", "npm:0.0.898"],\ - ["@fern-fern/ir-sdk", "npm:53.1.0"],\ + ["@fern-fern/ir-sdk", "npm:53.4.0"],\ ["@fern-typescript/commons", "workspace:generators/typescript/utils/commons"],\ ["@fern-typescript/contexts", "workspace:generators/typescript/utils/contexts"],\ ["@types/jest", "npm:29.5.12"],\ @@ -8655,7 +8840,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@fern-api/fs-utils", "workspace:packages/commons/fs-utils"],\ ["@fern-api/logger", "workspace:packages/cli/logger"],\ ["@fern-api/logging-execa", "workspace:packages/commons/logging-execa"],\ - ["@fern-fern/ir-sdk", "npm:53.1.0"],\ + ["@fern-fern/ir-sdk", "npm:53.4.0"],\ ["@fern-typescript/fetcher", "workspace:generators/typescript/utils/core-utilities/fetcher"],\ ["@fern-typescript/zurg", "workspace:generators/typescript/utils/core-utilities/zurg"],\ ["@types/decompress", "npm:4.2.7"],\ @@ -8694,7 +8879,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@fern-typescript/contexts", "workspace:generators/typescript/utils/contexts"],\ ["@fern-api/logger", "workspace:packages/cli/logger"],\ ["@fern-fern/generator-exec-sdk", "npm:0.0.898"],\ - ["@fern-fern/ir-sdk", "npm:53.1.0"],\ + ["@fern-fern/ir-sdk", "npm:53.4.0"],\ ["@fern-typescript/commons", "workspace:generators/typescript/utils/commons"],\ ["@types/jest", "npm:29.5.12"],\ ["@types/node", "npm:18.7.18"],\ @@ -8714,7 +8899,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "packageLocation": "./generators/typescript/sdk/endpoint-error-union-generator/",\ "packageDependencies": [\ ["@fern-typescript/endpoint-error-union-generator", "workspace:generators/typescript/sdk/endpoint-error-union-generator"],\ - ["@fern-fern/ir-sdk", "npm:53.1.0"],\ + ["@fern-fern/ir-sdk", "npm:53.4.0"],\ ["@fern-typescript/commons", "workspace:generators/typescript/utils/commons"],\ ["@fern-typescript/contexts", "workspace:generators/typescript/utils/contexts"],\ ["@fern-typescript/resolvers", "workspace:generators/typescript/utils/resolvers"],\ @@ -8737,7 +8922,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "packageLocation": "./generators/typescript/sdk/environments-generator/",\ "packageDependencies": [\ ["@fern-typescript/environments-generator", "workspace:generators/typescript/sdk/environments-generator"],\ - ["@fern-fern/ir-sdk", "npm:53.1.0"],\ + ["@fern-fern/ir-sdk", "npm:53.4.0"],\ ["@fern-typescript/commons", "workspace:generators/typescript/utils/commons"],\ ["@fern-typescript/contexts", "workspace:generators/typescript/utils/contexts"],\ ["@types/jest", "npm:29.5.12"],\ @@ -8759,7 +8944,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "packageDependencies": [\ ["@fern-typescript/express-endpoint-type-schemas-generator", "workspace:generators/typescript/express/express-endpoint-type-schemas-generator"],\ ["@fern-api/core-utils", "workspace:packages/commons/core-utils"],\ - ["@fern-fern/ir-sdk", "npm:53.1.0"],\ + ["@fern-fern/ir-sdk", "npm:53.4.0"],\ ["@fern-typescript/abstract-schema-generator", "workspace:generators/typescript/utils/abstract-schema-generator"],\ ["@fern-typescript/commons", "workspace:generators/typescript/utils/commons"],\ ["@fern-typescript/contexts", "workspace:generators/typescript/utils/contexts"],\ @@ -8781,7 +8966,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "packageLocation": "./generators/typescript/express/express-error-generator/",\ "packageDependencies": [\ ["@fern-typescript/express-error-generator", "workspace:generators/typescript/express/express-error-generator"],\ - ["@fern-fern/ir-sdk", "npm:53.1.0"],\ + ["@fern-fern/ir-sdk", "npm:53.4.0"],\ ["@fern-typescript/abstract-error-class-generator", "workspace:generators/typescript/utils/abstract-error-class-generator"],\ ["@fern-typescript/commons", "workspace:generators/typescript/utils/commons"],\ ["@fern-typescript/contexts", "workspace:generators/typescript/utils/contexts"],\ @@ -8804,7 +8989,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "packageDependencies": [\ ["@fern-typescript/express-error-schema-generator", "workspace:generators/typescript/express/express-error-schema-generator"],\ ["@fern-api/core-utils", "workspace:packages/commons/core-utils"],\ - ["@fern-fern/ir-sdk", "npm:53.1.0"],\ + ["@fern-fern/ir-sdk", "npm:53.4.0"],\ ["@fern-typescript/abstract-schema-generator", "workspace:generators/typescript/utils/abstract-schema-generator"],\ ["@fern-typescript/commons", "workspace:generators/typescript/utils/commons"],\ ["@fern-typescript/contexts", "workspace:generators/typescript/utils/contexts"],\ @@ -8828,7 +9013,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@fern-typescript/express-generator", "workspace:generators/typescript/express/generator"],\ ["@fern-api/core-utils", "workspace:packages/commons/core-utils"],\ ["@fern-api/fs-utils", "workspace:packages/commons/fs-utils"],\ - ["@fern-fern/ir-sdk", "npm:53.1.0"],\ + ["@fern-fern/ir-sdk", "npm:53.4.0"],\ ["@fern-typescript/commons", "workspace:generators/typescript/utils/commons"],\ ["@fern-typescript/contexts", "workspace:generators/typescript/utils/contexts"],\ ["@fern-typescript/express-endpoint-type-schemas-generator", "workspace:generators/typescript/express/express-endpoint-type-schemas-generator"],\ @@ -8863,7 +9048,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "packageDependencies": [\ ["@fern-typescript/express-generator-cli", "workspace:generators/typescript/express/cli"],\ ["@fern-fern/generator-exec-sdk", "npm:0.0.898"],\ - ["@fern-fern/ir-sdk", "npm:53.1.0"],\ + ["@fern-fern/ir-sdk", "npm:53.4.0"],\ ["@fern-typescript/abstract-generator-cli", "workspace:generators/typescript/utils/abstract-generator-cli"],\ ["@fern-typescript/commons", "workspace:generators/typescript/utils/commons"],\ ["@fern-typescript/contexts", "workspace:generators/typescript/utils/contexts"],\ @@ -8892,7 +9077,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "packageLocation": "./generators/typescript/express/express-inlined-request-body-generator/",\ "packageDependencies": [\ ["@fern-typescript/express-inlined-request-body-generator", "workspace:generators/typescript/express/express-inlined-request-body-generator"],\ - ["@fern-fern/ir-sdk", "npm:53.1.0"],\ + ["@fern-fern/ir-sdk", "npm:53.4.0"],\ ["@fern-typescript/commons", "workspace:generators/typescript/utils/commons"],\ ["@fern-typescript/contexts", "workspace:generators/typescript/utils/contexts"],\ ["@types/jest", "npm:29.5.12"],\ @@ -8912,7 +9097,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "packageLocation": "./generators/typescript/express/express-inlined-request-body-schema-generator/",\ "packageDependencies": [\ ["@fern-typescript/express-inlined-request-schema-generator", "workspace:generators/typescript/express/express-inlined-request-body-schema-generator"],\ - ["@fern-fern/ir-sdk", "npm:53.1.0"],\ + ["@fern-fern/ir-sdk", "npm:53.4.0"],\ ["@fern-typescript/abstract-schema-generator", "workspace:generators/typescript/utils/abstract-schema-generator"],\ ["@fern-typescript/commons", "workspace:generators/typescript/utils/commons"],\ ["@fern-typescript/contexts", "workspace:generators/typescript/utils/contexts"],\ @@ -8934,7 +9119,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "packageLocation": "./generators/typescript/express/express-register-generator/",\ "packageDependencies": [\ ["@fern-typescript/express-register-generator", "workspace:generators/typescript/express/express-register-generator"],\ - ["@fern-fern/ir-sdk", "npm:53.1.0"],\ + ["@fern-fern/ir-sdk", "npm:53.4.0"],\ ["@fern-typescript/commons", "workspace:generators/typescript/utils/commons"],\ ["@fern-typescript/contexts", "workspace:generators/typescript/utils/contexts"],\ ["@fern-typescript/resolvers", "workspace:generators/typescript/utils/resolvers"],\ @@ -8958,7 +9143,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "packageLocation": "./generators/typescript/express/express-service-generator/",\ "packageDependencies": [\ ["@fern-typescript/express-service-generator", "workspace:generators/typescript/express/express-service-generator"],\ - ["@fern-fern/ir-sdk", "npm:53.1.0"],\ + ["@fern-fern/ir-sdk", "npm:53.4.0"],\ ["@fern-typescript/commons", "workspace:generators/typescript/utils/commons"],\ ["@fern-typescript/contexts", "workspace:generators/typescript/utils/contexts"],\ ["@fern-typescript/resolvers", "workspace:generators/typescript/utils/resolvers"],\ @@ -8991,6 +9176,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["depcheck", "npm:1.4.6"],\ ["eslint", "npm:8.56.0"],\ ["express", "npm:4.19.2"],\ + ["fetch-mock-jest", "virtual:7be63f1ee9eb34cb62254a57273a43b86b8b7753aa555eb261c4195c28af56c1d0f4f9ed3a307dac6e859be2fc2f1dac6612f92db051061f91f1ec4982afe62a#npm:1.5.1"],\ ["form-data", "npm:4.0.0"],\ ["form-data-encoder", "npm:4.0.2"],\ ["formdata-node", "npm:6.0.3"],\ @@ -9055,7 +9241,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "packageDependencies": [\ ["@fern-typescript/request-wrapper-generator", "workspace:generators/typescript/sdk/request-wrapper-generator"],\ ["@fern-api/core-utils", "workspace:packages/commons/core-utils"],\ - ["@fern-fern/ir-sdk", "npm:53.1.0"],\ + ["@fern-fern/ir-sdk", "npm:53.4.0"],\ ["@fern-typescript/commons", "workspace:generators/typescript/utils/commons"],\ ["@fern-typescript/contexts", "workspace:generators/typescript/utils/contexts"],\ ["@types/jest", "npm:29.5.12"],\ @@ -9076,7 +9262,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "packageLocation": "./generators/typescript/utils/resolvers/",\ "packageDependencies": [\ ["@fern-typescript/resolvers", "workspace:generators/typescript/utils/resolvers"],\ - ["@fern-fern/ir-sdk", "npm:53.1.0"],\ + ["@fern-fern/ir-sdk", "npm:53.4.0"],\ ["@fern-typescript/commons", "workspace:generators/typescript/utils/commons"],\ ["@types/jest", "npm:29.5.12"],\ ["@types/node", "npm:18.7.18"],\ @@ -9096,7 +9282,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "packageDependencies": [\ ["@fern-typescript/sdk-client-class-generator", "workspace:generators/typescript/sdk/client-class-generator"],\ ["@fern-api/core-utils", "workspace:packages/commons/core-utils"],\ - ["@fern-fern/ir-sdk", "npm:53.1.0"],\ + ["@fern-fern/ir-sdk", "npm:53.4.0"],\ ["@fern-typescript/commons", "workspace:generators/typescript/utils/commons"],\ ["@fern-typescript/contexts", "workspace:generators/typescript/utils/contexts"],\ ["@fern-typescript/resolvers", "workspace:generators/typescript/utils/resolvers"],\ @@ -9120,7 +9306,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "packageDependencies": [\ ["@fern-typescript/sdk-endpoint-type-schemas-generator", "workspace:generators/typescript/sdk/sdk-endpoint-type-schemas-generator"],\ ["@fern-api/core-utils", "workspace:packages/commons/core-utils"],\ - ["@fern-fern/ir-sdk", "npm:53.1.0"],\ + ["@fern-fern/ir-sdk", "npm:53.4.0"],\ ["@fern-typescript/abstract-schema-generator", "workspace:generators/typescript/utils/abstract-schema-generator"],\ ["@fern-typescript/commons", "workspace:generators/typescript/utils/commons"],\ ["@fern-typescript/contexts", "workspace:generators/typescript/utils/contexts"],\ @@ -9144,7 +9330,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "packageLocation": "./generators/typescript/sdk/sdk-error-generator/",\ "packageDependencies": [\ ["@fern-typescript/sdk-error-generator", "workspace:generators/typescript/sdk/sdk-error-generator"],\ - ["@fern-fern/ir-sdk", "npm:53.1.0"],\ + ["@fern-fern/ir-sdk", "npm:53.4.0"],\ ["@fern-typescript/abstract-error-class-generator", "workspace:generators/typescript/utils/abstract-error-class-generator"],\ ["@fern-typescript/commons", "workspace:generators/typescript/utils/commons"],\ ["@fern-typescript/contexts", "workspace:generators/typescript/utils/contexts"],\ @@ -9167,7 +9353,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "packageDependencies": [\ ["@fern-typescript/sdk-error-schema-generator", "workspace:generators/typescript/sdk/sdk-error-schema-generator"],\ ["@fern-api/core-utils", "workspace:packages/commons/core-utils"],\ - ["@fern-fern/ir-sdk", "npm:53.1.0"],\ + ["@fern-fern/ir-sdk", "npm:53.4.0"],\ ["@fern-typescript/abstract-schema-generator", "workspace:generators/typescript/utils/abstract-schema-generator"],\ ["@fern-typescript/commons", "workspace:generators/typescript/utils/commons"],\ ["@fern-typescript/contexts", "workspace:generators/typescript/utils/contexts"],\ @@ -9197,7 +9383,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@fern-api/logging-execa", "workspace:packages/commons/logging-execa"],\ ["@fern-fern/generator-cli-sdk", "npm:0.0.56"],\ ["@fern-fern/generator-exec-sdk", "npm:0.0.898"],\ - ["@fern-fern/ir-sdk", "npm:53.1.0"],\ + ["@fern-fern/ir-sdk", "npm:53.4.0"],\ ["@fern-fern/snippet-sdk", "npm:0.0.5526"],\ ["@fern-typescript/commons", "workspace:generators/typescript/utils/commons"],\ ["@fern-typescript/contexts", "workspace:generators/typescript/utils/contexts"],\ @@ -9247,7 +9433,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@fern-typescript/sdk-generator-cli", "workspace:generators/typescript/sdk/cli"],\ ["@fern-api/fs-utils", "workspace:packages/commons/fs-utils"],\ ["@fern-api/generator-commons", "workspace:generators/commons"],\ - ["@fern-fern/ir-sdk", "npm:53.1.0"],\ + ["@fern-fern/ir-sdk", "npm:53.4.0"],\ ["@fern-typescript/abstract-generator-cli", "workspace:generators/typescript/utils/abstract-generator-cli"],\ ["@fern-typescript/commons", "workspace:generators/typescript/utils/commons"],\ ["@fern-typescript/contexts", "workspace:generators/typescript/utils/contexts"],\ @@ -9276,7 +9462,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "packageLocation": "./generators/typescript/sdk/sdk-inlined-request-body-schema-generator/",\ "packageDependencies": [\ ["@fern-typescript/sdk-inlined-request-schema-generator", "workspace:generators/typescript/sdk/sdk-inlined-request-body-schema-generator"],\ - ["@fern-fern/ir-sdk", "npm:53.1.0"],\ + ["@fern-fern/ir-sdk", "npm:53.4.0"],\ ["@fern-typescript/abstract-schema-generator", "workspace:generators/typescript/utils/abstract-schema-generator"],\ ["@fern-typescript/commons", "workspace:generators/typescript/utils/commons"],\ ["@fern-typescript/contexts", "workspace:generators/typescript/utils/contexts"],\ @@ -9298,7 +9484,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "packageLocation": "./generators/typescript/model/type-generator/",\ "packageDependencies": [\ ["@fern-typescript/type-generator", "workspace:generators/typescript/model/type-generator"],\ - ["@fern-fern/ir-sdk", "npm:53.1.0"],\ + ["@fern-fern/ir-sdk", "npm:53.4.0"],\ ["@fern-typescript/commons", "workspace:generators/typescript/utils/commons"],\ ["@fern-typescript/contexts", "workspace:generators/typescript/utils/contexts"],\ ["@fern-typescript/union-generator", "workspace:generators/typescript/model/union-generator"],\ @@ -9320,7 +9506,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "packageLocation": "./generators/typescript/model/type-reference-converters/",\ "packageDependencies": [\ ["@fern-typescript/type-reference-converters", "workspace:generators/typescript/model/type-reference-converters"],\ - ["@fern-fern/ir-sdk", "npm:53.1.0"],\ + ["@fern-fern/ir-sdk", "npm:53.4.0"],\ ["@fern-typescript/commons", "workspace:generators/typescript/utils/commons"],\ ["@fern-typescript/resolvers", "workspace:generators/typescript/utils/resolvers"],\ ["@types/jest", "npm:29.5.12"],\ @@ -9342,7 +9528,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "packageDependencies": [\ ["@fern-typescript/type-reference-example-generator", "workspace:generators/typescript/model/type-reference-example-generator"],\ ["@fern-api/core-utils", "workspace:packages/commons/core-utils"],\ - ["@fern-fern/ir-sdk", "npm:53.1.0"],\ + ["@fern-fern/ir-sdk", "npm:53.4.0"],\ ["@fern-typescript/commons", "workspace:generators/typescript/utils/commons"],\ ["@fern-typescript/contexts", "workspace:generators/typescript/utils/contexts"],\ ["@types/jest", "npm:29.5.12"],\ @@ -9363,7 +9549,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "packageLocation": "./generators/typescript/model/type-schema-generator/",\ "packageDependencies": [\ ["@fern-typescript/type-schema-generator", "workspace:generators/typescript/model/type-schema-generator"],\ - ["@fern-fern/ir-sdk", "npm:53.1.0"],\ + ["@fern-fern/ir-sdk", "npm:53.4.0"],\ ["@fern-typescript/abstract-schema-generator", "workspace:generators/typescript/utils/abstract-schema-generator"],\ ["@fern-typescript/commons", "workspace:generators/typescript/utils/commons"],\ ["@fern-typescript/contexts", "workspace:generators/typescript/utils/contexts"],\ @@ -9387,7 +9573,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "packageDependencies": [\ ["@fern-typescript/union-generator", "workspace:generators/typescript/model/union-generator"],\ ["@fern-api/core-utils", "workspace:packages/commons/core-utils"],\ - ["@fern-fern/ir-sdk", "npm:53.1.0"],\ + ["@fern-fern/ir-sdk", "npm:53.4.0"],\ ["@fern-typescript/commons", "workspace:generators/typescript/utils/commons"],\ ["@fern-typescript/contexts", "workspace:generators/typescript/utils/contexts"],\ ["@types/jest", "npm:29.5.12"],\ @@ -9408,7 +9594,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "packageLocation": "./generators/typescript/model/union-schema-generator/",\ "packageDependencies": [\ ["@fern-typescript/union-schema-generator", "workspace:generators/typescript/model/union-schema-generator"],\ - ["@fern-fern/ir-sdk", "npm:53.1.0"],\ + ["@fern-fern/ir-sdk", "npm:53.4.0"],\ ["@fern-typescript/abstract-schema-generator", "workspace:generators/typescript/utils/abstract-schema-generator"],\ ["@fern-typescript/commons", "workspace:generators/typescript/utils/commons"],\ ["@fern-typescript/contexts", "workspace:generators/typescript/utils/contexts"],\ @@ -9902,6 +10088,16 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@jridgewell/trace-mapping", "npm:0.3.15"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:0.3.5", {\ + "packageLocation": "./.yarn/cache/@jridgewell-gen-mapping-npm-0.3.5-d8b85ebeaf-ff7a1764eb.zip/node_modules/@jridgewell/gen-mapping/",\ + "packageDependencies": [\ + ["@jridgewell/gen-mapping", "npm:0.3.5"],\ + ["@jridgewell/set-array", "npm:1.2.1"],\ + ["@jridgewell/sourcemap-codec", "npm:1.4.13"],\ + ["@jridgewell/trace-mapping", "npm:0.3.25"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["@jridgewell/resolve-uri", [\ @@ -9927,6 +10123,13 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@jridgewell/set-array", "npm:1.1.2"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:1.2.1", {\ + "packageLocation": "./.yarn/cache/@jridgewell-set-array-npm-1.2.1-2312928209-832e513a85.zip/node_modules/@jridgewell/set-array/",\ + "packageDependencies": [\ + ["@jridgewell/set-array", "npm:1.2.1"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["@jridgewell/sourcemap-codec", [\ @@ -9964,6 +10167,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ],\ "linkType": "HARD"\ }],\ + ["npm:0.3.25", {\ + "packageLocation": "./.yarn/cache/@jridgewell-trace-mapping-npm-0.3.25-c076fd2279-9d3c40d225.zip/node_modules/@jridgewell/trace-mapping/",\ + "packageDependencies": [\ + ["@jridgewell/trace-mapping", "npm:0.3.25"],\ + ["@jridgewell/resolve-uri", "npm:3.1.1"],\ + ["@jridgewell/sourcemap-codec", "npm:1.4.15"]\ + ],\ + "linkType": "HARD"\ + }],\ ["npm:0.3.9", {\ "packageLocation": "./.yarn/cache/@jridgewell-trace-mapping-npm-0.3.9-91625cd7fb-d89597752f.zip/node_modules/@jridgewell/trace-mapping/",\ "packageDependencies": [\ @@ -9994,48 +10206,55 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["@mdx-js/mdx", [\ - ["npm:2.3.0", {\ - "packageLocation": "./.yarn/cache/@mdx-js-mdx-npm-2.3.0-043b30d13e-d918766a32.zip/node_modules/@mdx-js/mdx/",\ + ["npm:3.0.1", {\ + "packageLocation": "./.yarn/cache/@mdx-js-mdx-npm-3.0.1-560c3c34e1-8222166227.zip/node_modules/@mdx-js/mdx/",\ "packageDependencies": [\ - ["@mdx-js/mdx", "npm:2.3.0"],\ + ["@mdx-js/mdx", "npm:3.0.1"],\ + ["@types/estree", "npm:1.0.1"],\ ["@types/estree-jsx", "npm:1.0.0"],\ + ["@types/hast", "npm:3.0.4"],\ ["@types/mdx", "npm:2.0.7"],\ - ["estree-util-build-jsx", "npm:2.2.2"],\ - ["estree-util-is-identifier-name", "npm:2.1.0"],\ - ["estree-util-to-js", "npm:1.2.0"],\ + ["collapse-white-space", "npm:2.1.0"],\ + ["devlop", "npm:1.1.0"],\ + ["estree-util-build-jsx", "npm:3.0.1"],\ + ["estree-util-is-identifier-name", "npm:3.0.0"],\ + ["estree-util-to-js", "npm:2.0.0"],\ ["estree-walker", "npm:3.0.3"],\ - ["hast-util-to-estree", "npm:2.3.3"],\ - ["markdown-extensions", "npm:1.1.1"],\ + ["hast-util-to-estree", "npm:3.1.0"],\ + ["hast-util-to-jsx-runtime", "npm:2.3.0"],\ + ["markdown-extensions", "npm:2.0.0"],\ ["periscopic", "npm:3.1.0"],\ - ["remark-mdx", "npm:2.3.0"],\ - ["remark-parse", "npm:10.0.2"],\ - ["remark-rehype", "npm:10.1.0"],\ - ["unified", "npm:10.1.2"],\ - ["unist-util-position-from-estree", "npm:1.1.2"],\ - ["unist-util-stringify-position", "npm:3.0.3"],\ - ["unist-util-visit", "npm:4.1.2"],\ - ["vfile", "npm:5.3.7"]\ + ["remark-mdx", "npm:3.0.1"],\ + ["remark-parse", "npm:11.0.0"],\ + ["remark-rehype", "npm:11.1.0"],\ + ["source-map", "npm:0.7.4"],\ + ["unified", "npm:11.0.5"],\ + ["unist-util-position-from-estree", "npm:2.0.0"],\ + ["unist-util-stringify-position", "npm:4.0.0"],\ + ["unist-util-visit", "npm:5.0.0"],\ + ["vfile", "npm:6.0.2"]\ ],\ "linkType": "HARD"\ }]\ ]],\ ["@mdx-js/react", [\ - ["npm:2.3.0", {\ - "packageLocation": "./.yarn/cache/@mdx-js-react-npm-2.3.0-d5582a450b-f45fe77955.zip/node_modules/@mdx-js/react/",\ + ["npm:3.0.1", {\ + "packageLocation": "./.yarn/cache/@mdx-js-react-npm-3.0.1-1ce14f6273-1063a59726.zip/node_modules/@mdx-js/react/",\ "packageDependencies": [\ - ["@mdx-js/react", "npm:2.3.0"]\ + ["@mdx-js/react", "npm:3.0.1"]\ ],\ "linkType": "SOFT"\ }],\ - ["virtual:2f161f396c89aa6fde3f0af97714bc8e846fbba2762144364ba37831c5e56b4bc72d425ce0ae326b2e9561172f5705d1565c907f149309accfeaa86c6f888e75#npm:2.3.0", {\ - "packageLocation": "./.yarn/__virtual__/@mdx-js-react-virtual-af829513ce/0/cache/@mdx-js-react-npm-2.3.0-d5582a450b-f45fe77955.zip/node_modules/@mdx-js/react/",\ + ["virtual:9bdb9c90cb2930160b57d16262755be2f252e184195556ee0b55729cb49e6cb4238feedf086cb8bc1a629504f76b95e9017875aba1641a519b4c31270137642d#npm:3.0.1", {\ + "packageLocation": "./.yarn/__virtual__/@mdx-js-react-virtual-c9aa5abc44/0/cache/@mdx-js-react-npm-3.0.1-1ce14f6273-1063a59726.zip/node_modules/@mdx-js/react/",\ "packageDependencies": [\ - ["@mdx-js/react", "virtual:2f161f396c89aa6fde3f0af97714bc8e846fbba2762144364ba37831c5e56b4bc72d425ce0ae326b2e9561172f5705d1565c907f149309accfeaa86c6f888e75#npm:2.3.0"],\ + ["@mdx-js/react", "virtual:9bdb9c90cb2930160b57d16262755be2f252e184195556ee0b55729cb49e6cb4238feedf086cb8bc1a629504f76b95e9017875aba1641a519b4c31270137642d#npm:3.0.1"],\ ["@types/mdx", "npm:2.0.7"],\ - ["@types/react", "npm:18.2.21"],\ + ["@types/react", null],\ ["react", null]\ ],\ "packagePeers": [\ + "@types/react",\ "react"\ ],\ "linkType": "HARD"\ @@ -10830,14 +11049,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["@types/hast", [\ - ["npm:2.3.5", {\ - "packageLocation": "./.yarn/cache/@types-hast-npm-2.3.5-1a6e9442f9-e435e9fbf6.zip/node_modules/@types/hast/",\ - "packageDependencies": [\ - ["@types/hast", "npm:2.3.5"],\ - ["@types/unist", "npm:2.0.8"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:3.0.4", {\ "packageLocation": "./.yarn/cache/@types-hast-npm-3.0.4-640776a343-7a973e8d16.zip/node_modules/@types/hast/",\ "packageDependencies": [\ @@ -10937,13 +11148,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["@types/js-yaml", [\ - ["npm:4.0.5", {\ - "packageLocation": "./.yarn/cache/@types-js-yaml-npm-4.0.5-bb64d71397-7dcac8c50f.zip/node_modules/@types/js-yaml/",\ - "packageDependencies": [\ - ["@types/js-yaml", "npm:4.0.5"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:4.0.8", {\ "packageLocation": "./.yarn/cache/@types-js-yaml-npm-4.0.8-9956f51f10-a5a77a5a1e.zip/node_modules/@types/js-yaml/",\ "packageDependencies": [\ @@ -11018,6 +11222,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["@types/katex", [\ + ["npm:0.16.7", {\ + "packageLocation": "./.yarn/cache/@types-katex-npm-0.16.7-c19be7ec5f-4fd15d9355.zip/node_modules/@types/katex/",\ + "packageDependencies": [\ + ["@types/katex", "npm:0.16.7"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["@types/keyv", [\ ["npm:3.1.4", {\ "packageLocation": "./.yarn/cache/@types-keyv-npm-3.1.4-a8082ea56b-e009a2bfb5.zip/node_modules/@types/keyv/",\ @@ -11073,14 +11286,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["@types/mdast", [\ - ["npm:3.0.12", {\ - "packageLocation": "./.yarn/cache/@types-mdast-npm-3.0.12-e8f7ab24f4-83adb8679b.zip/node_modules/@types/mdast/",\ - "packageDependencies": [\ - ["@types/mdast", "npm:3.0.12"],\ - ["@types/unist", "npm:2.0.8"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:4.0.3", {\ "packageLocation": "./.yarn/cache/@types-mdast-npm-4.0.3-f88ce84e2c-345c5a22fc.zip/node_modules/@types/mdast/",\ "packageDependencies": [\ @@ -11276,16 +11481,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["@types/react", [\ - ["npm:18.2.21", {\ - "packageLocation": "./.yarn/cache/@types-react-npm-18.2.21-c50bc2f785-ffed203bfe.zip/node_modules/@types/react/",\ - "packageDependencies": [\ - ["@types/react", "npm:18.2.21"],\ - ["@types/prop-types", "npm:15.7.5"],\ - ["@types/scheduler", "npm:0.16.3"],\ - ["csstype", "npm:3.1.2"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:18.2.57", {\ "packageLocation": "./.yarn/cache/@types-react-npm-18.2.57-f19486f956-01e7a34241.zip/node_modules/@types/react/",\ "packageDependencies": [\ @@ -12239,10 +12434,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ],\ "linkType": "HARD"\ }],\ - ["virtual:ceca8ad557b91711b09fdf508bc7df78063b81db3f03ff8158670c8520e4ad88d27c3a3dd632b1b4599c2c1099a5fb90612bd82484bf3001794f5df4d750609a#npm:5.3.2", {\ - "packageLocation": "./.yarn/__virtual__/acorn-jsx-virtual-db48fbe7ea/0/cache/acorn-jsx-npm-5.3.2-d7594599ea-c3d3b2a89c.zip/node_modules/acorn-jsx/",\ + ["virtual:c1ee8da2208095601afd6b3d193759b23c67e6125a2702755478ccc78efb4f432797d8f0064f8fe4eef2ef73923982b9e89749d582ec5206c34703b558f226a5#npm:5.3.2", {\ + "packageLocation": "./.yarn/__virtual__/acorn-jsx-virtual-c4836acfe2/0/cache/acorn-jsx-npm-5.3.2-d7594599ea-c3d3b2a89c.zip/node_modules/acorn-jsx/",\ "packageDependencies": [\ - ["acorn-jsx", "virtual:ceca8ad557b91711b09fdf508bc7df78063b81db3f03ff8158670c8520e4ad88d27c3a3dd632b1b4599c2c1099a5fb90612bd82484bf3001794f5df4d750609a#npm:5.3.2"],\ + ["acorn-jsx", "virtual:c1ee8da2208095601afd6b3d193759b23c67e6125a2702755478ccc78efb4f432797d8f0064f8fe4eef2ef73923982b9e89749d582ec5206c34703b558f226a5#npm:5.3.2"],\ ["@types/acorn", null],\ ["acorn", "npm:8.10.0"]\ ],\ @@ -13486,6 +13681,17 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["update-browserslist-db", "virtual:8126a959d46e6def6d7f2497c47970a23a94adac85f5be1df9be290c674503b9bcbe0dc057e2741ee222cc2a8a3b9f584b20c3a9eb5ce085704d99fc5d94514c#npm:1.0.13"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:4.23.3", {\ + "packageLocation": "./.yarn/cache/browserslist-npm-4.23.3-4e727c7b5b-7906064f99.zip/node_modules/browserslist/",\ + "packageDependencies": [\ + ["browserslist", "npm:4.23.3"],\ + ["caniuse-lite", "npm:1.0.30001650"],\ + ["electron-to-chromium", "npm:1.5.5"],\ + ["node-releases", "npm:2.0.18"],\ + ["update-browserslist-db", "virtual:4e727c7b5b033f8d5ac7299f9860cb61f5802656f7b4fea2accd32d68dc1a767387a6d23f0724065d3c65e61cb31b9eec2438ae937ce36e7602b4586ede55af6#npm:1.1.0"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["bser", [\ @@ -13890,6 +14096,13 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["caniuse-lite", "npm:1.0.30001599"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:1.0.30001650", {\ + "packageLocation": "./.yarn/cache/caniuse-lite-npm-1.0.30001650-c867884d42-4892c25200.zip/node_modules/caniuse-lite/",\ + "packageDependencies": [\ + ["caniuse-lite", "npm:1.0.30001650"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["capture-stack-trace", [\ @@ -14362,6 +14575,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["collapse-white-space", [\ + ["npm:2.1.0", {\ + "packageLocation": "./.yarn/cache/collapse-white-space-npm-2.1.0-89651f51f3-c8978b1f4e.zip/node_modules/collapse-white-space/",\ + "packageDependencies": [\ + ["collapse-white-space", "npm:2.1.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["collect-v8-coverage", [\ ["npm:1.0.1", {\ "packageLocation": "./.yarn/cache/collect-v8-coverage-npm-1.0.1-39dec86bad-4efe0a1fcc.zip/node_modules/collect-v8-coverage/",\ @@ -14662,6 +14884,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["core-js", [\ + ["npm:3.38.0", {\ + "packageLocation": "./.yarn/unplugged/core-js-npm-3.38.0-3f7d7cec9a/node_modules/core-js/",\ + "packageDependencies": [\ + ["core-js", "npm:3.38.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["core-js-compat", [\ ["npm:3.32.1", {\ "packageLocation": "./.yarn/cache/core-js-compat-npm-3.32.1-d74aca93d6-2ce0002d6d.zip/node_modules/core-js-compat/",\ @@ -15515,13 +15746,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ],\ "linkType": "HARD"\ }],\ - ["npm:5.1.0", {\ - "packageLocation": "./.yarn/cache/diff-npm-5.1.0-d24d222280-c7bf0df7c9.zip/node_modules/diff/",\ - "packageDependencies": [\ - ["diff", "npm:5.1.0"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:5.2.0", {\ "packageLocation": "./.yarn/cache/diff-npm-5.2.0-f523a581f3-12b63ca9c3.zip/node_modules/diff/",\ "packageDependencies": [\ @@ -15758,6 +15982,13 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["electron-to-chromium", "npm:1.4.711"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:1.5.5", {\ + "packageLocation": "./.yarn/cache/electron-to-chromium-npm-1.5.5-ce59e1f3c6-fcdd2797ec.zip/node_modules/electron-to-chromium/",\ + "packageDependencies": [\ + ["electron-to-chromium", "npm:1.5.5"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["emittery", [\ @@ -16463,6 +16694,13 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["escalade", "npm:3.1.1"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:3.1.2", {\ + "packageLocation": "./.yarn/cache/escalade-npm-3.1.2-5826d31cf8-1ec0977aa2.zip/node_modules/escalade/",\ + "packageDependencies": [\ + ["escalade", "npm:3.1.2"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["escape-html", [\ @@ -17015,35 +17253,29 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["estree-util-attach-comments", [\ - ["npm:2.1.1", {\ - "packageLocation": "./.yarn/cache/estree-util-attach-comments-npm-2.1.1-872c177a8a-c5c2c41c9a.zip/node_modules/estree-util-attach-comments/",\ + ["npm:3.0.0", {\ + "packageLocation": "./.yarn/cache/estree-util-attach-comments-npm-3.0.0-9a9d33e548-56254eaef3.zip/node_modules/estree-util-attach-comments/",\ "packageDependencies": [\ - ["estree-util-attach-comments", "npm:2.1.1"],\ + ["estree-util-attach-comments", "npm:3.0.0"],\ ["@types/estree", "npm:1.0.1"]\ ],\ "linkType": "HARD"\ }]\ ]],\ ["estree-util-build-jsx", [\ - ["npm:2.2.2", {\ - "packageLocation": "./.yarn/cache/estree-util-build-jsx-npm-2.2.2-c3f1420348-d008ac36a4.zip/node_modules/estree-util-build-jsx/",\ + ["npm:3.0.1", {\ + "packageLocation": "./.yarn/cache/estree-util-build-jsx-npm-3.0.1-e6ce7e25ac-185eff060e.zip/node_modules/estree-util-build-jsx/",\ "packageDependencies": [\ - ["estree-util-build-jsx", "npm:2.2.2"],\ + ["estree-util-build-jsx", "npm:3.0.1"],\ ["@types/estree-jsx", "npm:1.0.0"],\ - ["estree-util-is-identifier-name", "npm:2.1.0"],\ + ["devlop", "npm:1.1.0"],\ + ["estree-util-is-identifier-name", "npm:3.0.0"],\ ["estree-walker", "npm:3.0.3"]\ ],\ "linkType": "HARD"\ }]\ ]],\ ["estree-util-is-identifier-name", [\ - ["npm:2.1.0", {\ - "packageLocation": "./.yarn/cache/estree-util-is-identifier-name-npm-2.1.0-2b8df71baf-cab317a071.zip/node_modules/estree-util-is-identifier-name/",\ - "packageDependencies": [\ - ["estree-util-is-identifier-name", "npm:2.1.0"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:3.0.0", {\ "packageLocation": "./.yarn/cache/estree-util-is-identifier-name-npm-3.0.0-7815ea9f20-ea3909f018.zip/node_modules/estree-util-is-identifier-name/",\ "packageDependencies": [\ @@ -17053,10 +17285,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["estree-util-to-js", [\ - ["npm:1.2.0", {\ - "packageLocation": "./.yarn/cache/estree-util-to-js-npm-1.2.0-85057be9d5-93a75e1051.zip/node_modules/estree-util-to-js/",\ + ["npm:2.0.0", {\ + "packageLocation": "./.yarn/cache/estree-util-to-js-npm-2.0.0-64970efd5d-833edc94ab.zip/node_modules/estree-util-to-js/",\ "packageDependencies": [\ - ["estree-util-to-js", "npm:1.2.0"],\ + ["estree-util-to-js", "npm:2.0.0"],\ ["@types/estree-jsx", "npm:1.0.0"],\ ["astring", "npm:1.8.6"],\ ["source-map", "npm:0.7.4"]\ @@ -17065,15 +17297,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["estree-util-visit", [\ - ["npm:1.2.1", {\ - "packageLocation": "./.yarn/cache/estree-util-visit-npm-1.2.1-58d95f90a0-6feea4fdc4.zip/node_modules/estree-util-visit/",\ - "packageDependencies": [\ - ["estree-util-visit", "npm:1.2.1"],\ - ["@types/estree-jsx", "npm:1.0.0"],\ - ["@types/unist", "npm:2.0.8"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:2.0.0", {\ "packageLocation": "./.yarn/cache/estree-util-visit-npm-2.0.0-a1ee97d6ab-6444b38f22.zip/node_modules/estree-util-visit/",\ "packageDependencies": [\ @@ -17474,6 +17697,61 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "SOFT"\ }]\ ]],\ + ["fetch-mock", [\ + ["npm:9.11.0", {\ + "packageLocation": "./.yarn/cache/fetch-mock-npm-9.11.0-6ebe138f97-debc4dd83b.zip/node_modules/fetch-mock/",\ + "packageDependencies": [\ + ["fetch-mock", "npm:9.11.0"]\ + ],\ + "linkType": "SOFT"\ + }],\ + ["virtual:8a68a7dc342801e315c2abf5b13b8891ab46358e9501fd72af4e16aa542f9ff0ce94b8dd7981c489a9774de03f96383bbddd8f760edcb4104f9538c1513c1b7b#npm:9.11.0", {\ + "packageLocation": "./.yarn/__virtual__/fetch-mock-virtual-38dfc83f31/0/cache/fetch-mock-npm-9.11.0-6ebe138f97-debc4dd83b.zip/node_modules/fetch-mock/",\ + "packageDependencies": [\ + ["fetch-mock", "virtual:8a68a7dc342801e315c2abf5b13b8891ab46358e9501fd72af4e16aa542f9ff0ce94b8dd7981c489a9774de03f96383bbddd8f760edcb4104f9538c1513c1b7b#npm:9.11.0"],\ + ["@babel/core", "npm:7.25.2"],\ + ["@babel/runtime", "npm:7.25.0"],\ + ["@types/node-fetch", "npm:2.6.9"],\ + ["core-js", "npm:3.38.0"],\ + ["debug", "virtual:4758feee42453c0e31b0d2032a7b1362d6b06281699830d2da9a056f2cca72bd2c5cfdb74005fdf03a64876be8eaca2dd7b0fc2dc59d14318badf19cb22ba18e#npm:4.3.4"],\ + ["glob-to-regexp", "npm:0.4.1"],\ + ["is-subset", "npm:0.1.1"],\ + ["lodash.isequal", "npm:4.5.0"],\ + ["node-fetch", "virtual:25958c3cdea01727abe9184cd62ebdcb7f32f5bd5b1d13e8a0e1d5080a9e9f7be886906e1af797d4fcc43965772a072bf87bbcb6b0a29bf8dd97020f3fa1ccf2#npm:2.7.0"],\ + ["path-to-regexp", "npm:2.4.0"],\ + ["querystring", "npm:0.2.1"],\ + ["whatwg-url", "npm:6.5.0"]\ + ],\ + "packagePeers": [\ + "@types/node-fetch",\ + "node-fetch"\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["fetch-mock-jest", [\ + ["npm:1.5.1", {\ + "packageLocation": "./.yarn/cache/fetch-mock-jest-npm-1.5.1-43a73431f1-371b1c4a1f.zip/node_modules/fetch-mock-jest/",\ + "packageDependencies": [\ + ["fetch-mock-jest", "npm:1.5.1"]\ + ],\ + "linkType": "SOFT"\ + }],\ + ["virtual:7be63f1ee9eb34cb62254a57273a43b86b8b7753aa555eb261c4195c28af56c1d0f4f9ed3a307dac6e859be2fc2f1dac6612f92db051061f91f1ec4982afe62a#npm:1.5.1", {\ + "packageLocation": "./.yarn/__virtual__/fetch-mock-jest-virtual-8a68a7dc34/0/cache/fetch-mock-jest-npm-1.5.1-43a73431f1-371b1c4a1f.zip/node_modules/fetch-mock-jest/",\ + "packageDependencies": [\ + ["fetch-mock-jest", "virtual:7be63f1ee9eb34cb62254a57273a43b86b8b7753aa555eb261c4195c28af56c1d0f4f9ed3a307dac6e859be2fc2f1dac6612f92db051061f91f1ec4982afe62a#npm:1.5.1"],\ + ["@types/node-fetch", "npm:2.6.9"],\ + ["fetch-mock", "virtual:8a68a7dc342801e315c2abf5b13b8891ab46358e9501fd72af4e16aa542f9ff0ce94b8dd7981c489a9774de03f96383bbddd8f760edcb4104f9538c1513c1b7b#npm:9.11.0"],\ + ["node-fetch", "virtual:25958c3cdea01727abe9184cd62ebdcb7f32f5bd5b1d13e8a0e1d5080a9e9f7be886906e1af797d4fcc43965772a072bf87bbcb6b0a29bf8dd97020f3fa1ccf2#npm:2.7.0"]\ + ],\ + "packagePeers": [\ + "@types/node-fetch",\ + "node-fetch"\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["figgy-pudding", [\ ["npm:3.5.2", {\ "packageLocation": "./.yarn/cache/figgy-pudding-npm-3.5.2-2f4e3e1305-4090bd6619.zip/node_modules/figgy-pudding/",\ @@ -18288,6 +18566,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["glob-to-regexp", [\ + ["npm:0.4.1", {\ + "packageLocation": "./.yarn/cache/glob-to-regexp-npm-0.4.1-cd697e0fc7-e795f4e8f0.zip/node_modules/glob-to-regexp/",\ + "packageDependencies": [\ + ["glob-to-regexp", "npm:0.4.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["global-dirs", [\ ["npm:0.1.1", {\ "packageLocation": "./.yarn/cache/global-dirs-npm-0.1.1-87c167e806-10624f5a8d.zip/node_modules/global-dirs/",\ @@ -18669,35 +18956,165 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ - ["hast-util-to-estree", [\ - ["npm:2.3.3", {\ - "packageLocation": "./.yarn/cache/hast-util-to-estree-npm-2.3.3-a87d9b491a-a09de0214d.zip/node_modules/hast-util-to-estree/",\ + ["hast-util-from-dom", [\ + ["npm:5.0.0", {\ + "packageLocation": "./.yarn/cache/hast-util-from-dom-npm-5.0.0-0973c39ef8-bf8f96c480.zip/node_modules/hast-util-from-dom/",\ "packageDependencies": [\ - ["hast-util-to-estree", "npm:2.3.3"],\ - ["@types/estree", "npm:1.0.1"],\ - ["@types/estree-jsx", "npm:1.0.0"],\ - ["@types/hast", "npm:2.3.5"],\ - ["@types/unist", "npm:2.0.8"],\ - ["comma-separated-tokens", "npm:2.0.3"],\ - ["estree-util-attach-comments", "npm:2.1.1"],\ - ["estree-util-is-identifier-name", "npm:2.1.0"],\ - ["hast-util-whitespace", "npm:2.0.1"],\ - ["mdast-util-mdx-expression", "npm:1.3.2"],\ - ["mdast-util-mdxjs-esm", "npm:1.3.1"],\ - ["property-information", "npm:6.3.0"],\ + ["hast-util-from-dom", "npm:5.0.0"],\ + ["@types/hast", "npm:3.0.4"],\ + ["hastscript", "npm:8.0.0"],\ + ["web-namespaces", "npm:2.0.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["hast-util-from-html", [\ + ["npm:2.0.1", {\ + "packageLocation": "./.yarn/cache/hast-util-from-html-npm-2.0.1-4d2564d3e6-8decdec1f2.zip/node_modules/hast-util-from-html/",\ + "packageDependencies": [\ + ["hast-util-from-html", "npm:2.0.1"],\ + ["@types/hast", "npm:3.0.4"],\ + ["devlop", "npm:1.1.0"],\ + ["hast-util-from-parse5", "npm:8.0.1"],\ + ["parse5", "npm:7.1.2"],\ + ["vfile", "npm:6.0.2"],\ + ["vfile-message", "npm:4.0.2"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["hast-util-from-html-isomorphic", [\ + ["npm:2.0.0", {\ + "packageLocation": "./.yarn/cache/hast-util-from-html-isomorphic-npm-2.0.0-572fde4fb0-a98d02890b.zip/node_modules/hast-util-from-html-isomorphic/",\ + "packageDependencies": [\ + ["hast-util-from-html-isomorphic", "npm:2.0.0"],\ + ["@types/hast", "npm:3.0.4"],\ + ["hast-util-from-dom", "npm:5.0.0"],\ + ["hast-util-from-html", "npm:2.0.1"],\ + ["unist-util-remove-position", "npm:5.0.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["hast-util-from-parse5", [\ + ["npm:8.0.1", {\ + "packageLocation": "./.yarn/cache/hast-util-from-parse5-npm-8.0.1-5ed6a912d8-fdd1ab8b03.zip/node_modules/hast-util-from-parse5/",\ + "packageDependencies": [\ + ["hast-util-from-parse5", "npm:8.0.1"],\ + ["@types/hast", "npm:3.0.4"],\ + ["@types/unist", "npm:3.0.2"],\ + ["devlop", "npm:1.1.0"],\ + ["hastscript", "npm:8.0.0"],\ + ["property-information", "npm:6.3.0"],\ + ["vfile", "npm:6.0.2"],\ + ["vfile-location", "npm:5.0.3"],\ + ["web-namespaces", "npm:2.0.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["hast-util-is-element", [\ + ["npm:3.0.0", {\ + "packageLocation": "./.yarn/cache/hast-util-is-element-npm-3.0.0-59c73c7f56-82569a420e.zip/node_modules/hast-util-is-element/",\ + "packageDependencies": [\ + ["hast-util-is-element", "npm:3.0.0"],\ + ["@types/hast", "npm:3.0.4"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["hast-util-parse-selector", [\ + ["npm:4.0.0", {\ + "packageLocation": "./.yarn/cache/hast-util-parse-selector-npm-4.0.0-adea10ab8c-76087670d3.zip/node_modules/hast-util-parse-selector/",\ + "packageDependencies": [\ + ["hast-util-parse-selector", "npm:4.0.0"],\ + ["@types/hast", "npm:3.0.4"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["hast-util-to-estree", [\ + ["npm:3.1.0", {\ + "packageLocation": "./.yarn/cache/hast-util-to-estree-npm-3.1.0-0bbaae89ac-61272f7c18.zip/node_modules/hast-util-to-estree/",\ + "packageDependencies": [\ + ["hast-util-to-estree", "npm:3.1.0"],\ + ["@types/estree", "npm:1.0.1"],\ + ["@types/estree-jsx", "npm:1.0.0"],\ + ["@types/hast", "npm:3.0.4"],\ + ["comma-separated-tokens", "npm:2.0.3"],\ + ["devlop", "npm:1.1.0"],\ + ["estree-util-attach-comments", "npm:3.0.0"],\ + ["estree-util-is-identifier-name", "npm:3.0.0"],\ + ["hast-util-whitespace", "npm:3.0.0"],\ + ["mdast-util-mdx-expression", "npm:2.0.0"],\ + ["mdast-util-mdx-jsx", "npm:3.1.2"],\ + ["mdast-util-mdxjs-esm", "npm:2.0.1"],\ + ["property-information", "npm:6.3.0"],\ ["space-separated-tokens", "npm:2.0.2"],\ - ["style-to-object", "npm:0.4.2"],\ - ["unist-util-position", "npm:4.0.4"],\ + ["style-to-object", "npm:0.4.4"],\ + ["unist-util-position", "npm:5.0.0"],\ ["zwitch", "npm:2.0.4"]\ ],\ "linkType": "HARD"\ }]\ ]],\ + ["hast-util-to-jsx-runtime", [\ + ["npm:2.3.0", {\ + "packageLocation": "./.yarn/cache/hast-util-to-jsx-runtime-npm-2.3.0-c0e033a67f-599a97c6ec.zip/node_modules/hast-util-to-jsx-runtime/",\ + "packageDependencies": [\ + ["hast-util-to-jsx-runtime", "npm:2.3.0"],\ + ["@types/estree", "npm:1.0.1"],\ + ["@types/hast", "npm:3.0.4"],\ + ["@types/unist", "npm:3.0.2"],\ + ["comma-separated-tokens", "npm:2.0.3"],\ + ["devlop", "npm:1.1.0"],\ + ["estree-util-is-identifier-name", "npm:3.0.0"],\ + ["hast-util-whitespace", "npm:3.0.0"],\ + ["mdast-util-mdx-expression", "npm:2.0.0"],\ + ["mdast-util-mdx-jsx", "npm:3.1.2"],\ + ["mdast-util-mdxjs-esm", "npm:2.0.1"],\ + ["property-information", "npm:6.3.0"],\ + ["space-separated-tokens", "npm:2.0.2"],\ + ["style-to-object", "npm:1.0.6"],\ + ["unist-util-position", "npm:5.0.0"],\ + ["vfile-message", "npm:4.0.2"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["hast-util-to-text", [\ + ["npm:4.0.2", {\ + "packageLocation": "./.yarn/cache/hast-util-to-text-npm-4.0.2-64a96edaeb-72cce08666.zip/node_modules/hast-util-to-text/",\ + "packageDependencies": [\ + ["hast-util-to-text", "npm:4.0.2"],\ + ["@types/hast", "npm:3.0.4"],\ + ["@types/unist", "npm:3.0.2"],\ + ["hast-util-is-element", "npm:3.0.0"],\ + ["unist-util-find-after", "npm:5.0.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["hast-util-whitespace", [\ - ["npm:2.0.1", {\ - "packageLocation": "./.yarn/cache/hast-util-whitespace-npm-2.0.1-0cb2b36fdf-431be6b2f3.zip/node_modules/hast-util-whitespace/",\ + ["npm:3.0.0", {\ + "packageLocation": "./.yarn/cache/hast-util-whitespace-npm-3.0.0-215dd4954b-41d93ccce2.zip/node_modules/hast-util-whitespace/",\ "packageDependencies": [\ - ["hast-util-whitespace", "npm:2.0.1"]\ + ["hast-util-whitespace", "npm:3.0.0"],\ + ["@types/hast", "npm:3.0.4"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["hastscript", [\ + ["npm:8.0.0", {\ + "packageLocation": "./.yarn/cache/hastscript-npm-8.0.0-acde2e34a0-ae3c20223e.zip/node_modules/hastscript/",\ + "packageDependencies": [\ + ["hastscript", "npm:8.0.0"],\ + ["@types/hast", "npm:3.0.4"],\ + ["comma-separated-tokens", "npm:2.0.3"],\ + ["hast-util-parse-selector", "npm:4.0.0"],\ + ["property-information", "npm:6.3.0"],\ + ["space-separated-tokens", "npm:2.0.2"]\ ],\ "linkType": "HARD"\ }]\ @@ -19108,6 +19525,13 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["inline-style-parser", "npm:0.1.1"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:0.2.3", {\ + "packageLocation": "./.yarn/cache/inline-style-parser-npm-0.2.3-b8a7023d7a-ed6454de80.zip/node_modules/inline-style-parser/",\ + "packageDependencies": [\ + ["inline-style-parser", "npm:0.2.3"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["inquirer", [\ @@ -19291,15 +19715,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ - ["is-buffer", [\ - ["npm:2.0.5", {\ - "packageLocation": "./.yarn/cache/is-buffer-npm-2.0.5-17e563f277-764c9ad8b5.zip/node_modules/is-buffer/",\ - "packageDependencies": [\ - ["is-buffer", "npm:2.0.5"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["is-callable", [\ ["npm:1.2.4", {\ "packageLocation": "./.yarn/cache/is-callable-npm-1.2.4-03fc17459c-1a28d57dc4.zip/node_modules/is-callable/",\ @@ -19762,6 +20177,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["is-subset", [\ + ["npm:0.1.1", {\ + "packageLocation": "./.yarn/cache/is-subset-npm-0.1.1-15dc569569-97b8d7852a.zip/node_modules/is-subset/",\ + "packageDependencies": [\ + ["is-subset", "npm:0.1.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["is-symbol", [\ ["npm:1.0.4", {\ "packageLocation": "./.yarn/cache/is-symbol-npm-1.0.4-eb9baac703-92805812ef.zip/node_modules/is-symbol/",\ @@ -21025,6 +21449,16 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["katex", [\ + ["npm:0.16.11", {\ + "packageLocation": "./.yarn/cache/katex-npm-0.16.11-7c04032a99-49d9340705.zip/node_modules/katex/",\ + "packageDependencies": [\ + ["katex", "npm:0.16.11"],\ + ["commander", "npm:8.3.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["keyv", [\ ["npm:4.3.3", {\ "packageLocation": "./.yarn/cache/keyv-npm-4.3.3-a263e23789-bcc946eeec.zip/node_modules/keyv/",\ @@ -21060,13 +21494,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["kleur", "npm:3.0.3"]\ ],\ "linkType": "HARD"\ - }],\ - ["npm:4.1.5", {\ - "packageLocation": "./.yarn/cache/kleur-npm-4.1.5-46b6135f41-1dc476e327.zip/node_modules/kleur/",\ - "packageDependencies": [\ - ["kleur", "npm:4.1.5"]\ - ],\ - "linkType": "HARD"\ }]\ ]],\ ["known-css-properties", [\ @@ -21952,10 +22379,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["markdown-extensions", [\ - ["npm:1.1.1", {\ - "packageLocation": "./.yarn/cache/markdown-extensions-npm-1.1.1-633329e3d0-8a6dd128be.zip/node_modules/markdown-extensions/",\ + ["npm:2.0.0", {\ + "packageLocation": "./.yarn/cache/markdown-extensions-npm-2.0.0-ab861fd299-ec4ffcb076.zip/node_modules/markdown-extensions/",\ "packageDependencies": [\ - ["markdown-extensions", "npm:1.1.1"]\ + ["markdown-extensions", "npm:2.0.0"]\ ],\ "linkType": "HARD"\ }]\ @@ -21978,51 +22405,20 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ - ["mdast-util-definitions", [\ - ["npm:5.1.2", {\ - "packageLocation": "./.yarn/cache/mdast-util-definitions-npm-5.1.2-45a5b0f1bf-2544daccab.zip/node_modules/mdast-util-definitions/",\ - "packageDependencies": [\ - ["mdast-util-definitions", "npm:5.1.2"],\ - ["@types/mdast", "npm:3.0.12"],\ - ["@types/unist", "npm:2.0.8"],\ - ["unist-util-visit", "npm:4.1.2"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["mdast-util-find-and-replace", [\ - ["npm:2.2.2", {\ - "packageLocation": "./.yarn/cache/mdast-util-find-and-replace-npm-2.2.2-7e2061aea9-b4ce463c43.zip/node_modules/mdast-util-find-and-replace/",\ + ["npm:3.0.1", {\ + "packageLocation": "./.yarn/cache/mdast-util-find-and-replace-npm-3.0.1-284ae6ddf8-05d5c4ff02.zip/node_modules/mdast-util-find-and-replace/",\ "packageDependencies": [\ - ["mdast-util-find-and-replace", "npm:2.2.2"],\ - ["@types/mdast", "npm:3.0.12"],\ + ["mdast-util-find-and-replace", "npm:3.0.1"],\ + ["@types/mdast", "npm:4.0.3"],\ ["escape-string-regexp", "npm:5.0.0"],\ - ["unist-util-is", "npm:5.2.1"],\ - ["unist-util-visit-parents", "npm:5.1.3"]\ + ["unist-util-is", "npm:6.0.0"],\ + ["unist-util-visit-parents", "npm:6.0.1"]\ ],\ "linkType": "HARD"\ }]\ ]],\ ["mdast-util-from-markdown", [\ - ["npm:1.3.1", {\ - "packageLocation": "./.yarn/cache/mdast-util-from-markdown-npm-1.3.1-dd1eea116a-c2fac22516.zip/node_modules/mdast-util-from-markdown/",\ - "packageDependencies": [\ - ["mdast-util-from-markdown", "npm:1.3.1"],\ - ["@types/mdast", "npm:3.0.12"],\ - ["@types/unist", "npm:2.0.8"],\ - ["decode-named-character-reference", "npm:1.0.2"],\ - ["mdast-util-to-string", "npm:3.2.0"],\ - ["micromark", "npm:3.2.0"],\ - ["micromark-util-decode-numeric-character-reference", "npm:1.1.0"],\ - ["micromark-util-decode-string", "npm:1.1.0"],\ - ["micromark-util-normalize-identifier", "npm:1.1.0"],\ - ["micromark-util-symbol", "npm:1.1.0"],\ - ["micromark-util-types", "npm:1.1.0"],\ - ["unist-util-stringify-position", "npm:3.0.3"],\ - ["uvu", "npm:0.5.6"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:2.0.0", {\ "packageLocation": "./.yarn/cache/mdast-util-from-markdown-npm-2.0.0-7358a7473f-4e8d8a46b4.zip/node_modules/mdast-util-from-markdown/",\ "packageDependencies": [\ @@ -22063,94 +22459,105 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["mdast-util-gfm", [\ - ["npm:2.0.2", {\ - "packageLocation": "./.yarn/cache/mdast-util-gfm-npm-2.0.2-34fe06a303-7078cb9852.zip/node_modules/mdast-util-gfm/",\ + ["npm:3.0.0", {\ + "packageLocation": "./.yarn/cache/mdast-util-gfm-npm-3.0.0-c4b06d0013-62039d2f68.zip/node_modules/mdast-util-gfm/",\ "packageDependencies": [\ - ["mdast-util-gfm", "npm:2.0.2"],\ - ["mdast-util-from-markdown", "npm:1.3.1"],\ - ["mdast-util-gfm-autolink-literal", "npm:1.0.3"],\ - ["mdast-util-gfm-footnote", "npm:1.0.2"],\ - ["mdast-util-gfm-strikethrough", "npm:1.0.3"],\ - ["mdast-util-gfm-table", "npm:1.0.7"],\ - ["mdast-util-gfm-task-list-item", "npm:1.0.2"],\ - ["mdast-util-to-markdown", "npm:1.5.0"]\ + ["mdast-util-gfm", "npm:3.0.0"],\ + ["mdast-util-from-markdown", "npm:2.0.0"],\ + ["mdast-util-gfm-autolink-literal", "npm:2.0.0"],\ + ["mdast-util-gfm-footnote", "npm:2.0.0"],\ + ["mdast-util-gfm-strikethrough", "npm:2.0.0"],\ + ["mdast-util-gfm-table", "npm:2.0.0"],\ + ["mdast-util-gfm-task-list-item", "npm:2.0.0"],\ + ["mdast-util-to-markdown", "npm:2.1.0"]\ ],\ "linkType": "HARD"\ }]\ ]],\ ["mdast-util-gfm-autolink-literal", [\ - ["npm:1.0.3", {\ - "packageLocation": "./.yarn/cache/mdast-util-gfm-autolink-literal-npm-1.0.3-30e29b9908-1748a8727c.zip/node_modules/mdast-util-gfm-autolink-literal/",\ + ["npm:2.0.0", {\ + "packageLocation": "./.yarn/cache/mdast-util-gfm-autolink-literal-npm-2.0.0-620ccef115-10322662e5.zip/node_modules/mdast-util-gfm-autolink-literal/",\ "packageDependencies": [\ - ["mdast-util-gfm-autolink-literal", "npm:1.0.3"],\ - ["@types/mdast", "npm:3.0.12"],\ + ["mdast-util-gfm-autolink-literal", "npm:2.0.0"],\ + ["@types/mdast", "npm:4.0.3"],\ ["ccount", "npm:2.0.1"],\ - ["mdast-util-find-and-replace", "npm:2.2.2"],\ - ["micromark-util-character", "npm:1.2.0"]\ + ["devlop", "npm:1.1.0"],\ + ["mdast-util-find-and-replace", "npm:3.0.1"],\ + ["micromark-util-character", "npm:2.1.0"]\ ],\ "linkType": "HARD"\ }]\ ]],\ ["mdast-util-gfm-footnote", [\ - ["npm:1.0.2", {\ - "packageLocation": "./.yarn/cache/mdast-util-gfm-footnote-npm-1.0.2-4584cc7d97-2d77505f93.zip/node_modules/mdast-util-gfm-footnote/",\ + ["npm:2.0.0", {\ + "packageLocation": "./.yarn/cache/mdast-util-gfm-footnote-npm-2.0.0-4a167ca606-45d26b40e7.zip/node_modules/mdast-util-gfm-footnote/",\ "packageDependencies": [\ - ["mdast-util-gfm-footnote", "npm:1.0.2"],\ - ["@types/mdast", "npm:3.0.12"],\ - ["mdast-util-to-markdown", "npm:1.5.0"],\ - ["micromark-util-normalize-identifier", "npm:1.1.0"]\ + ["mdast-util-gfm-footnote", "npm:2.0.0"],\ + ["@types/mdast", "npm:4.0.3"],\ + ["devlop", "npm:1.1.0"],\ + ["mdast-util-from-markdown", "npm:2.0.0"],\ + ["mdast-util-to-markdown", "npm:2.1.0"],\ + ["micromark-util-normalize-identifier", "npm:2.0.0"]\ ],\ "linkType": "HARD"\ }]\ ]],\ ["mdast-util-gfm-strikethrough", [\ - ["npm:1.0.3", {\ - "packageLocation": "./.yarn/cache/mdast-util-gfm-strikethrough-npm-1.0.3-213cf55fea-17003340ff.zip/node_modules/mdast-util-gfm-strikethrough/",\ + ["npm:2.0.0", {\ + "packageLocation": "./.yarn/cache/mdast-util-gfm-strikethrough-npm-2.0.0-d16d95c318-fe9b1d0eba.zip/node_modules/mdast-util-gfm-strikethrough/",\ "packageDependencies": [\ - ["mdast-util-gfm-strikethrough", "npm:1.0.3"],\ - ["@types/mdast", "npm:3.0.12"],\ - ["mdast-util-to-markdown", "npm:1.5.0"]\ + ["mdast-util-gfm-strikethrough", "npm:2.0.0"],\ + ["@types/mdast", "npm:4.0.3"],\ + ["mdast-util-from-markdown", "npm:2.0.0"],\ + ["mdast-util-to-markdown", "npm:2.1.0"]\ ],\ "linkType": "HARD"\ }]\ ]],\ ["mdast-util-gfm-table", [\ - ["npm:1.0.7", {\ - "packageLocation": "./.yarn/cache/mdast-util-gfm-table-npm-1.0.7-70536e7d2d-8b8c401bb4.zip/node_modules/mdast-util-gfm-table/",\ + ["npm:2.0.0", {\ + "packageLocation": "./.yarn/cache/mdast-util-gfm-table-npm-2.0.0-45a74f064b-063a627fd0.zip/node_modules/mdast-util-gfm-table/",\ "packageDependencies": [\ - ["mdast-util-gfm-table", "npm:1.0.7"],\ - ["@types/mdast", "npm:3.0.12"],\ + ["mdast-util-gfm-table", "npm:2.0.0"],\ + ["@types/mdast", "npm:4.0.3"],\ + ["devlop", "npm:1.1.0"],\ ["markdown-table", "npm:3.0.3"],\ - ["mdast-util-from-markdown", "npm:1.3.1"],\ - ["mdast-util-to-markdown", "npm:1.5.0"]\ + ["mdast-util-from-markdown", "npm:2.0.0"],\ + ["mdast-util-to-markdown", "npm:2.1.0"]\ ],\ "linkType": "HARD"\ }]\ ]],\ ["mdast-util-gfm-task-list-item", [\ - ["npm:1.0.2", {\ - "packageLocation": "./.yarn/cache/mdast-util-gfm-task-list-item-npm-1.0.2-9de4576007-c9b86037d6.zip/node_modules/mdast-util-gfm-task-list-item/",\ + ["npm:2.0.0", {\ + "packageLocation": "./.yarn/cache/mdast-util-gfm-task-list-item-npm-2.0.0-cb1270a10f-37db90c59b.zip/node_modules/mdast-util-gfm-task-list-item/",\ "packageDependencies": [\ - ["mdast-util-gfm-task-list-item", "npm:1.0.2"],\ - ["@types/mdast", "npm:3.0.12"],\ - ["mdast-util-to-markdown", "npm:1.5.0"]\ + ["mdast-util-gfm-task-list-item", "npm:2.0.0"],\ + ["@types/mdast", "npm:4.0.3"],\ + ["devlop", "npm:1.1.0"],\ + ["mdast-util-from-markdown", "npm:2.0.0"],\ + ["mdast-util-to-markdown", "npm:2.1.0"]\ ],\ "linkType": "HARD"\ }]\ ]],\ - ["mdast-util-mdx", [\ - ["npm:2.0.1", {\ - "packageLocation": "./.yarn/cache/mdast-util-mdx-npm-2.0.1-fa9e345324-7303149230.zip/node_modules/mdast-util-mdx/",\ + ["mdast-util-math", [\ + ["npm:3.0.0", {\ + "packageLocation": "./.yarn/cache/mdast-util-math-npm-3.0.0-8b1aa5f265-dc7dfb14ae.zip/node_modules/mdast-util-math/",\ "packageDependencies": [\ - ["mdast-util-mdx", "npm:2.0.1"],\ - ["mdast-util-from-markdown", "npm:1.3.1"],\ - ["mdast-util-mdx-expression", "npm:1.3.2"],\ - ["mdast-util-mdx-jsx", "npm:2.1.4"],\ - ["mdast-util-mdxjs-esm", "npm:1.3.1"],\ - ["mdast-util-to-markdown", "npm:1.5.0"]\ + ["mdast-util-math", "npm:3.0.0"],\ + ["@types/hast", "npm:3.0.4"],\ + ["@types/mdast", "npm:4.0.3"],\ + ["devlop", "npm:1.1.0"],\ + ["longest-streak", "npm:3.1.0"],\ + ["mdast-util-from-markdown", "npm:2.0.0"],\ + ["mdast-util-to-markdown", "npm:2.1.0"],\ + ["unist-util-remove-position", "npm:5.0.0"]\ ],\ "linkType": "HARD"\ - }],\ + }]\ + ]],\ + ["mdast-util-mdx", [\ ["npm:3.0.0", {\ "packageLocation": "./.yarn/cache/mdast-util-mdx-npm-3.0.0-02a6734e33-e2b007d826.zip/node_modules/mdast-util-mdx/",\ "packageDependencies": [\ @@ -22165,18 +22572,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["mdast-util-mdx-expression", [\ - ["npm:1.3.2", {\ - "packageLocation": "./.yarn/cache/mdast-util-mdx-expression-npm-1.3.2-0cd3362efc-e4c90f26de.zip/node_modules/mdast-util-mdx-expression/",\ - "packageDependencies": [\ - ["mdast-util-mdx-expression", "npm:1.3.2"],\ - ["@types/estree-jsx", "npm:1.0.0"],\ - ["@types/hast", "npm:2.3.5"],\ - ["@types/mdast", "npm:3.0.12"],\ - ["mdast-util-from-markdown", "npm:1.3.1"],\ - ["mdast-util-to-markdown", "npm:1.5.0"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:2.0.0", {\ "packageLocation": "./.yarn/cache/mdast-util-mdx-expression-npm-2.0.0-442ccac045-4e1183000e.zip/node_modules/mdast-util-mdx-expression/",\ "packageDependencies": [\ @@ -22192,25 +22587,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["mdast-util-mdx-jsx", [\ - ["npm:2.1.4", {\ - "packageLocation": "./.yarn/cache/mdast-util-mdx-jsx-npm-2.1.4-7b04372865-add3ff2dd1.zip/node_modules/mdast-util-mdx-jsx/",\ - "packageDependencies": [\ - ["mdast-util-mdx-jsx", "npm:2.1.4"],\ - ["@types/estree-jsx", "npm:1.0.0"],\ - ["@types/hast", "npm:2.3.5"],\ - ["@types/mdast", "npm:3.0.12"],\ - ["@types/unist", "npm:2.0.8"],\ - ["ccount", "npm:2.0.1"],\ - ["mdast-util-from-markdown", "npm:1.3.1"],\ - ["mdast-util-to-markdown", "npm:1.5.0"],\ - ["parse-entities", "npm:4.0.1"],\ - ["stringify-entities", "npm:4.0.3"],\ - ["unist-util-remove-position", "npm:4.0.2"],\ - ["unist-util-stringify-position", "npm:3.0.3"],\ - ["vfile-message", "npm:3.1.4"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:3.1.2", {\ "packageLocation": "./.yarn/cache/mdast-util-mdx-jsx-npm-3.1.2-ddb83e0459-33cb8a6577.zip/node_modules/mdast-util-mdx-jsx/",\ "packageDependencies": [\ @@ -22233,18 +22609,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["mdast-util-mdxjs-esm", [\ - ["npm:1.3.1", {\ - "packageLocation": "./.yarn/cache/mdast-util-mdxjs-esm-npm-1.3.1-ae04f9d0af-ee78a4f58a.zip/node_modules/mdast-util-mdxjs-esm/",\ - "packageDependencies": [\ - ["mdast-util-mdxjs-esm", "npm:1.3.1"],\ - ["@types/estree-jsx", "npm:1.0.0"],\ - ["@types/hast", "npm:2.3.5"],\ - ["@types/mdast", "npm:3.0.12"],\ - ["mdast-util-from-markdown", "npm:1.3.1"],\ - ["mdast-util-to-markdown", "npm:1.5.0"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:2.0.1", {\ "packageLocation": "./.yarn/cache/mdast-util-mdxjs-esm-npm-2.0.1-4431068664-1f9dad04d3.zip/node_modules/mdast-util-mdxjs-esm/",\ "packageDependencies": [\ @@ -22260,15 +22624,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["mdast-util-phrasing", [\ - ["npm:3.0.1", {\ - "packageLocation": "./.yarn/cache/mdast-util-phrasing-npm-3.0.1-1da1e5bff8-c5b616d9b1.zip/node_modules/mdast-util-phrasing/",\ - "packageDependencies": [\ - ["mdast-util-phrasing", "npm:3.0.1"],\ - ["@types/mdast", "npm:3.0.12"],\ - ["unist-util-is", "npm:5.2.1"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:4.1.0", {\ "packageLocation": "./.yarn/cache/mdast-util-phrasing-npm-4.1.0-30939ebbcd-3a97533e8a.zip/node_modules/mdast-util-phrasing/",\ "packageDependencies": [\ @@ -22280,38 +22635,24 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["mdast-util-to-hast", [\ - ["npm:12.3.0", {\ - "packageLocation": "./.yarn/cache/mdast-util-to-hast-npm-12.3.0-4814ec4c82-ea40c9f07d.zip/node_modules/mdast-util-to-hast/",\ + ["npm:13.2.0", {\ + "packageLocation": "./.yarn/cache/mdast-util-to-hast-npm-13.2.0-538a77f867-7e5231ff3d.zip/node_modules/mdast-util-to-hast/",\ "packageDependencies": [\ - ["mdast-util-to-hast", "npm:12.3.0"],\ - ["@types/hast", "npm:2.3.5"],\ - ["@types/mdast", "npm:3.0.12"],\ - ["mdast-util-definitions", "npm:5.1.2"],\ - ["micromark-util-sanitize-uri", "npm:1.2.0"],\ + ["mdast-util-to-hast", "npm:13.2.0"],\ + ["@types/hast", "npm:3.0.4"],\ + ["@types/mdast", "npm:4.0.3"],\ + ["@ungap/structured-clone", "npm:1.2.0"],\ + ["devlop", "npm:1.1.0"],\ + ["micromark-util-sanitize-uri", "npm:2.0.0"],\ ["trim-lines", "npm:3.0.1"],\ - ["unist-util-generated", "npm:2.0.1"],\ - ["unist-util-position", "npm:4.0.4"],\ - ["unist-util-visit", "npm:4.1.2"]\ + ["unist-util-position", "npm:5.0.0"],\ + ["unist-util-visit", "npm:5.0.0"],\ + ["vfile", "npm:6.0.2"]\ ],\ "linkType": "HARD"\ }]\ ]],\ ["mdast-util-to-markdown", [\ - ["npm:1.5.0", {\ - "packageLocation": "./.yarn/cache/mdast-util-to-markdown-npm-1.5.0-43c48b6c48-64338eb33e.zip/node_modules/mdast-util-to-markdown/",\ - "packageDependencies": [\ - ["mdast-util-to-markdown", "npm:1.5.0"],\ - ["@types/mdast", "npm:3.0.12"],\ - ["@types/unist", "npm:2.0.8"],\ - ["longest-streak", "npm:3.1.0"],\ - ["mdast-util-phrasing", "npm:3.0.1"],\ - ["mdast-util-to-string", "npm:3.2.0"],\ - ["micromark-util-decode-string", "npm:1.1.0"],\ - ["unist-util-visit", "npm:4.1.2"],\ - ["zwitch", "npm:2.0.4"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:2.1.0", {\ "packageLocation": "./.yarn/cache/mdast-util-to-markdown-npm-2.1.0-450939723c-3a2cf3957e.zip/node_modules/mdast-util-to-markdown/",\ "packageDependencies": [\ @@ -22329,14 +22670,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["mdast-util-to-string", [\ - ["npm:3.2.0", {\ - "packageLocation": "./.yarn/cache/mdast-util-to-string-npm-3.2.0-4f9fa356be-dc40b544d5.zip/node_modules/mdast-util-to-string/",\ - "packageDependencies": [\ - ["mdast-util-to-string", "npm:3.2.0"],\ - ["@types/mdast", "npm:3.0.12"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:4.0.0", {\ "packageLocation": "./.yarn/cache/mdast-util-to-string-npm-4.0.0-fc8d9714a5-35489fb571.zip/node_modules/mdast-util-to-string/",\ "packageDependencies": [\ @@ -22448,30 +22781,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["micromark", [\ - ["npm:3.2.0", {\ - "packageLocation": "./.yarn/cache/micromark-npm-3.2.0-5351b5395d-56c15851ad.zip/node_modules/micromark/",\ - "packageDependencies": [\ - ["micromark", "npm:3.2.0"],\ - ["@types/debug", "npm:4.1.8"],\ - ["debug", "virtual:4758feee42453c0e31b0d2032a7b1362d6b06281699830d2da9a056f2cca72bd2c5cfdb74005fdf03a64876be8eaca2dd7b0fc2dc59d14318badf19cb22ba18e#npm:4.3.4"],\ - ["decode-named-character-reference", "npm:1.0.2"],\ - ["micromark-core-commonmark", "npm:1.1.0"],\ - ["micromark-factory-space", "npm:1.1.0"],\ - ["micromark-util-character", "npm:1.2.0"],\ - ["micromark-util-chunked", "npm:1.1.0"],\ - ["micromark-util-combine-extensions", "npm:1.1.0"],\ - ["micromark-util-decode-numeric-character-reference", "npm:1.1.0"],\ - ["micromark-util-encode", "npm:1.1.0"],\ - ["micromark-util-normalize-identifier", "npm:1.1.0"],\ - ["micromark-util-resolve-all", "npm:1.1.0"],\ - ["micromark-util-sanitize-uri", "npm:1.2.0"],\ - ["micromark-util-subtokenize", "npm:1.1.0"],\ - ["micromark-util-symbol", "npm:1.1.0"],\ - ["micromark-util-types", "npm:1.1.0"],\ - ["uvu", "npm:0.5.6"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:4.0.0", {\ "packageLocation": "./.yarn/cache/micromark-npm-4.0.0-ddf83a29ef-b84ab5ab1a.zip/node_modules/micromark/",\ "packageDependencies": [\ @@ -22498,29 +22807,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["micromark-core-commonmark", [\ - ["npm:1.1.0", {\ - "packageLocation": "./.yarn/cache/micromark-core-commonmark-npm-1.1.0-6f0dca58f3-c6dfedc958.zip/node_modules/micromark-core-commonmark/",\ - "packageDependencies": [\ - ["micromark-core-commonmark", "npm:1.1.0"],\ - ["decode-named-character-reference", "npm:1.0.2"],\ - ["micromark-factory-destination", "npm:1.1.0"],\ - ["micromark-factory-label", "npm:1.1.0"],\ - ["micromark-factory-space", "npm:1.1.0"],\ - ["micromark-factory-title", "npm:1.1.0"],\ - ["micromark-factory-whitespace", "npm:1.1.0"],\ - ["micromark-util-character", "npm:1.2.0"],\ - ["micromark-util-chunked", "npm:1.1.0"],\ - ["micromark-util-classify-character", "npm:1.1.0"],\ - ["micromark-util-html-tag-name", "npm:1.2.0"],\ - ["micromark-util-normalize-identifier", "npm:1.1.0"],\ - ["micromark-util-resolve-all", "npm:1.1.0"],\ - ["micromark-util-subtokenize", "npm:1.1.0"],\ - ["micromark-util-symbol", "npm:1.1.0"],\ - ["micromark-util-types", "npm:1.1.0"],\ - ["uvu", "npm:0.5.6"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:2.0.1", {\ "packageLocation": "./.yarn/cache/micromark-core-commonmark-npm-2.0.1-47bd3ea994-6a9891cc88.zip/node_modules/micromark-core-commonmark/",\ "packageDependencies": [\ @@ -22546,101 +22832,117 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["micromark-extension-gfm", [\ - ["npm:2.0.3", {\ - "packageLocation": "./.yarn/cache/micromark-extension-gfm-npm-2.0.3-134337a21c-c4a917c16d.zip/node_modules/micromark-extension-gfm/",\ - "packageDependencies": [\ - ["micromark-extension-gfm", "npm:2.0.3"],\ - ["micromark-extension-gfm-autolink-literal", "npm:1.0.5"],\ - ["micromark-extension-gfm-footnote", "npm:1.1.2"],\ - ["micromark-extension-gfm-strikethrough", "npm:1.0.7"],\ - ["micromark-extension-gfm-table", "npm:1.0.7"],\ - ["micromark-extension-gfm-tagfilter", "npm:1.0.2"],\ - ["micromark-extension-gfm-task-list-item", "npm:1.0.5"],\ - ["micromark-util-combine-extensions", "npm:1.1.0"],\ - ["micromark-util-types", "npm:1.1.0"]\ + ["npm:3.0.0", {\ + "packageLocation": "./.yarn/cache/micromark-extension-gfm-npm-3.0.0-d154ab531f-2060fa6266.zip/node_modules/micromark-extension-gfm/",\ + "packageDependencies": [\ + ["micromark-extension-gfm", "npm:3.0.0"],\ + ["micromark-extension-gfm-autolink-literal", "npm:2.1.0"],\ + ["micromark-extension-gfm-footnote", "npm:2.1.0"],\ + ["micromark-extension-gfm-strikethrough", "npm:2.1.0"],\ + ["micromark-extension-gfm-table", "npm:2.1.0"],\ + ["micromark-extension-gfm-tagfilter", "npm:2.0.0"],\ + ["micromark-extension-gfm-task-list-item", "npm:2.1.0"],\ + ["micromark-util-combine-extensions", "npm:2.0.0"],\ + ["micromark-util-types", "npm:2.0.0"]\ ],\ "linkType": "HARD"\ }]\ ]],\ ["micromark-extension-gfm-autolink-literal", [\ - ["npm:1.0.5", {\ - "packageLocation": "./.yarn/cache/micromark-extension-gfm-autolink-literal-npm-1.0.5-1ada4a6641-ec2f6bc4a3.zip/node_modules/micromark-extension-gfm-autolink-literal/",\ + ["npm:2.1.0", {\ + "packageLocation": "./.yarn/cache/micromark-extension-gfm-autolink-literal-npm-2.1.0-8fcb271412-e00a570c70.zip/node_modules/micromark-extension-gfm-autolink-literal/",\ "packageDependencies": [\ - ["micromark-extension-gfm-autolink-literal", "npm:1.0.5"],\ - ["micromark-util-character", "npm:1.2.0"],\ - ["micromark-util-sanitize-uri", "npm:1.2.0"],\ - ["micromark-util-symbol", "npm:1.1.0"],\ - ["micromark-util-types", "npm:1.1.0"]\ + ["micromark-extension-gfm-autolink-literal", "npm:2.1.0"],\ + ["micromark-util-character", "npm:2.1.0"],\ + ["micromark-util-sanitize-uri", "npm:2.0.0"],\ + ["micromark-util-symbol", "npm:2.0.0"],\ + ["micromark-util-types", "npm:2.0.0"]\ ],\ "linkType": "HARD"\ }]\ ]],\ ["micromark-extension-gfm-footnote", [\ - ["npm:1.1.2", {\ - "packageLocation": "./.yarn/cache/micromark-extension-gfm-footnote-npm-1.1.2-caa5472e3f-c151a629ee.zip/node_modules/micromark-extension-gfm-footnote/",\ + ["npm:2.1.0", {\ + "packageLocation": "./.yarn/cache/micromark-extension-gfm-footnote-npm-2.1.0-1cf783dd36-ac6fb039e9.zip/node_modules/micromark-extension-gfm-footnote/",\ "packageDependencies": [\ - ["micromark-extension-gfm-footnote", "npm:1.1.2"],\ - ["micromark-core-commonmark", "npm:1.1.0"],\ - ["micromark-factory-space", "npm:1.1.0"],\ - ["micromark-util-character", "npm:1.2.0"],\ - ["micromark-util-normalize-identifier", "npm:1.1.0"],\ - ["micromark-util-sanitize-uri", "npm:1.2.0"],\ - ["micromark-util-symbol", "npm:1.1.0"],\ - ["micromark-util-types", "npm:1.1.0"],\ - ["uvu", "npm:0.5.6"]\ + ["micromark-extension-gfm-footnote", "npm:2.1.0"],\ + ["devlop", "npm:1.1.0"],\ + ["micromark-core-commonmark", "npm:2.0.1"],\ + ["micromark-factory-space", "npm:2.0.0"],\ + ["micromark-util-character", "npm:2.1.0"],\ + ["micromark-util-normalize-identifier", "npm:2.0.0"],\ + ["micromark-util-sanitize-uri", "npm:2.0.0"],\ + ["micromark-util-symbol", "npm:2.0.0"],\ + ["micromark-util-types", "npm:2.0.0"]\ ],\ "linkType": "HARD"\ }]\ ]],\ ["micromark-extension-gfm-strikethrough", [\ - ["npm:1.0.7", {\ - "packageLocation": "./.yarn/cache/micromark-extension-gfm-strikethrough-npm-1.0.7-f5e7b0b63e-169e310a44.zip/node_modules/micromark-extension-gfm-strikethrough/",\ + ["npm:2.1.0", {\ + "packageLocation": "./.yarn/cache/micromark-extension-gfm-strikethrough-npm-2.1.0-b2aa188eba-cdb7a38dd6.zip/node_modules/micromark-extension-gfm-strikethrough/",\ "packageDependencies": [\ - ["micromark-extension-gfm-strikethrough", "npm:1.0.7"],\ - ["micromark-util-chunked", "npm:1.1.0"],\ - ["micromark-util-classify-character", "npm:1.1.0"],\ - ["micromark-util-resolve-all", "npm:1.1.0"],\ - ["micromark-util-symbol", "npm:1.1.0"],\ - ["micromark-util-types", "npm:1.1.0"],\ - ["uvu", "npm:0.5.6"]\ + ["micromark-extension-gfm-strikethrough", "npm:2.1.0"],\ + ["devlop", "npm:1.1.0"],\ + ["micromark-util-chunked", "npm:2.0.0"],\ + ["micromark-util-classify-character", "npm:2.0.0"],\ + ["micromark-util-resolve-all", "npm:2.0.0"],\ + ["micromark-util-symbol", "npm:2.0.0"],\ + ["micromark-util-types", "npm:2.0.0"]\ ],\ "linkType": "HARD"\ }]\ ]],\ ["micromark-extension-gfm-table", [\ - ["npm:1.0.7", {\ - "packageLocation": "./.yarn/cache/micromark-extension-gfm-table-npm-1.0.7-878b7528e3-4853731285.zip/node_modules/micromark-extension-gfm-table/",\ + ["npm:2.1.0", {\ + "packageLocation": "./.yarn/cache/micromark-extension-gfm-table-npm-2.1.0-cd50a7004f-249d695f5f.zip/node_modules/micromark-extension-gfm-table/",\ "packageDependencies": [\ - ["micromark-extension-gfm-table", "npm:1.0.7"],\ - ["micromark-factory-space", "npm:1.1.0"],\ - ["micromark-util-character", "npm:1.2.0"],\ - ["micromark-util-symbol", "npm:1.1.0"],\ - ["micromark-util-types", "npm:1.1.0"],\ - ["uvu", "npm:0.5.6"]\ + ["micromark-extension-gfm-table", "npm:2.1.0"],\ + ["devlop", "npm:1.1.0"],\ + ["micromark-factory-space", "npm:2.0.0"],\ + ["micromark-util-character", "npm:2.1.0"],\ + ["micromark-util-symbol", "npm:2.0.0"],\ + ["micromark-util-types", "npm:2.0.0"]\ ],\ "linkType": "HARD"\ }]\ ]],\ ["micromark-extension-gfm-tagfilter", [\ - ["npm:1.0.2", {\ - "packageLocation": "./.yarn/cache/micromark-extension-gfm-tagfilter-npm-1.0.2-87d5ea927a-7d2441df51.zip/node_modules/micromark-extension-gfm-tagfilter/",\ + ["npm:2.0.0", {\ + "packageLocation": "./.yarn/cache/micromark-extension-gfm-tagfilter-npm-2.0.0-c5ad486636-cf21552f4a.zip/node_modules/micromark-extension-gfm-tagfilter/",\ "packageDependencies": [\ - ["micromark-extension-gfm-tagfilter", "npm:1.0.2"],\ - ["micromark-util-types", "npm:1.1.0"]\ + ["micromark-extension-gfm-tagfilter", "npm:2.0.0"],\ + ["micromark-util-types", "npm:2.0.0"]\ ],\ "linkType": "HARD"\ }]\ ]],\ ["micromark-extension-gfm-task-list-item", [\ - ["npm:1.0.5", {\ - "packageLocation": "./.yarn/cache/micromark-extension-gfm-task-list-item-npm-1.0.5-0fb4eed065-929f05343d.zip/node_modules/micromark-extension-gfm-task-list-item/",\ + ["npm:2.1.0", {\ + "packageLocation": "./.yarn/cache/micromark-extension-gfm-task-list-item-npm-2.1.0-b717607894-b1ad86a4e9.zip/node_modules/micromark-extension-gfm-task-list-item/",\ "packageDependencies": [\ - ["micromark-extension-gfm-task-list-item", "npm:1.0.5"],\ - ["micromark-factory-space", "npm:1.1.0"],\ - ["micromark-util-character", "npm:1.2.0"],\ - ["micromark-util-symbol", "npm:1.1.0"],\ - ["micromark-util-types", "npm:1.1.0"],\ - ["uvu", "npm:0.5.6"]\ + ["micromark-extension-gfm-task-list-item", "npm:2.1.0"],\ + ["devlop", "npm:1.1.0"],\ + ["micromark-factory-space", "npm:2.0.0"],\ + ["micromark-util-character", "npm:2.1.0"],\ + ["micromark-util-symbol", "npm:2.0.0"],\ + ["micromark-util-types", "npm:2.0.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["micromark-extension-math", [\ + ["npm:3.1.0", {\ + "packageLocation": "./.yarn/cache/micromark-extension-math-npm-3.1.0-60c186f61c-60a9813d45.zip/node_modules/micromark-extension-math/",\ + "packageDependencies": [\ + ["micromark-extension-math", "npm:3.1.0"],\ + ["@types/katex", "npm:0.16.7"],\ + ["devlop", "npm:1.1.0"],\ + ["katex", "npm:0.16.11"],\ + ["micromark-factory-space", "npm:2.0.0"],\ + ["micromark-util-character", "npm:2.1.0"],\ + ["micromark-util-symbol", "npm:2.0.0"],\ + ["micromark-util-types", "npm:2.0.0"]\ ],\ "linkType": "HARD"\ }]\ @@ -22660,21 +22962,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["micromark-extension-mdx-expression", [\ - ["npm:1.0.8", {\ - "packageLocation": "./.yarn/cache/micromark-extension-mdx-expression-npm-1.0.8-95d2ed6eb2-49750d10c1.zip/node_modules/micromark-extension-mdx-expression/",\ - "packageDependencies": [\ - ["micromark-extension-mdx-expression", "npm:1.0.8"],\ - ["@types/estree", "npm:1.0.1"],\ - ["micromark-factory-mdx-expression", "npm:1.0.9"],\ - ["micromark-factory-space", "npm:1.1.0"],\ - ["micromark-util-character", "npm:1.2.0"],\ - ["micromark-util-events-to-acorn", "npm:1.2.3"],\ - ["micromark-util-symbol", "npm:1.1.0"],\ - ["micromark-util-types", "npm:1.1.0"],\ - ["uvu", "npm:0.5.6"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:3.0.0", {\ "packageLocation": "./.yarn/cache/micromark-extension-mdx-expression-npm-3.0.0-4efecb7218-abd6ba0acd.zip/node_modules/micromark-extension-mdx-expression/",\ "packageDependencies": [\ @@ -22692,23 +22979,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["micromark-extension-mdx-jsx", [\ - ["npm:1.0.5", {\ - "packageLocation": "./.yarn/cache/micromark-extension-mdx-jsx-npm-1.0.5-ef5b951682-0ddb7b71c2.zip/node_modules/micromark-extension-mdx-jsx/",\ - "packageDependencies": [\ - ["micromark-extension-mdx-jsx", "npm:1.0.5"],\ - ["@types/acorn", "npm:4.0.6"],\ - ["@types/estree", "npm:1.0.1"],\ - ["estree-util-is-identifier-name", "npm:2.1.0"],\ - ["micromark-factory-mdx-expression", "npm:1.0.9"],\ - ["micromark-factory-space", "npm:1.1.0"],\ - ["micromark-util-character", "npm:1.2.0"],\ - ["micromark-util-symbol", "npm:1.1.0"],\ - ["micromark-util-types", "npm:1.1.0"],\ - ["uvu", "npm:0.5.6"],\ - ["vfile-message", "npm:3.1.4"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:3.0.0", {\ "packageLocation": "./.yarn/cache/micromark-extension-mdx-jsx-npm-3.0.0-9128341ebb-5e2f45d381.zip/node_modules/micromark-extension-mdx-jsx/",\ "packageDependencies": [\ @@ -22728,14 +22998,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["micromark-extension-mdx-md", [\ - ["npm:1.0.1", {\ - "packageLocation": "./.yarn/cache/micromark-extension-mdx-md-npm-1.0.1-c524399fe3-fdeaf8f4f9.zip/node_modules/micromark-extension-mdx-md/",\ - "packageDependencies": [\ - ["micromark-extension-mdx-md", "npm:1.0.1"],\ - ["micromark-util-types", "npm:1.1.0"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:2.0.0", {\ "packageLocation": "./.yarn/cache/micromark-extension-mdx-md-npm-2.0.0-eba668824c-7daf03372f.zip/node_modules/micromark-extension-mdx-md/",\ "packageDependencies": [\ @@ -22746,51 +23008,41 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["micromark-extension-mdxjs", [\ - ["npm:1.0.1", {\ - "packageLocation": "./.yarn/cache/micromark-extension-mdxjs-npm-1.0.1-ceca8ad557-1e6bf3df76.zip/node_modules/micromark-extension-mdxjs/",\ + ["npm:3.0.0", {\ + "packageLocation": "./.yarn/cache/micromark-extension-mdxjs-npm-3.0.0-c1ee8da220-7da6f0fb0e.zip/node_modules/micromark-extension-mdxjs/",\ "packageDependencies": [\ - ["micromark-extension-mdxjs", "npm:1.0.1"],\ + ["micromark-extension-mdxjs", "npm:3.0.0"],\ ["acorn", "npm:8.10.0"],\ - ["acorn-jsx", "virtual:ceca8ad557b91711b09fdf508bc7df78063b81db3f03ff8158670c8520e4ad88d27c3a3dd632b1b4599c2c1099a5fb90612bd82484bf3001794f5df4d750609a#npm:5.3.2"],\ - ["micromark-extension-mdx-expression", "npm:1.0.8"],\ - ["micromark-extension-mdx-jsx", "npm:1.0.5"],\ - ["micromark-extension-mdx-md", "npm:1.0.1"],\ - ["micromark-extension-mdxjs-esm", "npm:1.0.5"],\ - ["micromark-util-combine-extensions", "npm:1.1.0"],\ - ["micromark-util-types", "npm:1.1.0"]\ + ["acorn-jsx", "virtual:c1ee8da2208095601afd6b3d193759b23c67e6125a2702755478ccc78efb4f432797d8f0064f8fe4eef2ef73923982b9e89749d582ec5206c34703b558f226a5#npm:5.3.2"],\ + ["micromark-extension-mdx-expression", "npm:3.0.0"],\ + ["micromark-extension-mdx-jsx", "npm:3.0.0"],\ + ["micromark-extension-mdx-md", "npm:2.0.0"],\ + ["micromark-extension-mdxjs-esm", "npm:3.0.0"],\ + ["micromark-util-combine-extensions", "npm:2.0.0"],\ + ["micromark-util-types", "npm:2.0.0"]\ ],\ "linkType": "HARD"\ }]\ ]],\ ["micromark-extension-mdxjs-esm", [\ - ["npm:1.0.5", {\ - "packageLocation": "./.yarn/cache/micromark-extension-mdxjs-esm-npm-1.0.5-cb307e2a05-7006cfa963.zip/node_modules/micromark-extension-mdxjs-esm/",\ + ["npm:3.0.0", {\ + "packageLocation": "./.yarn/cache/micromark-extension-mdxjs-esm-npm-3.0.0-f09fb4b82d-fb33d85020.zip/node_modules/micromark-extension-mdxjs-esm/",\ "packageDependencies": [\ - ["micromark-extension-mdxjs-esm", "npm:1.0.5"],\ + ["micromark-extension-mdxjs-esm", "npm:3.0.0"],\ ["@types/estree", "npm:1.0.1"],\ - ["micromark-core-commonmark", "npm:1.1.0"],\ - ["micromark-util-character", "npm:1.2.0"],\ - ["micromark-util-events-to-acorn", "npm:1.2.3"],\ - ["micromark-util-symbol", "npm:1.1.0"],\ - ["micromark-util-types", "npm:1.1.0"],\ - ["unist-util-position-from-estree", "npm:1.1.2"],\ - ["uvu", "npm:0.5.6"],\ - ["vfile-message", "npm:3.1.4"]\ + ["devlop", "npm:1.1.0"],\ + ["micromark-core-commonmark", "npm:2.0.1"],\ + ["micromark-util-character", "npm:2.1.0"],\ + ["micromark-util-events-to-acorn", "npm:2.0.2"],\ + ["micromark-util-symbol", "npm:2.0.0"],\ + ["micromark-util-types", "npm:2.0.0"],\ + ["unist-util-position-from-estree", "npm:2.0.0"],\ + ["vfile-message", "npm:4.0.2"]\ ],\ "linkType": "HARD"\ }]\ ]],\ ["micromark-factory-destination", [\ - ["npm:1.1.0", {\ - "packageLocation": "./.yarn/cache/micromark-factory-destination-npm-1.1.0-b520b52727-9e2b5fb5fe.zip/node_modules/micromark-factory-destination/",\ - "packageDependencies": [\ - ["micromark-factory-destination", "npm:1.1.0"],\ - ["micromark-util-character", "npm:1.2.0"],\ - ["micromark-util-symbol", "npm:1.1.0"],\ - ["micromark-util-types", "npm:1.1.0"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:2.0.0", {\ "packageLocation": "./.yarn/cache/micromark-factory-destination-npm-2.0.0-1b8de67781-d36e65ed1c.zip/node_modules/micromark-factory-destination/",\ "packageDependencies": [\ @@ -22803,17 +23055,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["micromark-factory-label", [\ - ["npm:1.1.0", {\ - "packageLocation": "./.yarn/cache/micromark-factory-label-npm-1.1.0-d8a5a37124-fcda48f128.zip/node_modules/micromark-factory-label/",\ - "packageDependencies": [\ - ["micromark-factory-label", "npm:1.1.0"],\ - ["micromark-util-character", "npm:1.2.0"],\ - ["micromark-util-symbol", "npm:1.1.0"],\ - ["micromark-util-types", "npm:1.1.0"],\ - ["uvu", "npm:0.5.6"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:2.0.0", {\ "packageLocation": "./.yarn/cache/micromark-factory-label-npm-2.0.0-9e92e5cd87-c021dbd0ed.zip/node_modules/micromark-factory-label/",\ "packageDependencies": [\ @@ -22827,21 +23068,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["micromark-factory-mdx-expression", [\ - ["npm:1.0.9", {\ - "packageLocation": "./.yarn/cache/micromark-factory-mdx-expression-npm-1.0.9-5e83bb23b6-7359bf3290.zip/node_modules/micromark-factory-mdx-expression/",\ - "packageDependencies": [\ - ["micromark-factory-mdx-expression", "npm:1.0.9"],\ - ["@types/estree", "npm:1.0.1"],\ - ["micromark-util-character", "npm:1.2.0"],\ - ["micromark-util-events-to-acorn", "npm:1.2.3"],\ - ["micromark-util-symbol", "npm:1.1.0"],\ - ["micromark-util-types", "npm:1.1.0"],\ - ["unist-util-position-from-estree", "npm:1.1.2"],\ - ["uvu", "npm:0.5.6"],\ - ["vfile-message", "npm:3.1.4"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:2.0.1", {\ "packageLocation": "./.yarn/cache/micromark-factory-mdx-expression-npm-2.0.1-98802cfda0-2ba0ae939d.zip/node_modules/micromark-factory-mdx-expression/",\ "packageDependencies": [\ @@ -22859,15 +23085,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["micromark-factory-space", [\ - ["npm:1.1.0", {\ - "packageLocation": "./.yarn/cache/micromark-factory-space-npm-1.1.0-30229d1b5d-b58435076b.zip/node_modules/micromark-factory-space/",\ - "packageDependencies": [\ - ["micromark-factory-space", "npm:1.1.0"],\ - ["micromark-util-character", "npm:1.2.0"],\ - ["micromark-util-types", "npm:1.1.0"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:2.0.0", {\ "packageLocation": "./.yarn/cache/micromark-factory-space-npm-2.0.0-715185b38a-4ffdcdc2f7.zip/node_modules/micromark-factory-space/",\ "packageDependencies": [\ @@ -22879,17 +23096,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["micromark-factory-title", [\ - ["npm:1.1.0", {\ - "packageLocation": "./.yarn/cache/micromark-factory-title-npm-1.1.0-4af82ae5b2-4432d3dbc8.zip/node_modules/micromark-factory-title/",\ - "packageDependencies": [\ - ["micromark-factory-title", "npm:1.1.0"],\ - ["micromark-factory-space", "npm:1.1.0"],\ - ["micromark-util-character", "npm:1.2.0"],\ - ["micromark-util-symbol", "npm:1.1.0"],\ - ["micromark-util-types", "npm:1.1.0"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:2.0.0", {\ "packageLocation": "./.yarn/cache/micromark-factory-title-npm-2.0.0-9107a1e877-39e1ac23af.zip/node_modules/micromark-factory-title/",\ "packageDependencies": [\ @@ -22900,20 +23106,9 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["micromark-util-types", "npm:2.0.0"]\ ],\ "linkType": "HARD"\ - }]\ - ]],\ - ["micromark-factory-whitespace", [\ - ["npm:1.1.0", {\ - "packageLocation": "./.yarn/cache/micromark-factory-whitespace-npm-1.1.0-8564d6a9a1-ef0fa682c7.zip/node_modules/micromark-factory-whitespace/",\ - "packageDependencies": [\ - ["micromark-factory-whitespace", "npm:1.1.0"],\ - ["micromark-factory-space", "npm:1.1.0"],\ - ["micromark-util-character", "npm:1.2.0"],\ - ["micromark-util-symbol", "npm:1.1.0"],\ - ["micromark-util-types", "npm:1.1.0"]\ - ],\ - "linkType": "HARD"\ - }],\ + }]\ + ]],\ + ["micromark-factory-whitespace", [\ ["npm:2.0.0", {\ "packageLocation": "./.yarn/cache/micromark-factory-whitespace-npm-2.0.0-53940ab034-9587c2546d.zip/node_modules/micromark-factory-whitespace/",\ "packageDependencies": [\ @@ -22927,15 +23122,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["micromark-util-character", [\ - ["npm:1.2.0", {\ - "packageLocation": "./.yarn/cache/micromark-util-character-npm-1.2.0-b42e3441af-089e79162a.zip/node_modules/micromark-util-character/",\ - "packageDependencies": [\ - ["micromark-util-character", "npm:1.2.0"],\ - ["micromark-util-symbol", "npm:1.1.0"],\ - ["micromark-util-types", "npm:1.1.0"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:2.1.0", {\ "packageLocation": "./.yarn/cache/micromark-util-character-npm-2.1.0-86cf4a520e-36ee910f84.zip/node_modules/micromark-util-character/",\ "packageDependencies": [\ @@ -22947,14 +23133,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["micromark-util-chunked", [\ - ["npm:1.1.0", {\ - "packageLocation": "./.yarn/cache/micromark-util-chunked-npm-1.1.0-2b46b7c8a2-c435bde911.zip/node_modules/micromark-util-chunked/",\ - "packageDependencies": [\ - ["micromark-util-chunked", "npm:1.1.0"],\ - ["micromark-util-symbol", "npm:1.1.0"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:2.0.0", {\ "packageLocation": "./.yarn/cache/micromark-util-chunked-npm-2.0.0-97063efe7b-324f95cccd.zip/node_modules/micromark-util-chunked/",\ "packageDependencies": [\ @@ -22965,16 +23143,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["micromark-util-classify-character", [\ - ["npm:1.1.0", {\ - "packageLocation": "./.yarn/cache/micromark-util-classify-character-npm-1.1.0-77b33fd18e-8499cb0bb1.zip/node_modules/micromark-util-classify-character/",\ - "packageDependencies": [\ - ["micromark-util-classify-character", "npm:1.1.0"],\ - ["micromark-util-character", "npm:1.2.0"],\ - ["micromark-util-symbol", "npm:1.1.0"],\ - ["micromark-util-types", "npm:1.1.0"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:2.0.0", {\ "packageLocation": "./.yarn/cache/micromark-util-classify-character-npm-2.0.0-a260c97f86-086e52904d.zip/node_modules/micromark-util-classify-character/",\ "packageDependencies": [\ @@ -22987,15 +23155,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["micromark-util-combine-extensions", [\ - ["npm:1.1.0", {\ - "packageLocation": "./.yarn/cache/micromark-util-combine-extensions-npm-1.1.0-d7734a9ec8-ee78464f5d.zip/node_modules/micromark-util-combine-extensions/",\ - "packageDependencies": [\ - ["micromark-util-combine-extensions", "npm:1.1.0"],\ - ["micromark-util-chunked", "npm:1.1.0"],\ - ["micromark-util-types", "npm:1.1.0"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:2.0.0", {\ "packageLocation": "./.yarn/cache/micromark-util-combine-extensions-npm-2.0.0-6af1824ca7-107c477003.zip/node_modules/micromark-util-combine-extensions/",\ "packageDependencies": [\ @@ -23007,14 +23166,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["micromark-util-decode-numeric-character-reference", [\ - ["npm:1.1.0", {\ - "packageLocation": "./.yarn/cache/micromark-util-decode-numeric-character-reference-npm-1.1.0-0381c1cb74-4733fe7514.zip/node_modules/micromark-util-decode-numeric-character-reference/",\ - "packageDependencies": [\ - ["micromark-util-decode-numeric-character-reference", "npm:1.1.0"],\ - ["micromark-util-symbol", "npm:1.1.0"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:2.0.1", {\ "packageLocation": "./.yarn/cache/micromark-util-decode-numeric-character-reference-npm-2.0.1-2db25e156f-9512507722.zip/node_modules/micromark-util-decode-numeric-character-reference/",\ "packageDependencies": [\ @@ -23025,17 +23176,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["micromark-util-decode-string", [\ - ["npm:1.1.0", {\ - "packageLocation": "./.yarn/cache/micromark-util-decode-string-npm-1.1.0-d3fef9c9ba-f1625155db.zip/node_modules/micromark-util-decode-string/",\ - "packageDependencies": [\ - ["micromark-util-decode-string", "npm:1.1.0"],\ - ["decode-named-character-reference", "npm:1.0.2"],\ - ["micromark-util-character", "npm:1.2.0"],\ - ["micromark-util-decode-numeric-character-reference", "npm:1.1.0"],\ - ["micromark-util-symbol", "npm:1.1.0"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:2.0.0", {\ "packageLocation": "./.yarn/cache/micromark-util-decode-string-npm-2.0.0-111ff2ba19-a75daf32a4.zip/node_modules/micromark-util-decode-string/",\ "packageDependencies": [\ @@ -23049,13 +23189,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["micromark-util-encode", [\ - ["npm:1.1.0", {\ - "packageLocation": "./.yarn/cache/micromark-util-encode-npm-1.1.0-65f415c4fe-4ef29d02b1.zip/node_modules/micromark-util-encode/",\ - "packageDependencies": [\ - ["micromark-util-encode", "npm:1.1.0"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:2.0.0", {\ "packageLocation": "./.yarn/cache/micromark-util-encode-npm-2.0.0-c2e70ee7cb-853a3f33fc.zip/node_modules/micromark-util-encode/",\ "packageDependencies": [\ @@ -23065,21 +23198,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["micromark-util-events-to-acorn", [\ - ["npm:1.2.3", {\ - "packageLocation": "./.yarn/cache/micromark-util-events-to-acorn-npm-1.2.3-e5c8bad960-aba0dadb86.zip/node_modules/micromark-util-events-to-acorn/",\ - "packageDependencies": [\ - ["micromark-util-events-to-acorn", "npm:1.2.3"],\ - ["@types/acorn", "npm:4.0.6"],\ - ["@types/estree", "npm:1.0.1"],\ - ["@types/unist", "npm:2.0.8"],\ - ["estree-util-visit", "npm:1.2.1"],\ - ["micromark-util-symbol", "npm:1.1.0"],\ - ["micromark-util-types", "npm:1.1.0"],\ - ["uvu", "npm:0.5.6"],\ - ["vfile-message", "npm:3.1.4"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:2.0.2", {\ "packageLocation": "./.yarn/cache/micromark-util-events-to-acorn-npm-2.0.2-57370cc568-bcb3eeac52.zip/node_modules/micromark-util-events-to-acorn/",\ "packageDependencies": [\ @@ -23097,13 +23215,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["micromark-util-html-tag-name", [\ - ["npm:1.2.0", {\ - "packageLocation": "./.yarn/cache/micromark-util-html-tag-name-npm-1.2.0-d8309ab06f-ccf0fa99b5.zip/node_modules/micromark-util-html-tag-name/",\ - "packageDependencies": [\ - ["micromark-util-html-tag-name", "npm:1.2.0"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:2.0.0", {\ "packageLocation": "./.yarn/cache/micromark-util-html-tag-name-npm-2.0.0-b09431e16f-d786d4486f.zip/node_modules/micromark-util-html-tag-name/",\ "packageDependencies": [\ @@ -23113,14 +23224,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["micromark-util-normalize-identifier", [\ - ["npm:1.1.0", {\ - "packageLocation": "./.yarn/cache/micromark-util-normalize-identifier-npm-1.1.0-378d909800-8655bea41f.zip/node_modules/micromark-util-normalize-identifier/",\ - "packageDependencies": [\ - ["micromark-util-normalize-identifier", "npm:1.1.0"],\ - ["micromark-util-symbol", "npm:1.1.0"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:2.0.0", {\ "packageLocation": "./.yarn/cache/micromark-util-normalize-identifier-npm-2.0.0-1bfb89b3be-b36da2d3fd.zip/node_modules/micromark-util-normalize-identifier/",\ "packageDependencies": [\ @@ -23131,14 +23234,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["micromark-util-resolve-all", [\ - ["npm:1.1.0", {\ - "packageLocation": "./.yarn/cache/micromark-util-resolve-all-npm-1.1.0-c49b6d7c36-1ce6c0237c.zip/node_modules/micromark-util-resolve-all/",\ - "packageDependencies": [\ - ["micromark-util-resolve-all", "npm:1.1.0"],\ - ["micromark-util-types", "npm:1.1.0"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:2.0.0", {\ "packageLocation": "./.yarn/cache/micromark-util-resolve-all-npm-2.0.0-113e659bd2-31fe703b85.zip/node_modules/micromark-util-resolve-all/",\ "packageDependencies": [\ @@ -23149,16 +23244,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["micromark-util-sanitize-uri", [\ - ["npm:1.2.0", {\ - "packageLocation": "./.yarn/cache/micromark-util-sanitize-uri-npm-1.2.0-b14e5e159a-6663f365c4.zip/node_modules/micromark-util-sanitize-uri/",\ - "packageDependencies": [\ - ["micromark-util-sanitize-uri", "npm:1.2.0"],\ - ["micromark-util-character", "npm:1.2.0"],\ - ["micromark-util-encode", "npm:1.1.0"],\ - ["micromark-util-symbol", "npm:1.1.0"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:2.0.0", {\ "packageLocation": "./.yarn/cache/micromark-util-sanitize-uri-npm-2.0.0-6c6c9b7f33-ea4c28bbff.zip/node_modules/micromark-util-sanitize-uri/",\ "packageDependencies": [\ @@ -23171,17 +23256,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["micromark-util-subtokenize", [\ - ["npm:1.1.0", {\ - "packageLocation": "./.yarn/cache/micromark-util-subtokenize-npm-1.1.0-72005ae28b-4a9d780c4d.zip/node_modules/micromark-util-subtokenize/",\ - "packageDependencies": [\ - ["micromark-util-subtokenize", "npm:1.1.0"],\ - ["micromark-util-chunked", "npm:1.1.0"],\ - ["micromark-util-symbol", "npm:1.1.0"],\ - ["micromark-util-types", "npm:1.1.0"],\ - ["uvu", "npm:0.5.6"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:2.0.1", {\ "packageLocation": "./.yarn/cache/micromark-util-subtokenize-npm-2.0.1-6236be35f3-5d338883ad.zip/node_modules/micromark-util-subtokenize/",\ "packageDependencies": [\ @@ -23195,13 +23269,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["micromark-util-symbol", [\ - ["npm:1.1.0", {\ - "packageLocation": "./.yarn/cache/micromark-util-symbol-npm-1.1.0-90b0865932-02414a753b.zip/node_modules/micromark-util-symbol/",\ - "packageDependencies": [\ - ["micromark-util-symbol", "npm:1.1.0"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:2.0.0", {\ "packageLocation": "./.yarn/cache/micromark-util-symbol-npm-2.0.0-dbed08e1a1-fa4a05bff5.zip/node_modules/micromark-util-symbol/",\ "packageDependencies": [\ @@ -23211,13 +23278,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["micromark-util-types", [\ - ["npm:1.1.0", {\ - "packageLocation": "./.yarn/cache/micromark-util-types-npm-1.1.0-9df6df907c-b0ef2b4b95.zip/node_modules/micromark-util-types/",\ - "packageDependencies": [\ - ["micromark-util-types", "npm:1.1.0"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:2.0.0", {\ "packageLocation": "./.yarn/cache/micromark-util-types-npm-2.0.0-75af4f6790-819fef3ab5.zip/node_modules/micromark-util-types/",\ "packageDependencies": [\ @@ -23545,15 +23605,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ - ["mri", [\ - ["npm:1.2.0", {\ - "packageLocation": "./.yarn/cache/mri-npm-1.2.0-8ecee0357d-83f515abbc.zip/node_modules/mri/",\ - "packageDependencies": [\ - ["mri", "npm:1.2.0"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["mrlint", [\ ["npm:0.1.0", {\ "packageLocation": "./.yarn/cache/mrlint-npm-0.1.0-b4ec7009da-0ae2568c97.zip/node_modules/mrlint/",\ @@ -23689,30 +23740,28 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["next-mdx-remote", [\ - ["npm:4.4.1", {\ - "packageLocation": "./.yarn/cache/next-mdx-remote-npm-4.4.1-2a17ed923a-95cd77d03a.zip/node_modules/next-mdx-remote/",\ + ["npm:5.0.0", {\ + "packageLocation": "./.yarn/cache/next-mdx-remote-npm-5.0.0-7247bc8e12-7e8dd72794.zip/node_modules/next-mdx-remote/",\ "packageDependencies": [\ - ["next-mdx-remote", "npm:4.4.1"]\ + ["next-mdx-remote", "npm:5.0.0"]\ ],\ "linkType": "SOFT"\ }],\ - ["virtual:2434b322786904da39177ff03bdc5c7dbbf348dd60dac92e23ac99d71edb460312c4755faf7215e21192d941b8070d96ed0843e8723d06d3b608bb1250bd6880#npm:4.4.1", {\ - "packageLocation": "./.yarn/__virtual__/next-mdx-remote-virtual-2f161f396c/0/cache/next-mdx-remote-npm-4.4.1-2a17ed923a-95cd77d03a.zip/node_modules/next-mdx-remote/",\ + ["virtual:2434b322786904da39177ff03bdc5c7dbbf348dd60dac92e23ac99d71edb460312c4755faf7215e21192d941b8070d96ed0843e8723d06d3b608bb1250bd6880#npm:5.0.0", {\ + "packageLocation": "./.yarn/__virtual__/next-mdx-remote-virtual-9bdb9c90cb/0/cache/next-mdx-remote-npm-5.0.0-7247bc8e12-7e8dd72794.zip/node_modules/next-mdx-remote/",\ "packageDependencies": [\ - ["next-mdx-remote", "virtual:2434b322786904da39177ff03bdc5c7dbbf348dd60dac92e23ac99d71edb460312c4755faf7215e21192d941b8070d96ed0843e8723d06d3b608bb1250bd6880#npm:4.4.1"],\ - ["@mdx-js/mdx", "npm:2.3.0"],\ - ["@mdx-js/react", "virtual:2f161f396c89aa6fde3f0af97714bc8e846fbba2762144364ba37831c5e56b4bc72d425ce0ae326b2e9561172f5705d1565c907f149309accfeaa86c6f888e75#npm:2.3.0"],\ + ["next-mdx-remote", "virtual:2434b322786904da39177ff03bdc5c7dbbf348dd60dac92e23ac99d71edb460312c4755faf7215e21192d941b8070d96ed0843e8723d06d3b608bb1250bd6880#npm:5.0.0"],\ + ["@babel/code-frame", "npm:7.23.5"],\ + ["@mdx-js/mdx", "npm:3.0.1"],\ + ["@mdx-js/react", "virtual:9bdb9c90cb2930160b57d16262755be2f252e184195556ee0b55729cb49e6cb4238feedf086cb8bc1a629504f76b95e9017875aba1641a519b4c31270137642d#npm:3.0.1"],\ ["@types/react", null],\ - ["@types/react-dom", null],\ ["react", null],\ - ["react-dom", null],\ - ["vfile", "npm:5.3.7"],\ - ["vfile-matter", "npm:3.0.1"]\ + ["unist-util-remove", "npm:3.1.1"],\ + ["vfile", "npm:6.0.2"],\ + ["vfile-matter", "npm:5.0.0"]\ ],\ "packagePeers": [\ - "@types/react-dom",\ "@types/react",\ - "react-dom",\ "react"\ ],\ "linkType": "HARD"\ @@ -23861,6 +23910,13 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["node-releases", "npm:2.0.14"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:2.0.18", {\ + "packageLocation": "./.yarn/cache/node-releases-npm-2.0.18-51abc46668-ef55a3d853.zip/node_modules/node-releases/",\ + "packageDependencies": [\ + ["node-releases", "npm:2.0.18"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["nopt", [\ @@ -25054,6 +25110,13 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["path-to-regexp", "npm:0.1.7"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:2.4.0", {\ + "packageLocation": "./.yarn/cache/path-to-regexp-npm-2.4.0-ce02fd84d9-581175bf29.zip/node_modules/path-to-regexp/",\ + "packageDependencies": [\ + ["path-to-regexp", "npm:2.4.0"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["path-type", [\ @@ -25111,6 +25174,13 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["picocolors", "npm:1.0.0"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:1.0.1", {\ + "packageLocation": "./.yarn/cache/picocolors-npm-1.0.1-39442f3da8-fa68166d1f.zip/node_modules/picocolors/",\ + "packageDependencies": [\ + ["picocolors", "npm:1.0.1"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["picomatch", [\ @@ -25853,6 +25923,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["querystring", [\ + ["npm:0.2.1", {\ + "packageLocation": "./.yarn/cache/querystring-npm-0.2.1-15cb60859d-7b83b45d64.zip/node_modules/querystring/",\ + "packageDependencies": [\ + ["querystring", "npm:0.2.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["querystringify", [\ ["npm:2.2.0", {\ "packageLocation": "./.yarn/cache/querystringify-npm-2.2.0-4e77c9f606-5641ea231b.zip/node_modules/querystringify/",\ @@ -26250,6 +26329,13 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["regenerator-runtime", "npm:0.13.11"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:0.14.1", {\ + "packageLocation": "./.yarn/cache/regenerator-runtime-npm-0.14.1-a6c97c609a-9f57c93277.zip/node_modules/regenerator-runtime/",\ + "packageDependencies": [\ + ["regenerator-runtime", "npm:0.14.1"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["regenerator-transform", [\ @@ -26404,51 +26490,96 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["rehype-katex", [\ + ["npm:7.0.0", {\ + "packageLocation": "./.yarn/cache/rehype-katex-npm-7.0.0-704b6f2147-3184cf7635.zip/node_modules/rehype-katex/",\ + "packageDependencies": [\ + ["rehype-katex", "npm:7.0.0"],\ + ["@types/hast", "npm:3.0.4"],\ + ["@types/katex", "npm:0.16.7"],\ + ["hast-util-from-html-isomorphic", "npm:2.0.0"],\ + ["hast-util-to-text", "npm:4.0.2"],\ + ["katex", "npm:0.16.11"],\ + ["unist-util-visit-parents", "npm:6.0.1"],\ + ["vfile", "npm:6.0.2"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["remark-gfm", [\ - ["npm:3.0.1", {\ - "packageLocation": "./.yarn/cache/remark-gfm-npm-3.0.1-4a9f6f751e-02254f74d6.zip/node_modules/remark-gfm/",\ + ["npm:4.0.0", {\ + "packageLocation": "./.yarn/cache/remark-gfm-npm-4.0.0-8bb699e315-84bea84e38.zip/node_modules/remark-gfm/",\ + "packageDependencies": [\ + ["remark-gfm", "npm:4.0.0"],\ + ["@types/mdast", "npm:4.0.3"],\ + ["mdast-util-gfm", "npm:3.0.0"],\ + ["micromark-extension-gfm", "npm:3.0.0"],\ + ["remark-parse", "npm:11.0.0"],\ + ["remark-stringify", "npm:11.0.0"],\ + ["unified", "npm:11.0.5"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["remark-math", [\ + ["npm:6.0.0", {\ + "packageLocation": "./.yarn/cache/remark-math-npm-6.0.0-747000722b-fef489acb6.zip/node_modules/remark-math/",\ "packageDependencies": [\ - ["remark-gfm", "npm:3.0.1"],\ - ["@types/mdast", "npm:3.0.12"],\ - ["mdast-util-gfm", "npm:2.0.2"],\ - ["micromark-extension-gfm", "npm:2.0.3"],\ - ["unified", "npm:10.1.2"]\ + ["remark-math", "npm:6.0.0"],\ + ["@types/mdast", "npm:4.0.3"],\ + ["mdast-util-math", "npm:3.0.0"],\ + ["micromark-extension-math", "npm:3.1.0"],\ + ["unified", "npm:11.0.5"]\ ],\ "linkType": "HARD"\ }]\ ]],\ ["remark-mdx", [\ - ["npm:2.3.0", {\ - "packageLocation": "./.yarn/cache/remark-mdx-npm-2.3.0-9c964d4d99-98486986c5.zip/node_modules/remark-mdx/",\ + ["npm:3.0.1", {\ + "packageLocation": "./.yarn/cache/remark-mdx-npm-3.0.1-898cfa3bb1-e7fcffbe1c.zip/node_modules/remark-mdx/",\ "packageDependencies": [\ - ["remark-mdx", "npm:2.3.0"],\ - ["mdast-util-mdx", "npm:2.0.1"],\ - ["micromark-extension-mdxjs", "npm:1.0.1"]\ + ["remark-mdx", "npm:3.0.1"],\ + ["mdast-util-mdx", "npm:3.0.0"],\ + ["micromark-extension-mdxjs", "npm:3.0.0"]\ ],\ "linkType": "HARD"\ }]\ ]],\ ["remark-parse", [\ - ["npm:10.0.2", {\ - "packageLocation": "./.yarn/cache/remark-parse-npm-10.0.2-9d19189a4e-5041b4b447.zip/node_modules/remark-parse/",\ + ["npm:11.0.0", {\ + "packageLocation": "./.yarn/cache/remark-parse-npm-11.0.0-6484fba69e-d83d245290.zip/node_modules/remark-parse/",\ "packageDependencies": [\ - ["remark-parse", "npm:10.0.2"],\ - ["@types/mdast", "npm:3.0.12"],\ - ["mdast-util-from-markdown", "npm:1.3.1"],\ - ["unified", "npm:10.1.2"]\ + ["remark-parse", "npm:11.0.0"],\ + ["@types/mdast", "npm:4.0.3"],\ + ["mdast-util-from-markdown", "npm:2.0.0"],\ + ["micromark-util-types", "npm:2.0.0"],\ + ["unified", "npm:11.0.5"]\ ],\ "linkType": "HARD"\ }]\ ]],\ ["remark-rehype", [\ - ["npm:10.1.0", {\ - "packageLocation": "./.yarn/cache/remark-rehype-npm-10.1.0-bd8e6f7d8b-b9ac8acff3.zip/node_modules/remark-rehype/",\ + ["npm:11.1.0", {\ + "packageLocation": "./.yarn/cache/remark-rehype-npm-11.1.0-52f1fb906c-f0c731f0ab.zip/node_modules/remark-rehype/",\ + "packageDependencies": [\ + ["remark-rehype", "npm:11.1.0"],\ + ["@types/hast", "npm:3.0.4"],\ + ["@types/mdast", "npm:4.0.3"],\ + ["mdast-util-to-hast", "npm:13.2.0"],\ + ["unified", "npm:11.0.5"],\ + ["vfile", "npm:6.0.2"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["remark-stringify", [\ + ["npm:11.0.0", {\ + "packageLocation": "./.yarn/cache/remark-stringify-npm-11.0.0-b41a557b8d-59e07460eb.zip/node_modules/remark-stringify/",\ "packageDependencies": [\ - ["remark-rehype", "npm:10.1.0"],\ - ["@types/hast", "npm:2.3.5"],\ - ["@types/mdast", "npm:3.0.12"],\ - ["mdast-util-to-hast", "npm:12.3.0"],\ - ["unified", "npm:10.1.2"]\ + ["remark-stringify", "npm:11.0.0"],\ + ["@types/mdast", "npm:4.0.3"],\ + ["mdast-util-to-markdown", "npm:2.1.0"],\ + ["unified", "npm:11.0.5"]\ ],\ "linkType": "HARD"\ }]\ @@ -26818,16 +26949,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ - ["sade", [\ - ["npm:1.8.1", {\ - "packageLocation": "./.yarn/cache/sade-npm-1.8.1-4759dc74c1-0756e5b04c.zip/node_modules/sade/",\ - "packageDependencies": [\ - ["sade", "npm:1.8.1"],\ - ["mri", "npm:1.2.0"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["safe-array-concat", [\ ["npm:1.0.1", {\ "packageLocation": "./.yarn/cache/safe-array-concat-npm-1.0.1-8a42907bbf-001ecf1d8a.zip/node_modules/safe-array-concat/",\ @@ -28101,13 +28222,21 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["style-to-object", [\ - ["npm:0.4.2", {\ - "packageLocation": "./.yarn/cache/style-to-object-npm-0.4.2-e00b8a7105-314a80bcfa.zip/node_modules/style-to-object/",\ + ["npm:0.4.4", {\ + "packageLocation": "./.yarn/cache/style-to-object-npm-0.4.4-703ebb5748-41656c06f9.zip/node_modules/style-to-object/",\ "packageDependencies": [\ - ["style-to-object", "npm:0.4.2"],\ + ["style-to-object", "npm:0.4.4"],\ ["inline-style-parser", "npm:0.1.1"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:1.0.6", {\ + "packageLocation": "./.yarn/cache/style-to-object-npm-1.0.6-b50013e448-5b58295dcc.zip/node_modules/style-to-object/",\ + "packageDependencies": [\ + ["style-to-object", "npm:1.0.6"],\ + ["inline-style-parser", "npm:0.2.3"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["stylelint", [\ @@ -29011,10 +29140,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ],\ "linkType": "SOFT"\ }],\ - ["virtual:9e631fdef068254621a19a5302702e20281cda5a60fe764a6b88e820d77693736ea4cdf3665f0d998ae66032ecb486659cfa24e2f50bc69f276ae549c0bf9a05#npm:10.0.1", {\ - "packageLocation": "./.yarn/__virtual__/ts-essentials-virtual-292753b767/0/cache/ts-essentials-npm-10.0.1-1673b298f7-f70583c154.zip/node_modules/ts-essentials/",\ + ["virtual:28d1ceb312fdf30b3889763086eb43ef1fa907b02f19c69ce6ef14f15fd5dcd8fec7e8749fffde28c437ad670bcaad63cb76004dc0b461741f0ead797d2fa6aa#npm:10.0.1", {\ + "packageLocation": "./.yarn/__virtual__/ts-essentials-virtual-775a1e0e35/0/cache/ts-essentials-npm-10.0.1-1673b298f7-f70583c154.zip/node_modules/ts-essentials/",\ "packageDependencies": [\ - ["ts-essentials", "virtual:9e631fdef068254621a19a5302702e20281cda5a60fe764a6b88e820d77693736ea4cdf3665f0d998ae66032ecb486659cfa24e2f50bc69f276ae549c0bf9a05#npm:10.0.1"],\ + ["ts-essentials", "virtual:28d1ceb312fdf30b3889763086eb43ef1fa907b02f19c69ce6ef14f15fd5dcd8fec7e8749fffde28c437ad670bcaad63cb76004dc0b461741f0ead797d2fa6aa#npm:10.0.1"],\ ["@types/typescript", null],\ ["typescript", null]\ ],\ @@ -29579,17 +29708,17 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["unified", [\ - ["npm:10.1.2", {\ - "packageLocation": "./.yarn/cache/unified-npm-10.1.2-731093c9be-053e7c65ed.zip/node_modules/unified/",\ + ["npm:11.0.5", {\ + "packageLocation": "./.yarn/cache/unified-npm-11.0.5-ac5333017e-b3bf7fd6f5.zip/node_modules/unified/",\ "packageDependencies": [\ - ["unified", "npm:10.1.2"],\ - ["@types/unist", "npm:2.0.8"],\ + ["unified", "npm:11.0.5"],\ + ["@types/unist", "npm:3.0.2"],\ ["bail", "npm:2.0.2"],\ + ["devlop", "npm:1.1.0"],\ ["extend", "npm:3.0.2"],\ - ["is-buffer", "npm:2.0.5"],\ ["is-plain-obj", "npm:4.1.0"],\ ["trough", "npm:2.1.0"],\ - ["vfile", "npm:5.3.7"]\ + ["vfile", "npm:6.0.2"]\ ],\ "linkType": "HARD"\ }]\ @@ -29624,11 +29753,13 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ - ["unist-util-generated", [\ - ["npm:2.0.1", {\ - "packageLocation": "./.yarn/cache/unist-util-generated-npm-2.0.1-cba405dd6d-6221ad0571.zip/node_modules/unist-util-generated/",\ + ["unist-util-find-after", [\ + ["npm:5.0.0", {\ + "packageLocation": "./.yarn/cache/unist-util-find-after-npm-5.0.0-04b78835bc-e64bd5ebee.zip/node_modules/unist-util-find-after/",\ "packageDependencies": [\ - ["unist-util-generated", "npm:2.0.1"]\ + ["unist-util-find-after", "npm:5.0.0"],\ + ["@types/unist", "npm:3.0.2"],\ + ["unist-util-is", "npm:6.0.0"]\ ],\ "linkType": "HARD"\ }]\ @@ -29652,24 +29783,16 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["unist-util-position", [\ - ["npm:4.0.4", {\ - "packageLocation": "./.yarn/cache/unist-util-position-npm-4.0.4-833bfce46c-e7487b6cec.zip/node_modules/unist-util-position/",\ + ["npm:5.0.0", {\ + "packageLocation": "./.yarn/cache/unist-util-position-npm-5.0.0-38f216b0a0-f89b27989b.zip/node_modules/unist-util-position/",\ "packageDependencies": [\ - ["unist-util-position", "npm:4.0.4"],\ - ["@types/unist", "npm:2.0.8"]\ + ["unist-util-position", "npm:5.0.0"],\ + ["@types/unist", "npm:3.0.2"]\ ],\ "linkType": "HARD"\ }]\ ]],\ ["unist-util-position-from-estree", [\ - ["npm:1.1.2", {\ - "packageLocation": "./.yarn/cache/unist-util-position-from-estree-npm-1.1.2-2c54b9b445-e3f4060e2a.zip/node_modules/unist-util-position-from-estree/",\ - "packageDependencies": [\ - ["unist-util-position-from-estree", "npm:1.1.2"],\ - ["@types/unist", "npm:2.0.8"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:2.0.0", {\ "packageLocation": "./.yarn/cache/unist-util-position-from-estree-npm-2.0.0-adf063eee5-d3b3048a57.zip/node_modules/unist-util-position-from-estree/",\ "packageDependencies": [\ @@ -29679,16 +29802,19 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ - ["unist-util-remove-position", [\ - ["npm:4.0.2", {\ - "packageLocation": "./.yarn/cache/unist-util-remove-position-npm-4.0.2-5806d5548a-989831da91.zip/node_modules/unist-util-remove-position/",\ + ["unist-util-remove", [\ + ["npm:3.1.1", {\ + "packageLocation": "./.yarn/cache/unist-util-remove-npm-3.1.1-4eb96f179f-ed7c762941.zip/node_modules/unist-util-remove/",\ "packageDependencies": [\ - ["unist-util-remove-position", "npm:4.0.2"],\ + ["unist-util-remove", "npm:3.1.1"],\ ["@types/unist", "npm:2.0.8"],\ - ["unist-util-visit", "npm:4.1.2"]\ + ["unist-util-is", "npm:5.2.1"],\ + ["unist-util-visit-parents", "npm:5.1.3"]\ ],\ "linkType": "HARD"\ - }],\ + }]\ + ]],\ + ["unist-util-remove-position", [\ ["npm:5.0.0", {\ "packageLocation": "./.yarn/cache/unist-util-remove-position-npm-5.0.0-1f2a181e0a-8aabdb9d0e.zip/node_modules/unist-util-remove-position/",\ "packageDependencies": [\ @@ -29700,14 +29826,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["unist-util-stringify-position", [\ - ["npm:3.0.3", {\ - "packageLocation": "./.yarn/cache/unist-util-stringify-position-npm-3.0.3-3ab0818239-dbd66c1518.zip/node_modules/unist-util-stringify-position/",\ - "packageDependencies": [\ - ["unist-util-stringify-position", "npm:3.0.3"],\ - ["@types/unist", "npm:2.0.8"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:4.0.0", {\ "packageLocation": "./.yarn/cache/unist-util-stringify-position-npm-4.0.0-2362acd217-e2e7aee4b9.zip/node_modules/unist-util-stringify-position/",\ "packageDependencies": [\ @@ -29718,16 +29836,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["unist-util-visit", [\ - ["npm:4.1.2", {\ - "packageLocation": "./.yarn/cache/unist-util-visit-npm-4.1.2-6b950e655a-95a34e3f7b.zip/node_modules/unist-util-visit/",\ - "packageDependencies": [\ - ["unist-util-visit", "npm:4.1.2"],\ - ["@types/unist", "npm:2.0.8"],\ - ["unist-util-is", "npm:5.2.1"],\ - ["unist-util-visit-parents", "npm:5.1.3"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:5.0.0", {\ "packageLocation": "./.yarn/cache/unist-util-visit-npm-5.0.0-df56c75117-9ec42e618e.zip/node_modules/unist-util-visit/",\ "packageDependencies": [\ @@ -29815,6 +29923,28 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ],\ "linkType": "SOFT"\ }],\ + ["npm:1.1.0", {\ + "packageLocation": "./.yarn/cache/update-browserslist-db-npm-1.1.0-3d2cb7d955-7b74694d96.zip/node_modules/update-browserslist-db/",\ + "packageDependencies": [\ + ["update-browserslist-db", "npm:1.1.0"]\ + ],\ + "linkType": "SOFT"\ + }],\ + ["virtual:4e727c7b5b033f8d5ac7299f9860cb61f5802656f7b4fea2accd32d68dc1a767387a6d23f0724065d3c65e61cb31b9eec2438ae937ce36e7602b4586ede55af6#npm:1.1.0", {\ + "packageLocation": "./.yarn/__virtual__/update-browserslist-db-virtual-e5d722ea57/0/cache/update-browserslist-db-npm-1.1.0-3d2cb7d955-7b74694d96.zip/node_modules/update-browserslist-db/",\ + "packageDependencies": [\ + ["update-browserslist-db", "virtual:4e727c7b5b033f8d5ac7299f9860cb61f5802656f7b4fea2accd32d68dc1a767387a6d23f0724065d3c65e61cb31b9eec2438ae937ce36e7602b4586ede55af6#npm:1.1.0"],\ + ["@types/browserslist", null],\ + ["browserslist", "npm:4.23.3"],\ + ["escalade", "npm:3.1.2"],\ + ["picocolors", "npm:1.0.1"]\ + ],\ + "packagePeers": [\ + "@types/browserslist",\ + "browserslist"\ + ],\ + "linkType": "HARD"\ + }],\ ["virtual:8126a959d46e6def6d7f2497c47970a23a94adac85f5be1df9be290c674503b9bcbe0dc057e2741ee222cc2a8a3b9f584b20c3a9eb5ce085704d99fc5d94514c#npm:1.0.13", {\ "packageLocation": "./.yarn/__virtual__/update-browserslist-db-virtual-c44986bb1a/0/cache/update-browserslist-db-npm-1.0.13-ea7b8ee24d-1e47d80182.zip/node_modules/update-browserslist-db/",\ "packageDependencies": [\ @@ -29995,19 +30125,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ - ["uvu", [\ - ["npm:0.5.6", {\ - "packageLocation": "./.yarn/cache/uvu-npm-0.5.6-c8507ad49b-09460a3797.zip/node_modules/uvu/",\ - "packageDependencies": [\ - ["uvu", "npm:0.5.6"],\ - ["dequal", "npm:2.0.3"],\ - ["diff", "npm:5.1.0"],\ - ["kleur", "npm:4.1.5"],\ - ["sade", "npm:1.8.1"]\ - ],\ - "linkType": "HARD"\ - }]\ - ]],\ ["v8-compile-cache", [\ ["npm:2.3.0", {\ "packageLocation": "./.yarn/cache/v8-compile-cache-npm-2.3.0-961375f150-adb0a271ea.zip/node_modules/v8-compile-cache/",\ @@ -30088,40 +30205,40 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["vfile", [\ - ["npm:5.3.7", {\ - "packageLocation": "./.yarn/cache/vfile-npm-5.3.7-3fe49f8a33-642cce703a.zip/node_modules/vfile/",\ + ["npm:6.0.2", {\ + "packageLocation": "./.yarn/cache/vfile-npm-6.0.2-19da19c73a-2f3f405654.zip/node_modules/vfile/",\ "packageDependencies": [\ - ["vfile", "npm:5.3.7"],\ - ["@types/unist", "npm:2.0.8"],\ - ["is-buffer", "npm:2.0.5"],\ - ["unist-util-stringify-position", "npm:3.0.3"],\ - ["vfile-message", "npm:3.1.4"]\ + ["vfile", "npm:6.0.2"],\ + ["@types/unist", "npm:3.0.2"],\ + ["unist-util-stringify-position", "npm:4.0.0"],\ + ["vfile-message", "npm:4.0.2"]\ ],\ "linkType": "HARD"\ }]\ ]],\ - ["vfile-matter", [\ - ["npm:3.0.1", {\ - "packageLocation": "./.yarn/cache/vfile-matter-npm-3.0.1-9d8d8bf427-ced55ed7d7.zip/node_modules/vfile-matter/",\ + ["vfile-location", [\ + ["npm:5.0.3", {\ + "packageLocation": "./.yarn/cache/vfile-location-npm-5.0.3-f510ce60de-bfb3821b69.zip/node_modules/vfile-location/",\ "packageDependencies": [\ - ["vfile-matter", "npm:3.0.1"],\ - ["@types/js-yaml", "npm:4.0.5"],\ - ["is-buffer", "npm:2.0.5"],\ - ["js-yaml", "npm:4.1.0"]\ + ["vfile-location", "npm:5.0.3"],\ + ["@types/unist", "npm:3.0.2"],\ + ["vfile", "npm:6.0.2"]\ ],\ "linkType": "HARD"\ }]\ ]],\ - ["vfile-message", [\ - ["npm:3.1.4", {\ - "packageLocation": "./.yarn/cache/vfile-message-npm-3.1.4-47b355eba8-d0ee7da197.zip/node_modules/vfile-message/",\ + ["vfile-matter", [\ + ["npm:5.0.0", {\ + "packageLocation": "./.yarn/cache/vfile-matter-npm-5.0.0-8f034005c1-713327640e.zip/node_modules/vfile-matter/",\ "packageDependencies": [\ - ["vfile-message", "npm:3.1.4"],\ - ["@types/unist", "npm:2.0.8"],\ - ["unist-util-stringify-position", "npm:3.0.3"]\ + ["vfile-matter", "npm:5.0.0"],\ + ["vfile", "npm:6.0.2"],\ + ["yaml", "npm:2.3.3"]\ ],\ "linkType": "HARD"\ - }],\ + }]\ + ]],\ + ["vfile-message", [\ ["npm:4.0.2", {\ "packageLocation": "./.yarn/cache/vfile-message-npm-4.0.2-6a07dfdc39-964e7e119f.zip/node_modules/vfile-message/",\ "packageDependencies": [\ @@ -30360,6 +30477,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD"\ }]\ ]],\ + ["web-namespaces", [\ + ["npm:2.0.1", {\ + "packageLocation": "./.yarn/cache/web-namespaces-npm-2.0.1-f7b8233848-b6d9f02f1a.zip/node_modules/web-namespaces/",\ + "packageDependencies": [\ + ["web-namespaces", "npm:2.0.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["webidl-conversions", [\ ["npm:3.0.1", {\ "packageLocation": "./.yarn/cache/webidl-conversions-npm-3.0.1-60310f6a2b-c92a0a6ab9.zip/node_modules/webidl-conversions/",\ @@ -30428,6 +30554,16 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ],\ "linkType": "HARD"\ }],\ + ["npm:6.5.0", {\ + "packageLocation": "./.yarn/cache/whatwg-url-npm-6.5.0-07c2c28a54-a10bd5e29f.zip/node_modules/whatwg-url/",\ + "packageDependencies": [\ + ["whatwg-url", "npm:6.5.0"],\ + ["lodash.sortby", "npm:4.7.0"],\ + ["tr46", "npm:1.0.1"],\ + ["webidl-conversions", "npm:4.0.2"]\ + ],\ + "linkType": "HARD"\ + }],\ ["npm:7.1.0", {\ "packageLocation": "./.yarn/cache/whatwg-url-npm-7.1.0-d6cae01571-fecb07c872.zip/node_modules/whatwg-url/",\ "packageDependencies": [\ diff --git a/.yarn/cache/@babel-code-frame-npm-7.24.7-315a600a58-830e62cd38.zip b/.yarn/cache/@babel-code-frame-npm-7.24.7-315a600a58-830e62cd38.zip new file mode 100644 index 00000000000..053969ec0be Binary files /dev/null and b/.yarn/cache/@babel-code-frame-npm-7.24.7-315a600a58-830e62cd38.zip differ diff --git a/.yarn/cache/@babel-compat-data-npm-7.25.2-119057710e-b61bc9da7c.zip b/.yarn/cache/@babel-compat-data-npm-7.25.2-119057710e-b61bc9da7c.zip new file mode 100644 index 00000000000..4bef9a733a4 Binary files /dev/null and b/.yarn/cache/@babel-compat-data-npm-7.25.2-119057710e-b61bc9da7c.zip differ diff --git a/.yarn/cache/@babel-core-npm-7.25.2-341930f809-9a1ef604a7.zip b/.yarn/cache/@babel-core-npm-7.25.2-341930f809-9a1ef604a7.zip new file mode 100644 index 00000000000..57980cf23f7 Binary files /dev/null and b/.yarn/cache/@babel-core-npm-7.25.2-341930f809-9a1ef604a7.zip differ diff --git a/.yarn/cache/@babel-generator-npm-7.25.0-4bba208756-bf25649dde.zip b/.yarn/cache/@babel-generator-npm-7.25.0-4bba208756-bf25649dde.zip new file mode 100644 index 00000000000..d1a7e948ced Binary files /dev/null and b/.yarn/cache/@babel-generator-npm-7.25.0-4bba208756-bf25649dde.zip differ diff --git a/.yarn/cache/@babel-helper-compilation-targets-npm-7.25.2-27e0232144-aed33c5496.zip b/.yarn/cache/@babel-helper-compilation-targets-npm-7.25.2-27e0232144-aed33c5496.zip new file mode 100644 index 00000000000..5f511fa2626 Binary files /dev/null and b/.yarn/cache/@babel-helper-compilation-targets-npm-7.25.2-27e0232144-aed33c5496.zip differ diff --git a/.yarn/cache/@babel-helper-module-imports-npm-7.24.7-f60e66adbf-8ac15d96d2.zip b/.yarn/cache/@babel-helper-module-imports-npm-7.24.7-f60e66adbf-8ac15d96d2.zip new file mode 100644 index 00000000000..95e2043a8a0 Binary files /dev/null and b/.yarn/cache/@babel-helper-module-imports-npm-7.24.7-f60e66adbf-8ac15d96d2.zip differ diff --git a/.yarn/cache/@babel-helper-module-transforms-npm-7.25.2-2c8d511580-282d4e3308.zip b/.yarn/cache/@babel-helper-module-transforms-npm-7.25.2-2c8d511580-282d4e3308.zip new file mode 100644 index 00000000000..90bcbffe8c6 Binary files /dev/null and b/.yarn/cache/@babel-helper-module-transforms-npm-7.25.2-2c8d511580-282d4e3308.zip differ diff --git a/.yarn/cache/@babel-helper-simple-access-npm-7.24.7-beddd00b0e-ddbf55f9de.zip b/.yarn/cache/@babel-helper-simple-access-npm-7.24.7-beddd00b0e-ddbf55f9de.zip new file mode 100644 index 00000000000..38ea173f820 Binary files /dev/null and b/.yarn/cache/@babel-helper-simple-access-npm-7.24.7-beddd00b0e-ddbf55f9de.zip differ diff --git a/.yarn/cache/@babel-helper-string-parser-npm-7.24.8-133b2e71e1-39b03c5119.zip b/.yarn/cache/@babel-helper-string-parser-npm-7.24.8-133b2e71e1-39b03c5119.zip new file mode 100644 index 00000000000..c36f592ef3f Binary files /dev/null and b/.yarn/cache/@babel-helper-string-parser-npm-7.24.8-133b2e71e1-39b03c5119.zip differ diff --git a/.yarn/cache/@babel-helper-validator-identifier-npm-7.24.7-748889c8d2-6799ab117c.zip b/.yarn/cache/@babel-helper-validator-identifier-npm-7.24.7-748889c8d2-6799ab117c.zip new file mode 100644 index 00000000000..d63dc7cf484 Binary files /dev/null and b/.yarn/cache/@babel-helper-validator-identifier-npm-7.24.7-748889c8d2-6799ab117c.zip differ diff --git a/.yarn/cache/@babel-helper-validator-option-npm-7.24.8-e093ef5016-a52442dfa7.zip b/.yarn/cache/@babel-helper-validator-option-npm-7.24.8-e093ef5016-a52442dfa7.zip new file mode 100644 index 00000000000..bbaa9491b6a Binary files /dev/null and b/.yarn/cache/@babel-helper-validator-option-npm-7.24.8-e093ef5016-a52442dfa7.zip differ diff --git a/.yarn/cache/@babel-helpers-npm-7.25.0-f552d9aaf3-739e3704ff.zip b/.yarn/cache/@babel-helpers-npm-7.25.0-f552d9aaf3-739e3704ff.zip new file mode 100644 index 00000000000..52135bdd463 Binary files /dev/null and b/.yarn/cache/@babel-helpers-npm-7.25.0-f552d9aaf3-739e3704ff.zip differ diff --git a/.yarn/cache/@babel-highlight-npm-7.24.7-d792bd8d9f-5cd3a89f14.zip b/.yarn/cache/@babel-highlight-npm-7.24.7-d792bd8d9f-5cd3a89f14.zip new file mode 100644 index 00000000000..13606408fc0 Binary files /dev/null and b/.yarn/cache/@babel-highlight-npm-7.24.7-d792bd8d9f-5cd3a89f14.zip differ diff --git a/.yarn/cache/@babel-parser-npm-7.25.3-e33bb4a0e6-b55aba6421.zip b/.yarn/cache/@babel-parser-npm-7.25.3-e33bb4a0e6-b55aba6421.zip new file mode 100644 index 00000000000..5ec509e09a5 Binary files /dev/null and b/.yarn/cache/@babel-parser-npm-7.25.3-e33bb4a0e6-b55aba6421.zip differ diff --git a/.yarn/cache/@babel-runtime-npm-7.25.0-a7bca33687-4a2a374a58.zip b/.yarn/cache/@babel-runtime-npm-7.25.0-a7bca33687-4a2a374a58.zip new file mode 100644 index 00000000000..58adb5ac06f Binary files /dev/null and b/.yarn/cache/@babel-runtime-npm-7.25.0-a7bca33687-4a2a374a58.zip differ diff --git a/.yarn/cache/@babel-template-npm-7.25.0-2c6ddcb43a-3f2db56871.zip b/.yarn/cache/@babel-template-npm-7.25.0-2c6ddcb43a-3f2db56871.zip new file mode 100644 index 00000000000..5f9c6abd3f4 Binary files /dev/null and b/.yarn/cache/@babel-template-npm-7.25.0-2c6ddcb43a-3f2db56871.zip differ diff --git a/.yarn/cache/@babel-types-npm-7.25.2-7d3fc0ed1e-f73f66ba90.zip b/.yarn/cache/@babel-types-npm-7.25.2-7d3fc0ed1e-f73f66ba90.zip new file mode 100644 index 00000000000..2ea5f08d9b7 Binary files /dev/null and b/.yarn/cache/@babel-types-npm-7.25.2-7d3fc0ed1e-f73f66ba90.zip differ diff --git a/.yarn/cache/@fern-api-fdr-sdk-npm-0.98.16-3955e989a-9e631fdef0-d3a2269365.zip b/.yarn/cache/@fern-api-fdr-sdk-npm-0.98.18-aaf13f7f5-28d1ceb312-2cab59acf6.zip similarity index 79% rename from .yarn/cache/@fern-api-fdr-sdk-npm-0.98.16-3955e989a-9e631fdef0-d3a2269365.zip rename to .yarn/cache/@fern-api-fdr-sdk-npm-0.98.18-aaf13f7f5-28d1ceb312-2cab59acf6.zip index b3a592973c5..c5953a55f34 100644 Binary files a/.yarn/cache/@fern-api-fdr-sdk-npm-0.98.16-3955e989a-9e631fdef0-d3a2269365.zip and b/.yarn/cache/@fern-api-fdr-sdk-npm-0.98.18-aaf13f7f5-28d1ceb312-2cab59acf6.zip differ diff --git a/.yarn/cache/@fern-fern-ir-sdk-npm-53.1.0-2c91c594fe-813f60b02d.zip b/.yarn/cache/@fern-fern-ir-sdk-npm-53.4.0-36a127e1df-1201f9f39c.zip similarity index 75% rename from .yarn/cache/@fern-fern-ir-sdk-npm-53.1.0-2c91c594fe-813f60b02d.zip rename to .yarn/cache/@fern-fern-ir-sdk-npm-53.4.0-36a127e1df-1201f9f39c.zip index ed43164eb4f..758bda4da9b 100644 Binary files a/.yarn/cache/@fern-fern-ir-sdk-npm-53.1.0-2c91c594fe-813f60b02d.zip and b/.yarn/cache/@fern-fern-ir-sdk-npm-53.4.0-36a127e1df-1201f9f39c.zip differ diff --git a/.yarn/cache/@jridgewell-gen-mapping-npm-0.3.5-d8b85ebeaf-ff7a1764eb.zip b/.yarn/cache/@jridgewell-gen-mapping-npm-0.3.5-d8b85ebeaf-ff7a1764eb.zip new file mode 100644 index 00000000000..ab69f33cdb4 Binary files /dev/null and b/.yarn/cache/@jridgewell-gen-mapping-npm-0.3.5-d8b85ebeaf-ff7a1764eb.zip differ diff --git a/.yarn/cache/@jridgewell-set-array-npm-1.2.1-2312928209-832e513a85.zip b/.yarn/cache/@jridgewell-set-array-npm-1.2.1-2312928209-832e513a85.zip new file mode 100644 index 00000000000..8a72fc72dfb Binary files /dev/null and b/.yarn/cache/@jridgewell-set-array-npm-1.2.1-2312928209-832e513a85.zip differ diff --git a/.yarn/cache/@jridgewell-trace-mapping-npm-0.3.25-c076fd2279-9d3c40d225.zip b/.yarn/cache/@jridgewell-trace-mapping-npm-0.3.25-c076fd2279-9d3c40d225.zip new file mode 100644 index 00000000000..fc42ef59cd6 Binary files /dev/null and b/.yarn/cache/@jridgewell-trace-mapping-npm-0.3.25-c076fd2279-9d3c40d225.zip differ diff --git a/.yarn/cache/@mdx-js-mdx-npm-2.3.0-043b30d13e-d918766a32.zip b/.yarn/cache/@mdx-js-mdx-npm-2.3.0-043b30d13e-d918766a32.zip deleted file mode 100644 index d3bbbe041bc..00000000000 Binary files a/.yarn/cache/@mdx-js-mdx-npm-2.3.0-043b30d13e-d918766a32.zip and /dev/null differ diff --git a/.yarn/cache/@mdx-js-mdx-npm-3.0.1-560c3c34e1-8222166227.zip b/.yarn/cache/@mdx-js-mdx-npm-3.0.1-560c3c34e1-8222166227.zip new file mode 100644 index 00000000000..2bce4e0cf78 Binary files /dev/null and b/.yarn/cache/@mdx-js-mdx-npm-3.0.1-560c3c34e1-8222166227.zip differ diff --git a/.yarn/cache/@mdx-js-react-npm-2.3.0-d5582a450b-f45fe77955.zip b/.yarn/cache/@mdx-js-react-npm-2.3.0-d5582a450b-f45fe77955.zip deleted file mode 100644 index 0d5c5616a45..00000000000 Binary files a/.yarn/cache/@mdx-js-react-npm-2.3.0-d5582a450b-f45fe77955.zip and /dev/null differ diff --git a/.yarn/cache/@mdx-js-react-npm-3.0.1-1ce14f6273-1063a59726.zip b/.yarn/cache/@mdx-js-react-npm-3.0.1-1ce14f6273-1063a59726.zip new file mode 100644 index 00000000000..fdb38570b8b Binary files /dev/null and b/.yarn/cache/@mdx-js-react-npm-3.0.1-1ce14f6273-1063a59726.zip differ diff --git a/.yarn/cache/@types-hast-npm-2.3.5-1a6e9442f9-e435e9fbf6.zip b/.yarn/cache/@types-hast-npm-2.3.5-1a6e9442f9-e435e9fbf6.zip deleted file mode 100644 index c4376d246ec..00000000000 Binary files a/.yarn/cache/@types-hast-npm-2.3.5-1a6e9442f9-e435e9fbf6.zip and /dev/null differ diff --git a/.yarn/cache/@types-js-yaml-npm-4.0.5-bb64d71397-7dcac8c50f.zip b/.yarn/cache/@types-js-yaml-npm-4.0.5-bb64d71397-7dcac8c50f.zip deleted file mode 100644 index 5b8f4dfc843..00000000000 Binary files a/.yarn/cache/@types-js-yaml-npm-4.0.5-bb64d71397-7dcac8c50f.zip and /dev/null differ diff --git a/.yarn/cache/@types-katex-npm-0.16.7-c19be7ec5f-4fd15d9355.zip b/.yarn/cache/@types-katex-npm-0.16.7-c19be7ec5f-4fd15d9355.zip new file mode 100644 index 00000000000..6a0311103f6 Binary files /dev/null and b/.yarn/cache/@types-katex-npm-0.16.7-c19be7ec5f-4fd15d9355.zip differ diff --git a/.yarn/cache/@types-mdast-npm-3.0.12-e8f7ab24f4-83adb8679b.zip b/.yarn/cache/@types-mdast-npm-3.0.12-e8f7ab24f4-83adb8679b.zip deleted file mode 100644 index 06bdeb018e1..00000000000 Binary files a/.yarn/cache/@types-mdast-npm-3.0.12-e8f7ab24f4-83adb8679b.zip and /dev/null differ diff --git a/.yarn/cache/@types-react-npm-18.2.21-c50bc2f785-ffed203bfe.zip b/.yarn/cache/@types-react-npm-18.2.21-c50bc2f785-ffed203bfe.zip deleted file mode 100644 index 826fdb31e1c..00000000000 Binary files a/.yarn/cache/@types-react-npm-18.2.21-c50bc2f785-ffed203bfe.zip and /dev/null differ diff --git a/.yarn/cache/browserslist-npm-4.23.3-4e727c7b5b-7906064f99.zip b/.yarn/cache/browserslist-npm-4.23.3-4e727c7b5b-7906064f99.zip new file mode 100644 index 00000000000..7d73b7a9b6a Binary files /dev/null and b/.yarn/cache/browserslist-npm-4.23.3-4e727c7b5b-7906064f99.zip differ diff --git a/.yarn/cache/caniuse-lite-npm-1.0.30001650-c867884d42-4892c25200.zip b/.yarn/cache/caniuse-lite-npm-1.0.30001650-c867884d42-4892c25200.zip new file mode 100644 index 00000000000..8b435f2aee3 Binary files /dev/null and b/.yarn/cache/caniuse-lite-npm-1.0.30001650-c867884d42-4892c25200.zip differ diff --git a/.yarn/cache/collapse-white-space-npm-2.1.0-89651f51f3-c8978b1f4e.zip b/.yarn/cache/collapse-white-space-npm-2.1.0-89651f51f3-c8978b1f4e.zip new file mode 100644 index 00000000000..af6752a7104 Binary files /dev/null and b/.yarn/cache/collapse-white-space-npm-2.1.0-89651f51f3-c8978b1f4e.zip differ diff --git a/.yarn/cache/core-js-npm-3.38.0-3f7d7cec9a-71ef0598da.zip b/.yarn/cache/core-js-npm-3.38.0-3f7d7cec9a-71ef0598da.zip new file mode 100644 index 00000000000..d7d5ee7bb13 Binary files /dev/null and b/.yarn/cache/core-js-npm-3.38.0-3f7d7cec9a-71ef0598da.zip differ diff --git a/.yarn/cache/diff-npm-5.1.0-d24d222280-c7bf0df7c9.zip b/.yarn/cache/diff-npm-5.1.0-d24d222280-c7bf0df7c9.zip deleted file mode 100644 index 14dfd16bb7a..00000000000 Binary files a/.yarn/cache/diff-npm-5.1.0-d24d222280-c7bf0df7c9.zip and /dev/null differ diff --git a/.yarn/cache/electron-to-chromium-npm-1.5.5-ce59e1f3c6-fcdd2797ec.zip b/.yarn/cache/electron-to-chromium-npm-1.5.5-ce59e1f3c6-fcdd2797ec.zip new file mode 100644 index 00000000000..d0d96457581 Binary files /dev/null and b/.yarn/cache/electron-to-chromium-npm-1.5.5-ce59e1f3c6-fcdd2797ec.zip differ diff --git a/.yarn/cache/escalade-npm-3.1.2-5826d31cf8-1ec0977aa2.zip b/.yarn/cache/escalade-npm-3.1.2-5826d31cf8-1ec0977aa2.zip new file mode 100644 index 00000000000..3a466d18907 Binary files /dev/null and b/.yarn/cache/escalade-npm-3.1.2-5826d31cf8-1ec0977aa2.zip differ diff --git a/.yarn/cache/estree-util-attach-comments-npm-2.1.1-872c177a8a-c5c2c41c9a.zip b/.yarn/cache/estree-util-attach-comments-npm-2.1.1-872c177a8a-c5c2c41c9a.zip deleted file mode 100644 index bf7396cb380..00000000000 Binary files a/.yarn/cache/estree-util-attach-comments-npm-2.1.1-872c177a8a-c5c2c41c9a.zip and /dev/null differ diff --git a/.yarn/cache/estree-util-attach-comments-npm-3.0.0-9a9d33e548-56254eaef3.zip b/.yarn/cache/estree-util-attach-comments-npm-3.0.0-9a9d33e548-56254eaef3.zip new file mode 100644 index 00000000000..93609a8feac Binary files /dev/null and b/.yarn/cache/estree-util-attach-comments-npm-3.0.0-9a9d33e548-56254eaef3.zip differ diff --git a/.yarn/cache/estree-util-build-jsx-npm-2.2.2-c3f1420348-d008ac36a4.zip b/.yarn/cache/estree-util-build-jsx-npm-2.2.2-c3f1420348-d008ac36a4.zip deleted file mode 100644 index a2b3244c0bb..00000000000 Binary files a/.yarn/cache/estree-util-build-jsx-npm-2.2.2-c3f1420348-d008ac36a4.zip and /dev/null differ diff --git a/.yarn/cache/estree-util-build-jsx-npm-3.0.1-e6ce7e25ac-185eff060e.zip b/.yarn/cache/estree-util-build-jsx-npm-3.0.1-e6ce7e25ac-185eff060e.zip new file mode 100644 index 00000000000..cc7b633933d Binary files /dev/null and b/.yarn/cache/estree-util-build-jsx-npm-3.0.1-e6ce7e25ac-185eff060e.zip differ diff --git a/.yarn/cache/estree-util-is-identifier-name-npm-2.1.0-2b8df71baf-cab317a071.zip b/.yarn/cache/estree-util-is-identifier-name-npm-2.1.0-2b8df71baf-cab317a071.zip deleted file mode 100644 index ce84d1455ca..00000000000 Binary files a/.yarn/cache/estree-util-is-identifier-name-npm-2.1.0-2b8df71baf-cab317a071.zip and /dev/null differ diff --git a/.yarn/cache/estree-util-to-js-npm-1.2.0-85057be9d5-93a75e1051.zip b/.yarn/cache/estree-util-to-js-npm-1.2.0-85057be9d5-93a75e1051.zip deleted file mode 100644 index 3acab1adeda..00000000000 Binary files a/.yarn/cache/estree-util-to-js-npm-1.2.0-85057be9d5-93a75e1051.zip and /dev/null differ diff --git a/.yarn/cache/estree-util-to-js-npm-2.0.0-64970efd5d-833edc94ab.zip b/.yarn/cache/estree-util-to-js-npm-2.0.0-64970efd5d-833edc94ab.zip new file mode 100644 index 00000000000..59cbe095369 Binary files /dev/null and b/.yarn/cache/estree-util-to-js-npm-2.0.0-64970efd5d-833edc94ab.zip differ diff --git a/.yarn/cache/estree-util-visit-npm-1.2.1-58d95f90a0-6feea4fdc4.zip b/.yarn/cache/estree-util-visit-npm-1.2.1-58d95f90a0-6feea4fdc4.zip deleted file mode 100644 index b72954545ce..00000000000 Binary files a/.yarn/cache/estree-util-visit-npm-1.2.1-58d95f90a0-6feea4fdc4.zip and /dev/null differ diff --git a/.yarn/cache/fetch-mock-jest-npm-1.5.1-43a73431f1-371b1c4a1f.zip b/.yarn/cache/fetch-mock-jest-npm-1.5.1-43a73431f1-371b1c4a1f.zip new file mode 100644 index 00000000000..7f37fc04ec9 Binary files /dev/null and b/.yarn/cache/fetch-mock-jest-npm-1.5.1-43a73431f1-371b1c4a1f.zip differ diff --git a/.yarn/cache/fetch-mock-npm-9.11.0-6ebe138f97-debc4dd83b.zip b/.yarn/cache/fetch-mock-npm-9.11.0-6ebe138f97-debc4dd83b.zip new file mode 100644 index 00000000000..9e6fc38750e Binary files /dev/null and b/.yarn/cache/fetch-mock-npm-9.11.0-6ebe138f97-debc4dd83b.zip differ diff --git a/.yarn/cache/glob-to-regexp-npm-0.4.1-cd697e0fc7-e795f4e8f0.zip b/.yarn/cache/glob-to-regexp-npm-0.4.1-cd697e0fc7-e795f4e8f0.zip new file mode 100644 index 00000000000..2276b3f4a5f Binary files /dev/null and b/.yarn/cache/glob-to-regexp-npm-0.4.1-cd697e0fc7-e795f4e8f0.zip differ diff --git a/.yarn/cache/hast-util-from-dom-npm-5.0.0-0973c39ef8-bf8f96c480.zip b/.yarn/cache/hast-util-from-dom-npm-5.0.0-0973c39ef8-bf8f96c480.zip new file mode 100644 index 00000000000..41efa798635 Binary files /dev/null and b/.yarn/cache/hast-util-from-dom-npm-5.0.0-0973c39ef8-bf8f96c480.zip differ diff --git a/.yarn/cache/hast-util-from-html-isomorphic-npm-2.0.0-572fde4fb0-a98d02890b.zip b/.yarn/cache/hast-util-from-html-isomorphic-npm-2.0.0-572fde4fb0-a98d02890b.zip new file mode 100644 index 00000000000..135ae66e3fc Binary files /dev/null and b/.yarn/cache/hast-util-from-html-isomorphic-npm-2.0.0-572fde4fb0-a98d02890b.zip differ diff --git a/.yarn/cache/hast-util-from-html-npm-2.0.1-4d2564d3e6-8decdec1f2.zip b/.yarn/cache/hast-util-from-html-npm-2.0.1-4d2564d3e6-8decdec1f2.zip new file mode 100644 index 00000000000..6a65a880680 Binary files /dev/null and b/.yarn/cache/hast-util-from-html-npm-2.0.1-4d2564d3e6-8decdec1f2.zip differ diff --git a/.yarn/cache/hast-util-from-parse5-npm-8.0.1-5ed6a912d8-fdd1ab8b03.zip b/.yarn/cache/hast-util-from-parse5-npm-8.0.1-5ed6a912d8-fdd1ab8b03.zip new file mode 100644 index 00000000000..b3f94367af3 Binary files /dev/null and b/.yarn/cache/hast-util-from-parse5-npm-8.0.1-5ed6a912d8-fdd1ab8b03.zip differ diff --git a/.yarn/cache/hast-util-is-element-npm-3.0.0-59c73c7f56-82569a420e.zip b/.yarn/cache/hast-util-is-element-npm-3.0.0-59c73c7f56-82569a420e.zip new file mode 100644 index 00000000000..7273c459ca5 Binary files /dev/null and b/.yarn/cache/hast-util-is-element-npm-3.0.0-59c73c7f56-82569a420e.zip differ diff --git a/.yarn/cache/hast-util-parse-selector-npm-4.0.0-adea10ab8c-76087670d3.zip b/.yarn/cache/hast-util-parse-selector-npm-4.0.0-adea10ab8c-76087670d3.zip new file mode 100644 index 00000000000..9cddecdc413 Binary files /dev/null and b/.yarn/cache/hast-util-parse-selector-npm-4.0.0-adea10ab8c-76087670d3.zip differ diff --git a/.yarn/cache/hast-util-to-estree-npm-2.3.3-a87d9b491a-a09de0214d.zip b/.yarn/cache/hast-util-to-estree-npm-2.3.3-a87d9b491a-a09de0214d.zip deleted file mode 100644 index d45e6527e73..00000000000 Binary files a/.yarn/cache/hast-util-to-estree-npm-2.3.3-a87d9b491a-a09de0214d.zip and /dev/null differ diff --git a/.yarn/cache/hast-util-to-estree-npm-3.1.0-0bbaae89ac-61272f7c18.zip b/.yarn/cache/hast-util-to-estree-npm-3.1.0-0bbaae89ac-61272f7c18.zip new file mode 100644 index 00000000000..f558f45b252 Binary files /dev/null and b/.yarn/cache/hast-util-to-estree-npm-3.1.0-0bbaae89ac-61272f7c18.zip differ diff --git a/.yarn/cache/hast-util-to-jsx-runtime-npm-2.3.0-c0e033a67f-599a97c6ec.zip b/.yarn/cache/hast-util-to-jsx-runtime-npm-2.3.0-c0e033a67f-599a97c6ec.zip new file mode 100644 index 00000000000..5c87b8340cd Binary files /dev/null and b/.yarn/cache/hast-util-to-jsx-runtime-npm-2.3.0-c0e033a67f-599a97c6ec.zip differ diff --git a/.yarn/cache/hast-util-to-text-npm-4.0.2-64a96edaeb-72cce08666.zip b/.yarn/cache/hast-util-to-text-npm-4.0.2-64a96edaeb-72cce08666.zip new file mode 100644 index 00000000000..d67d5cf1a52 Binary files /dev/null and b/.yarn/cache/hast-util-to-text-npm-4.0.2-64a96edaeb-72cce08666.zip differ diff --git a/.yarn/cache/hast-util-whitespace-npm-2.0.1-0cb2b36fdf-431be6b2f3.zip b/.yarn/cache/hast-util-whitespace-npm-2.0.1-0cb2b36fdf-431be6b2f3.zip deleted file mode 100644 index 8fa5268928b..00000000000 Binary files a/.yarn/cache/hast-util-whitespace-npm-2.0.1-0cb2b36fdf-431be6b2f3.zip and /dev/null differ diff --git a/.yarn/cache/hast-util-whitespace-npm-3.0.0-215dd4954b-41d93ccce2.zip b/.yarn/cache/hast-util-whitespace-npm-3.0.0-215dd4954b-41d93ccce2.zip new file mode 100644 index 00000000000..431512844e5 Binary files /dev/null and b/.yarn/cache/hast-util-whitespace-npm-3.0.0-215dd4954b-41d93ccce2.zip differ diff --git a/.yarn/cache/hastscript-npm-8.0.0-acde2e34a0-ae3c20223e.zip b/.yarn/cache/hastscript-npm-8.0.0-acde2e34a0-ae3c20223e.zip new file mode 100644 index 00000000000..0e24a47b7ee Binary files /dev/null and b/.yarn/cache/hastscript-npm-8.0.0-acde2e34a0-ae3c20223e.zip differ diff --git a/.yarn/cache/inline-style-parser-npm-0.2.3-b8a7023d7a-ed6454de80.zip b/.yarn/cache/inline-style-parser-npm-0.2.3-b8a7023d7a-ed6454de80.zip new file mode 100644 index 00000000000..cb8e46e734b Binary files /dev/null and b/.yarn/cache/inline-style-parser-npm-0.2.3-b8a7023d7a-ed6454de80.zip differ diff --git a/.yarn/cache/is-buffer-npm-2.0.5-17e563f277-764c9ad8b5.zip b/.yarn/cache/is-buffer-npm-2.0.5-17e563f277-764c9ad8b5.zip deleted file mode 100644 index 313ef275f7d..00000000000 Binary files a/.yarn/cache/is-buffer-npm-2.0.5-17e563f277-764c9ad8b5.zip and /dev/null differ diff --git a/.yarn/cache/is-subset-npm-0.1.1-15dc569569-97b8d7852a.zip b/.yarn/cache/is-subset-npm-0.1.1-15dc569569-97b8d7852a.zip new file mode 100644 index 00000000000..80a9a8ac025 Binary files /dev/null and b/.yarn/cache/is-subset-npm-0.1.1-15dc569569-97b8d7852a.zip differ diff --git a/.yarn/cache/katex-npm-0.16.11-7c04032a99-49d9340705.zip b/.yarn/cache/katex-npm-0.16.11-7c04032a99-49d9340705.zip new file mode 100644 index 00000000000..cfc8133b4c4 Binary files /dev/null and b/.yarn/cache/katex-npm-0.16.11-7c04032a99-49d9340705.zip differ diff --git a/.yarn/cache/kleur-npm-4.1.5-46b6135f41-1dc476e327.zip b/.yarn/cache/kleur-npm-4.1.5-46b6135f41-1dc476e327.zip deleted file mode 100644 index f05e8e30f80..00000000000 Binary files a/.yarn/cache/kleur-npm-4.1.5-46b6135f41-1dc476e327.zip and /dev/null differ diff --git a/.yarn/cache/markdown-extensions-npm-1.1.1-633329e3d0-8a6dd128be.zip b/.yarn/cache/markdown-extensions-npm-1.1.1-633329e3d0-8a6dd128be.zip deleted file mode 100644 index 4072b5fdcac..00000000000 Binary files a/.yarn/cache/markdown-extensions-npm-1.1.1-633329e3d0-8a6dd128be.zip and /dev/null differ diff --git a/.yarn/cache/markdown-extensions-npm-2.0.0-ab861fd299-ec4ffcb076.zip b/.yarn/cache/markdown-extensions-npm-2.0.0-ab861fd299-ec4ffcb076.zip new file mode 100644 index 00000000000..2a6bf14036c Binary files /dev/null and b/.yarn/cache/markdown-extensions-npm-2.0.0-ab861fd299-ec4ffcb076.zip differ diff --git a/.yarn/cache/mdast-util-definitions-npm-5.1.2-45a5b0f1bf-2544daccab.zip b/.yarn/cache/mdast-util-definitions-npm-5.1.2-45a5b0f1bf-2544daccab.zip deleted file mode 100644 index 847b86e3e00..00000000000 Binary files a/.yarn/cache/mdast-util-definitions-npm-5.1.2-45a5b0f1bf-2544daccab.zip and /dev/null differ diff --git a/.yarn/cache/mdast-util-find-and-replace-npm-2.2.2-7e2061aea9-b4ce463c43.zip b/.yarn/cache/mdast-util-find-and-replace-npm-2.2.2-7e2061aea9-b4ce463c43.zip deleted file mode 100644 index 37f8fc094c5..00000000000 Binary files a/.yarn/cache/mdast-util-find-and-replace-npm-2.2.2-7e2061aea9-b4ce463c43.zip and /dev/null differ diff --git a/.yarn/cache/mdast-util-find-and-replace-npm-3.0.1-284ae6ddf8-05d5c4ff02.zip b/.yarn/cache/mdast-util-find-and-replace-npm-3.0.1-284ae6ddf8-05d5c4ff02.zip new file mode 100644 index 00000000000..533fabfb2cc Binary files /dev/null and b/.yarn/cache/mdast-util-find-and-replace-npm-3.0.1-284ae6ddf8-05d5c4ff02.zip differ diff --git a/.yarn/cache/mdast-util-from-markdown-npm-1.3.1-dd1eea116a-c2fac22516.zip b/.yarn/cache/mdast-util-from-markdown-npm-1.3.1-dd1eea116a-c2fac22516.zip deleted file mode 100644 index 1161c2b26ed..00000000000 Binary files a/.yarn/cache/mdast-util-from-markdown-npm-1.3.1-dd1eea116a-c2fac22516.zip and /dev/null differ diff --git a/.yarn/cache/mdast-util-gfm-autolink-literal-npm-1.0.3-30e29b9908-1748a8727c.zip b/.yarn/cache/mdast-util-gfm-autolink-literal-npm-1.0.3-30e29b9908-1748a8727c.zip deleted file mode 100644 index e06f556f4c1..00000000000 Binary files a/.yarn/cache/mdast-util-gfm-autolink-literal-npm-1.0.3-30e29b9908-1748a8727c.zip and /dev/null differ diff --git a/.yarn/cache/mdast-util-gfm-autolink-literal-npm-2.0.0-620ccef115-10322662e5.zip b/.yarn/cache/mdast-util-gfm-autolink-literal-npm-2.0.0-620ccef115-10322662e5.zip new file mode 100644 index 00000000000..ec0e280772d Binary files /dev/null and b/.yarn/cache/mdast-util-gfm-autolink-literal-npm-2.0.0-620ccef115-10322662e5.zip differ diff --git a/.yarn/cache/mdast-util-gfm-footnote-npm-1.0.2-4584cc7d97-2d77505f93.zip b/.yarn/cache/mdast-util-gfm-footnote-npm-1.0.2-4584cc7d97-2d77505f93.zip deleted file mode 100644 index 9f1764e739e..00000000000 Binary files a/.yarn/cache/mdast-util-gfm-footnote-npm-1.0.2-4584cc7d97-2d77505f93.zip and /dev/null differ diff --git a/.yarn/cache/mdast-util-gfm-footnote-npm-2.0.0-4a167ca606-45d26b40e7.zip b/.yarn/cache/mdast-util-gfm-footnote-npm-2.0.0-4a167ca606-45d26b40e7.zip new file mode 100644 index 00000000000..e297d3e5d13 Binary files /dev/null and b/.yarn/cache/mdast-util-gfm-footnote-npm-2.0.0-4a167ca606-45d26b40e7.zip differ diff --git a/.yarn/cache/mdast-util-gfm-npm-2.0.2-34fe06a303-7078cb9852.zip b/.yarn/cache/mdast-util-gfm-npm-2.0.2-34fe06a303-7078cb9852.zip deleted file mode 100644 index 77094f9f341..00000000000 Binary files a/.yarn/cache/mdast-util-gfm-npm-2.0.2-34fe06a303-7078cb9852.zip and /dev/null differ diff --git a/.yarn/cache/mdast-util-gfm-npm-3.0.0-c4b06d0013-62039d2f68.zip b/.yarn/cache/mdast-util-gfm-npm-3.0.0-c4b06d0013-62039d2f68.zip new file mode 100644 index 00000000000..c1457137425 Binary files /dev/null and b/.yarn/cache/mdast-util-gfm-npm-3.0.0-c4b06d0013-62039d2f68.zip differ diff --git a/.yarn/cache/mdast-util-gfm-strikethrough-npm-1.0.3-213cf55fea-17003340ff.zip b/.yarn/cache/mdast-util-gfm-strikethrough-npm-1.0.3-213cf55fea-17003340ff.zip deleted file mode 100644 index 34c5d11cae7..00000000000 Binary files a/.yarn/cache/mdast-util-gfm-strikethrough-npm-1.0.3-213cf55fea-17003340ff.zip and /dev/null differ diff --git a/.yarn/cache/mdast-util-gfm-strikethrough-npm-2.0.0-d16d95c318-fe9b1d0eba.zip b/.yarn/cache/mdast-util-gfm-strikethrough-npm-2.0.0-d16d95c318-fe9b1d0eba.zip new file mode 100644 index 00000000000..4278f9e7555 Binary files /dev/null and b/.yarn/cache/mdast-util-gfm-strikethrough-npm-2.0.0-d16d95c318-fe9b1d0eba.zip differ diff --git a/.yarn/cache/mdast-util-gfm-table-npm-1.0.7-70536e7d2d-8b8c401bb4.zip b/.yarn/cache/mdast-util-gfm-table-npm-1.0.7-70536e7d2d-8b8c401bb4.zip deleted file mode 100644 index 803480a3e92..00000000000 Binary files a/.yarn/cache/mdast-util-gfm-table-npm-1.0.7-70536e7d2d-8b8c401bb4.zip and /dev/null differ diff --git a/.yarn/cache/mdast-util-gfm-table-npm-2.0.0-45a74f064b-063a627fd0.zip b/.yarn/cache/mdast-util-gfm-table-npm-2.0.0-45a74f064b-063a627fd0.zip new file mode 100644 index 00000000000..554176484a8 Binary files /dev/null and b/.yarn/cache/mdast-util-gfm-table-npm-2.0.0-45a74f064b-063a627fd0.zip differ diff --git a/.yarn/cache/mdast-util-gfm-task-list-item-npm-1.0.2-9de4576007-c9b86037d6.zip b/.yarn/cache/mdast-util-gfm-task-list-item-npm-1.0.2-9de4576007-c9b86037d6.zip deleted file mode 100644 index 000d2093b78..00000000000 Binary files a/.yarn/cache/mdast-util-gfm-task-list-item-npm-1.0.2-9de4576007-c9b86037d6.zip and /dev/null differ diff --git a/.yarn/cache/mdast-util-gfm-task-list-item-npm-2.0.0-cb1270a10f-37db90c59b.zip b/.yarn/cache/mdast-util-gfm-task-list-item-npm-2.0.0-cb1270a10f-37db90c59b.zip new file mode 100644 index 00000000000..0a3eaff8f5f Binary files /dev/null and b/.yarn/cache/mdast-util-gfm-task-list-item-npm-2.0.0-cb1270a10f-37db90c59b.zip differ diff --git a/.yarn/cache/mdast-util-math-npm-3.0.0-8b1aa5f265-dc7dfb14ae.zip b/.yarn/cache/mdast-util-math-npm-3.0.0-8b1aa5f265-dc7dfb14ae.zip new file mode 100644 index 00000000000..8ab00c57aef Binary files /dev/null and b/.yarn/cache/mdast-util-math-npm-3.0.0-8b1aa5f265-dc7dfb14ae.zip differ diff --git a/.yarn/cache/mdast-util-mdx-expression-npm-1.3.2-0cd3362efc-e4c90f26de.zip b/.yarn/cache/mdast-util-mdx-expression-npm-1.3.2-0cd3362efc-e4c90f26de.zip deleted file mode 100644 index 69130170204..00000000000 Binary files a/.yarn/cache/mdast-util-mdx-expression-npm-1.3.2-0cd3362efc-e4c90f26de.zip and /dev/null differ diff --git a/.yarn/cache/mdast-util-mdx-jsx-npm-2.1.4-7b04372865-add3ff2dd1.zip b/.yarn/cache/mdast-util-mdx-jsx-npm-2.1.4-7b04372865-add3ff2dd1.zip deleted file mode 100644 index 4eb20dda4cf..00000000000 Binary files a/.yarn/cache/mdast-util-mdx-jsx-npm-2.1.4-7b04372865-add3ff2dd1.zip and /dev/null differ diff --git a/.yarn/cache/mdast-util-mdx-npm-2.0.1-fa9e345324-7303149230.zip b/.yarn/cache/mdast-util-mdx-npm-2.0.1-fa9e345324-7303149230.zip deleted file mode 100644 index 5eff4900d9f..00000000000 Binary files a/.yarn/cache/mdast-util-mdx-npm-2.0.1-fa9e345324-7303149230.zip and /dev/null differ diff --git a/.yarn/cache/mdast-util-mdxjs-esm-npm-1.3.1-ae04f9d0af-ee78a4f58a.zip b/.yarn/cache/mdast-util-mdxjs-esm-npm-1.3.1-ae04f9d0af-ee78a4f58a.zip deleted file mode 100644 index 64f58dd7230..00000000000 Binary files a/.yarn/cache/mdast-util-mdxjs-esm-npm-1.3.1-ae04f9d0af-ee78a4f58a.zip and /dev/null differ diff --git a/.yarn/cache/mdast-util-phrasing-npm-3.0.1-1da1e5bff8-c5b616d9b1.zip b/.yarn/cache/mdast-util-phrasing-npm-3.0.1-1da1e5bff8-c5b616d9b1.zip deleted file mode 100644 index bb8a25883e6..00000000000 Binary files a/.yarn/cache/mdast-util-phrasing-npm-3.0.1-1da1e5bff8-c5b616d9b1.zip and /dev/null differ diff --git a/.yarn/cache/mdast-util-to-hast-npm-12.3.0-4814ec4c82-ea40c9f07d.zip b/.yarn/cache/mdast-util-to-hast-npm-12.3.0-4814ec4c82-ea40c9f07d.zip deleted file mode 100644 index f128faa4e9f..00000000000 Binary files a/.yarn/cache/mdast-util-to-hast-npm-12.3.0-4814ec4c82-ea40c9f07d.zip and /dev/null differ diff --git a/.yarn/cache/mdast-util-to-hast-npm-13.2.0-538a77f867-7e5231ff3d.zip b/.yarn/cache/mdast-util-to-hast-npm-13.2.0-538a77f867-7e5231ff3d.zip new file mode 100644 index 00000000000..1d0795adb36 Binary files /dev/null and b/.yarn/cache/mdast-util-to-hast-npm-13.2.0-538a77f867-7e5231ff3d.zip differ diff --git a/.yarn/cache/mdast-util-to-markdown-npm-1.5.0-43c48b6c48-64338eb33e.zip b/.yarn/cache/mdast-util-to-markdown-npm-1.5.0-43c48b6c48-64338eb33e.zip deleted file mode 100644 index a82507e4c6a..00000000000 Binary files a/.yarn/cache/mdast-util-to-markdown-npm-1.5.0-43c48b6c48-64338eb33e.zip and /dev/null differ diff --git a/.yarn/cache/mdast-util-to-string-npm-3.2.0-4f9fa356be-dc40b544d5.zip b/.yarn/cache/mdast-util-to-string-npm-3.2.0-4f9fa356be-dc40b544d5.zip deleted file mode 100644 index d8390423c89..00000000000 Binary files a/.yarn/cache/mdast-util-to-string-npm-3.2.0-4f9fa356be-dc40b544d5.zip and /dev/null differ diff --git a/.yarn/cache/micromark-core-commonmark-npm-1.1.0-6f0dca58f3-c6dfedc958.zip b/.yarn/cache/micromark-core-commonmark-npm-1.1.0-6f0dca58f3-c6dfedc958.zip deleted file mode 100644 index f15431dacc6..00000000000 Binary files a/.yarn/cache/micromark-core-commonmark-npm-1.1.0-6f0dca58f3-c6dfedc958.zip and /dev/null differ diff --git a/.yarn/cache/micromark-extension-gfm-autolink-literal-npm-1.0.5-1ada4a6641-ec2f6bc4a3.zip b/.yarn/cache/micromark-extension-gfm-autolink-literal-npm-1.0.5-1ada4a6641-ec2f6bc4a3.zip deleted file mode 100644 index 4a06e85c640..00000000000 Binary files a/.yarn/cache/micromark-extension-gfm-autolink-literal-npm-1.0.5-1ada4a6641-ec2f6bc4a3.zip and /dev/null differ diff --git a/.yarn/cache/micromark-extension-gfm-autolink-literal-npm-2.1.0-8fcb271412-e00a570c70.zip b/.yarn/cache/micromark-extension-gfm-autolink-literal-npm-2.1.0-8fcb271412-e00a570c70.zip new file mode 100644 index 00000000000..08364772673 Binary files /dev/null and b/.yarn/cache/micromark-extension-gfm-autolink-literal-npm-2.1.0-8fcb271412-e00a570c70.zip differ diff --git a/.yarn/cache/micromark-extension-gfm-footnote-npm-1.1.2-caa5472e3f-c151a629ee.zip b/.yarn/cache/micromark-extension-gfm-footnote-npm-1.1.2-caa5472e3f-c151a629ee.zip deleted file mode 100644 index 4ea734761f9..00000000000 Binary files a/.yarn/cache/micromark-extension-gfm-footnote-npm-1.1.2-caa5472e3f-c151a629ee.zip and /dev/null differ diff --git a/.yarn/cache/micromark-extension-gfm-footnote-npm-2.1.0-1cf783dd36-ac6fb039e9.zip b/.yarn/cache/micromark-extension-gfm-footnote-npm-2.1.0-1cf783dd36-ac6fb039e9.zip new file mode 100644 index 00000000000..01afeafb63c Binary files /dev/null and b/.yarn/cache/micromark-extension-gfm-footnote-npm-2.1.0-1cf783dd36-ac6fb039e9.zip differ diff --git a/.yarn/cache/micromark-extension-gfm-npm-2.0.3-134337a21c-c4a917c16d.zip b/.yarn/cache/micromark-extension-gfm-npm-2.0.3-134337a21c-c4a917c16d.zip deleted file mode 100644 index 87217b1bc4e..00000000000 Binary files a/.yarn/cache/micromark-extension-gfm-npm-2.0.3-134337a21c-c4a917c16d.zip and /dev/null differ diff --git a/.yarn/cache/micromark-extension-gfm-npm-3.0.0-d154ab531f-2060fa6266.zip b/.yarn/cache/micromark-extension-gfm-npm-3.0.0-d154ab531f-2060fa6266.zip new file mode 100644 index 00000000000..8c04327d106 Binary files /dev/null and b/.yarn/cache/micromark-extension-gfm-npm-3.0.0-d154ab531f-2060fa6266.zip differ diff --git a/.yarn/cache/micromark-extension-gfm-strikethrough-npm-1.0.7-f5e7b0b63e-169e310a44.zip b/.yarn/cache/micromark-extension-gfm-strikethrough-npm-1.0.7-f5e7b0b63e-169e310a44.zip deleted file mode 100644 index 151a81c5c80..00000000000 Binary files a/.yarn/cache/micromark-extension-gfm-strikethrough-npm-1.0.7-f5e7b0b63e-169e310a44.zip and /dev/null differ diff --git a/.yarn/cache/micromark-extension-gfm-strikethrough-npm-2.1.0-b2aa188eba-cdb7a38dd6.zip b/.yarn/cache/micromark-extension-gfm-strikethrough-npm-2.1.0-b2aa188eba-cdb7a38dd6.zip new file mode 100644 index 00000000000..2c0767d61b2 Binary files /dev/null and b/.yarn/cache/micromark-extension-gfm-strikethrough-npm-2.1.0-b2aa188eba-cdb7a38dd6.zip differ diff --git a/.yarn/cache/micromark-extension-gfm-table-npm-1.0.7-878b7528e3-4853731285.zip b/.yarn/cache/micromark-extension-gfm-table-npm-1.0.7-878b7528e3-4853731285.zip deleted file mode 100644 index ab4bbb5d9dd..00000000000 Binary files a/.yarn/cache/micromark-extension-gfm-table-npm-1.0.7-878b7528e3-4853731285.zip and /dev/null differ diff --git a/.yarn/cache/micromark-extension-gfm-table-npm-2.1.0-cd50a7004f-249d695f5f.zip b/.yarn/cache/micromark-extension-gfm-table-npm-2.1.0-cd50a7004f-249d695f5f.zip new file mode 100644 index 00000000000..90c0d0f6cbc Binary files /dev/null and b/.yarn/cache/micromark-extension-gfm-table-npm-2.1.0-cd50a7004f-249d695f5f.zip differ diff --git a/.yarn/cache/micromark-extension-gfm-tagfilter-npm-1.0.2-87d5ea927a-7d2441df51.zip b/.yarn/cache/micromark-extension-gfm-tagfilter-npm-1.0.2-87d5ea927a-7d2441df51.zip deleted file mode 100644 index df51d4867db..00000000000 Binary files a/.yarn/cache/micromark-extension-gfm-tagfilter-npm-1.0.2-87d5ea927a-7d2441df51.zip and /dev/null differ diff --git a/.yarn/cache/micromark-extension-gfm-tagfilter-npm-2.0.0-c5ad486636-cf21552f4a.zip b/.yarn/cache/micromark-extension-gfm-tagfilter-npm-2.0.0-c5ad486636-cf21552f4a.zip new file mode 100644 index 00000000000..118c9b47d42 Binary files /dev/null and b/.yarn/cache/micromark-extension-gfm-tagfilter-npm-2.0.0-c5ad486636-cf21552f4a.zip differ diff --git a/.yarn/cache/micromark-extension-gfm-task-list-item-npm-1.0.5-0fb4eed065-929f05343d.zip b/.yarn/cache/micromark-extension-gfm-task-list-item-npm-1.0.5-0fb4eed065-929f05343d.zip deleted file mode 100644 index 53762b75875..00000000000 Binary files a/.yarn/cache/micromark-extension-gfm-task-list-item-npm-1.0.5-0fb4eed065-929f05343d.zip and /dev/null differ diff --git a/.yarn/cache/micromark-extension-gfm-task-list-item-npm-2.1.0-b717607894-b1ad86a4e9.zip b/.yarn/cache/micromark-extension-gfm-task-list-item-npm-2.1.0-b717607894-b1ad86a4e9.zip new file mode 100644 index 00000000000..2a9334214cd Binary files /dev/null and b/.yarn/cache/micromark-extension-gfm-task-list-item-npm-2.1.0-b717607894-b1ad86a4e9.zip differ diff --git a/.yarn/cache/micromark-extension-math-npm-3.1.0-60c186f61c-60a9813d45.zip b/.yarn/cache/micromark-extension-math-npm-3.1.0-60c186f61c-60a9813d45.zip new file mode 100644 index 00000000000..2c0516993c3 Binary files /dev/null and b/.yarn/cache/micromark-extension-math-npm-3.1.0-60c186f61c-60a9813d45.zip differ diff --git a/.yarn/cache/micromark-extension-mdx-expression-npm-1.0.8-95d2ed6eb2-49750d10c1.zip b/.yarn/cache/micromark-extension-mdx-expression-npm-1.0.8-95d2ed6eb2-49750d10c1.zip deleted file mode 100644 index 27e9eb1d737..00000000000 Binary files a/.yarn/cache/micromark-extension-mdx-expression-npm-1.0.8-95d2ed6eb2-49750d10c1.zip and /dev/null differ diff --git a/.yarn/cache/micromark-extension-mdx-jsx-npm-1.0.5-ef5b951682-0ddb7b71c2.zip b/.yarn/cache/micromark-extension-mdx-jsx-npm-1.0.5-ef5b951682-0ddb7b71c2.zip deleted file mode 100644 index 9d810925a9e..00000000000 Binary files a/.yarn/cache/micromark-extension-mdx-jsx-npm-1.0.5-ef5b951682-0ddb7b71c2.zip and /dev/null differ diff --git a/.yarn/cache/micromark-extension-mdx-md-npm-1.0.1-c524399fe3-fdeaf8f4f9.zip b/.yarn/cache/micromark-extension-mdx-md-npm-1.0.1-c524399fe3-fdeaf8f4f9.zip deleted file mode 100644 index 28ccd0c14cd..00000000000 Binary files a/.yarn/cache/micromark-extension-mdx-md-npm-1.0.1-c524399fe3-fdeaf8f4f9.zip and /dev/null differ diff --git a/.yarn/cache/micromark-extension-mdxjs-esm-npm-1.0.5-cb307e2a05-7006cfa963.zip b/.yarn/cache/micromark-extension-mdxjs-esm-npm-1.0.5-cb307e2a05-7006cfa963.zip deleted file mode 100644 index ed67e7ad4df..00000000000 Binary files a/.yarn/cache/micromark-extension-mdxjs-esm-npm-1.0.5-cb307e2a05-7006cfa963.zip and /dev/null differ diff --git a/.yarn/cache/micromark-extension-mdxjs-esm-npm-3.0.0-f09fb4b82d-fb33d85020.zip b/.yarn/cache/micromark-extension-mdxjs-esm-npm-3.0.0-f09fb4b82d-fb33d85020.zip new file mode 100644 index 00000000000..42052f93ca2 Binary files /dev/null and b/.yarn/cache/micromark-extension-mdxjs-esm-npm-3.0.0-f09fb4b82d-fb33d85020.zip differ diff --git a/.yarn/cache/micromark-extension-mdxjs-npm-1.0.1-ceca8ad557-1e6bf3df76.zip b/.yarn/cache/micromark-extension-mdxjs-npm-1.0.1-ceca8ad557-1e6bf3df76.zip deleted file mode 100644 index 0a4546ebd68..00000000000 Binary files a/.yarn/cache/micromark-extension-mdxjs-npm-1.0.1-ceca8ad557-1e6bf3df76.zip and /dev/null differ diff --git a/.yarn/cache/micromark-extension-mdxjs-npm-3.0.0-c1ee8da220-7da6f0fb0e.zip b/.yarn/cache/micromark-extension-mdxjs-npm-3.0.0-c1ee8da220-7da6f0fb0e.zip new file mode 100644 index 00000000000..0d67ebcbc26 Binary files /dev/null and b/.yarn/cache/micromark-extension-mdxjs-npm-3.0.0-c1ee8da220-7da6f0fb0e.zip differ diff --git a/.yarn/cache/micromark-factory-destination-npm-1.1.0-b520b52727-9e2b5fb5fe.zip b/.yarn/cache/micromark-factory-destination-npm-1.1.0-b520b52727-9e2b5fb5fe.zip deleted file mode 100644 index 1cf4a810619..00000000000 Binary files a/.yarn/cache/micromark-factory-destination-npm-1.1.0-b520b52727-9e2b5fb5fe.zip and /dev/null differ diff --git a/.yarn/cache/micromark-factory-label-npm-1.1.0-d8a5a37124-fcda48f128.zip b/.yarn/cache/micromark-factory-label-npm-1.1.0-d8a5a37124-fcda48f128.zip deleted file mode 100644 index f6afd6e4d41..00000000000 Binary files a/.yarn/cache/micromark-factory-label-npm-1.1.0-d8a5a37124-fcda48f128.zip and /dev/null differ diff --git a/.yarn/cache/micromark-factory-mdx-expression-npm-1.0.9-5e83bb23b6-7359bf3290.zip b/.yarn/cache/micromark-factory-mdx-expression-npm-1.0.9-5e83bb23b6-7359bf3290.zip deleted file mode 100644 index a4057421913..00000000000 Binary files a/.yarn/cache/micromark-factory-mdx-expression-npm-1.0.9-5e83bb23b6-7359bf3290.zip and /dev/null differ diff --git a/.yarn/cache/micromark-factory-space-npm-1.1.0-30229d1b5d-b58435076b.zip b/.yarn/cache/micromark-factory-space-npm-1.1.0-30229d1b5d-b58435076b.zip deleted file mode 100644 index e3ac573f6ed..00000000000 Binary files a/.yarn/cache/micromark-factory-space-npm-1.1.0-30229d1b5d-b58435076b.zip and /dev/null differ diff --git a/.yarn/cache/micromark-factory-title-npm-1.1.0-4af82ae5b2-4432d3dbc8.zip b/.yarn/cache/micromark-factory-title-npm-1.1.0-4af82ae5b2-4432d3dbc8.zip deleted file mode 100644 index a56c3cf5e0c..00000000000 Binary files a/.yarn/cache/micromark-factory-title-npm-1.1.0-4af82ae5b2-4432d3dbc8.zip and /dev/null differ diff --git a/.yarn/cache/micromark-factory-whitespace-npm-1.1.0-8564d6a9a1-ef0fa682c7.zip b/.yarn/cache/micromark-factory-whitespace-npm-1.1.0-8564d6a9a1-ef0fa682c7.zip deleted file mode 100644 index d14d10633d7..00000000000 Binary files a/.yarn/cache/micromark-factory-whitespace-npm-1.1.0-8564d6a9a1-ef0fa682c7.zip and /dev/null differ diff --git a/.yarn/cache/micromark-npm-3.2.0-5351b5395d-56c15851ad.zip b/.yarn/cache/micromark-npm-3.2.0-5351b5395d-56c15851ad.zip deleted file mode 100644 index a4d29ce71f1..00000000000 Binary files a/.yarn/cache/micromark-npm-3.2.0-5351b5395d-56c15851ad.zip and /dev/null differ diff --git a/.yarn/cache/micromark-util-character-npm-1.2.0-b42e3441af-089e79162a.zip b/.yarn/cache/micromark-util-character-npm-1.2.0-b42e3441af-089e79162a.zip deleted file mode 100644 index ba56e48e479..00000000000 Binary files a/.yarn/cache/micromark-util-character-npm-1.2.0-b42e3441af-089e79162a.zip and /dev/null differ diff --git a/.yarn/cache/micromark-util-chunked-npm-1.1.0-2b46b7c8a2-c435bde911.zip b/.yarn/cache/micromark-util-chunked-npm-1.1.0-2b46b7c8a2-c435bde911.zip deleted file mode 100644 index 0a25383b7db..00000000000 Binary files a/.yarn/cache/micromark-util-chunked-npm-1.1.0-2b46b7c8a2-c435bde911.zip and /dev/null differ diff --git a/.yarn/cache/micromark-util-classify-character-npm-1.1.0-77b33fd18e-8499cb0bb1.zip b/.yarn/cache/micromark-util-classify-character-npm-1.1.0-77b33fd18e-8499cb0bb1.zip deleted file mode 100644 index 13abbdf7bd5..00000000000 Binary files a/.yarn/cache/micromark-util-classify-character-npm-1.1.0-77b33fd18e-8499cb0bb1.zip and /dev/null differ diff --git a/.yarn/cache/micromark-util-combine-extensions-npm-1.1.0-d7734a9ec8-ee78464f5d.zip b/.yarn/cache/micromark-util-combine-extensions-npm-1.1.0-d7734a9ec8-ee78464f5d.zip deleted file mode 100644 index a7352d3f2d3..00000000000 Binary files a/.yarn/cache/micromark-util-combine-extensions-npm-1.1.0-d7734a9ec8-ee78464f5d.zip and /dev/null differ diff --git a/.yarn/cache/micromark-util-decode-numeric-character-reference-npm-1.1.0-0381c1cb74-4733fe7514.zip b/.yarn/cache/micromark-util-decode-numeric-character-reference-npm-1.1.0-0381c1cb74-4733fe7514.zip deleted file mode 100644 index 01e168aa90c..00000000000 Binary files a/.yarn/cache/micromark-util-decode-numeric-character-reference-npm-1.1.0-0381c1cb74-4733fe7514.zip and /dev/null differ diff --git a/.yarn/cache/micromark-util-decode-string-npm-1.1.0-d3fef9c9ba-f1625155db.zip b/.yarn/cache/micromark-util-decode-string-npm-1.1.0-d3fef9c9ba-f1625155db.zip deleted file mode 100644 index f70d2bdaea8..00000000000 Binary files a/.yarn/cache/micromark-util-decode-string-npm-1.1.0-d3fef9c9ba-f1625155db.zip and /dev/null differ diff --git a/.yarn/cache/micromark-util-encode-npm-1.1.0-65f415c4fe-4ef29d02b1.zip b/.yarn/cache/micromark-util-encode-npm-1.1.0-65f415c4fe-4ef29d02b1.zip deleted file mode 100644 index a40e21507d6..00000000000 Binary files a/.yarn/cache/micromark-util-encode-npm-1.1.0-65f415c4fe-4ef29d02b1.zip and /dev/null differ diff --git a/.yarn/cache/micromark-util-events-to-acorn-npm-1.2.3-e5c8bad960-aba0dadb86.zip b/.yarn/cache/micromark-util-events-to-acorn-npm-1.2.3-e5c8bad960-aba0dadb86.zip deleted file mode 100644 index 4fa42b59c84..00000000000 Binary files a/.yarn/cache/micromark-util-events-to-acorn-npm-1.2.3-e5c8bad960-aba0dadb86.zip and /dev/null differ diff --git a/.yarn/cache/micromark-util-html-tag-name-npm-1.2.0-d8309ab06f-ccf0fa99b5.zip b/.yarn/cache/micromark-util-html-tag-name-npm-1.2.0-d8309ab06f-ccf0fa99b5.zip deleted file mode 100644 index d4735421c96..00000000000 Binary files a/.yarn/cache/micromark-util-html-tag-name-npm-1.2.0-d8309ab06f-ccf0fa99b5.zip and /dev/null differ diff --git a/.yarn/cache/micromark-util-normalize-identifier-npm-1.1.0-378d909800-8655bea41f.zip b/.yarn/cache/micromark-util-normalize-identifier-npm-1.1.0-378d909800-8655bea41f.zip deleted file mode 100644 index 7a04a9bb73f..00000000000 Binary files a/.yarn/cache/micromark-util-normalize-identifier-npm-1.1.0-378d909800-8655bea41f.zip and /dev/null differ diff --git a/.yarn/cache/micromark-util-resolve-all-npm-1.1.0-c49b6d7c36-1ce6c0237c.zip b/.yarn/cache/micromark-util-resolve-all-npm-1.1.0-c49b6d7c36-1ce6c0237c.zip deleted file mode 100644 index 8b5bae88815..00000000000 Binary files a/.yarn/cache/micromark-util-resolve-all-npm-1.1.0-c49b6d7c36-1ce6c0237c.zip and /dev/null differ diff --git a/.yarn/cache/micromark-util-sanitize-uri-npm-1.2.0-b14e5e159a-6663f365c4.zip b/.yarn/cache/micromark-util-sanitize-uri-npm-1.2.0-b14e5e159a-6663f365c4.zip deleted file mode 100644 index 7bf44a29dfd..00000000000 Binary files a/.yarn/cache/micromark-util-sanitize-uri-npm-1.2.0-b14e5e159a-6663f365c4.zip and /dev/null differ diff --git a/.yarn/cache/micromark-util-subtokenize-npm-1.1.0-72005ae28b-4a9d780c4d.zip b/.yarn/cache/micromark-util-subtokenize-npm-1.1.0-72005ae28b-4a9d780c4d.zip deleted file mode 100644 index d401183cd12..00000000000 Binary files a/.yarn/cache/micromark-util-subtokenize-npm-1.1.0-72005ae28b-4a9d780c4d.zip and /dev/null differ diff --git a/.yarn/cache/micromark-util-symbol-npm-1.1.0-90b0865932-02414a753b.zip b/.yarn/cache/micromark-util-symbol-npm-1.1.0-90b0865932-02414a753b.zip deleted file mode 100644 index 31d60e1192e..00000000000 Binary files a/.yarn/cache/micromark-util-symbol-npm-1.1.0-90b0865932-02414a753b.zip and /dev/null differ diff --git a/.yarn/cache/micromark-util-types-npm-1.1.0-9df6df907c-b0ef2b4b95.zip b/.yarn/cache/micromark-util-types-npm-1.1.0-9df6df907c-b0ef2b4b95.zip deleted file mode 100644 index 0edc649bcd9..00000000000 Binary files a/.yarn/cache/micromark-util-types-npm-1.1.0-9df6df907c-b0ef2b4b95.zip and /dev/null differ diff --git a/.yarn/cache/mri-npm-1.2.0-8ecee0357d-83f515abbc.zip b/.yarn/cache/mri-npm-1.2.0-8ecee0357d-83f515abbc.zip deleted file mode 100644 index 89ae2ac72e8..00000000000 Binary files a/.yarn/cache/mri-npm-1.2.0-8ecee0357d-83f515abbc.zip and /dev/null differ diff --git a/.yarn/cache/next-mdx-remote-npm-4.4.1-2a17ed923a-95cd77d03a.zip b/.yarn/cache/next-mdx-remote-npm-4.4.1-2a17ed923a-95cd77d03a.zip deleted file mode 100644 index a7f49ae5198..00000000000 Binary files a/.yarn/cache/next-mdx-remote-npm-4.4.1-2a17ed923a-95cd77d03a.zip and /dev/null differ diff --git a/.yarn/cache/next-mdx-remote-npm-5.0.0-7247bc8e12-7e8dd72794.zip b/.yarn/cache/next-mdx-remote-npm-5.0.0-7247bc8e12-7e8dd72794.zip new file mode 100644 index 00000000000..4805e3fcffb Binary files /dev/null and b/.yarn/cache/next-mdx-remote-npm-5.0.0-7247bc8e12-7e8dd72794.zip differ diff --git a/.yarn/cache/node-releases-npm-2.0.18-51abc46668-ef55a3d853.zip b/.yarn/cache/node-releases-npm-2.0.18-51abc46668-ef55a3d853.zip new file mode 100644 index 00000000000..b125493bf15 Binary files /dev/null and b/.yarn/cache/node-releases-npm-2.0.18-51abc46668-ef55a3d853.zip differ diff --git a/.yarn/cache/path-to-regexp-npm-2.4.0-ce02fd84d9-581175bf29.zip b/.yarn/cache/path-to-regexp-npm-2.4.0-ce02fd84d9-581175bf29.zip new file mode 100644 index 00000000000..c4c34424f54 Binary files /dev/null and b/.yarn/cache/path-to-regexp-npm-2.4.0-ce02fd84d9-581175bf29.zip differ diff --git a/.yarn/cache/picocolors-npm-1.0.1-39442f3da8-fa68166d1f.zip b/.yarn/cache/picocolors-npm-1.0.1-39442f3da8-fa68166d1f.zip new file mode 100644 index 00000000000..21041b39e9b Binary files /dev/null and b/.yarn/cache/picocolors-npm-1.0.1-39442f3da8-fa68166d1f.zip differ diff --git a/.yarn/cache/querystring-npm-0.2.1-15cb60859d-7b83b45d64.zip b/.yarn/cache/querystring-npm-0.2.1-15cb60859d-7b83b45d64.zip new file mode 100644 index 00000000000..50f259ec7d0 Binary files /dev/null and b/.yarn/cache/querystring-npm-0.2.1-15cb60859d-7b83b45d64.zip differ diff --git a/.yarn/cache/regenerator-runtime-npm-0.14.1-a6c97c609a-9f57c93277.zip b/.yarn/cache/regenerator-runtime-npm-0.14.1-a6c97c609a-9f57c93277.zip new file mode 100644 index 00000000000..3d8cc689b1c Binary files /dev/null and b/.yarn/cache/regenerator-runtime-npm-0.14.1-a6c97c609a-9f57c93277.zip differ diff --git a/.yarn/cache/rehype-katex-npm-7.0.0-704b6f2147-3184cf7635.zip b/.yarn/cache/rehype-katex-npm-7.0.0-704b6f2147-3184cf7635.zip new file mode 100644 index 00000000000..f2fc6923b95 Binary files /dev/null and b/.yarn/cache/rehype-katex-npm-7.0.0-704b6f2147-3184cf7635.zip differ diff --git a/.yarn/cache/remark-gfm-npm-3.0.1-4a9f6f751e-02254f74d6.zip b/.yarn/cache/remark-gfm-npm-3.0.1-4a9f6f751e-02254f74d6.zip deleted file mode 100644 index 406448ab0c4..00000000000 Binary files a/.yarn/cache/remark-gfm-npm-3.0.1-4a9f6f751e-02254f74d6.zip and /dev/null differ diff --git a/.yarn/cache/remark-gfm-npm-4.0.0-8bb699e315-84bea84e38.zip b/.yarn/cache/remark-gfm-npm-4.0.0-8bb699e315-84bea84e38.zip new file mode 100644 index 00000000000..b141c5957cc Binary files /dev/null and b/.yarn/cache/remark-gfm-npm-4.0.0-8bb699e315-84bea84e38.zip differ diff --git a/.yarn/cache/remark-math-npm-6.0.0-747000722b-fef489acb6.zip b/.yarn/cache/remark-math-npm-6.0.0-747000722b-fef489acb6.zip new file mode 100644 index 00000000000..72cca629fb6 Binary files /dev/null and b/.yarn/cache/remark-math-npm-6.0.0-747000722b-fef489acb6.zip differ diff --git a/.yarn/cache/remark-mdx-npm-2.3.0-9c964d4d99-98486986c5.zip b/.yarn/cache/remark-mdx-npm-2.3.0-9c964d4d99-98486986c5.zip deleted file mode 100644 index 85b5c272ed2..00000000000 Binary files a/.yarn/cache/remark-mdx-npm-2.3.0-9c964d4d99-98486986c5.zip and /dev/null differ diff --git a/.yarn/cache/remark-mdx-npm-3.0.1-898cfa3bb1-e7fcffbe1c.zip b/.yarn/cache/remark-mdx-npm-3.0.1-898cfa3bb1-e7fcffbe1c.zip new file mode 100644 index 00000000000..6df99d475d5 Binary files /dev/null and b/.yarn/cache/remark-mdx-npm-3.0.1-898cfa3bb1-e7fcffbe1c.zip differ diff --git a/.yarn/cache/remark-parse-npm-10.0.2-9d19189a4e-5041b4b447.zip b/.yarn/cache/remark-parse-npm-10.0.2-9d19189a4e-5041b4b447.zip deleted file mode 100644 index 9d3b9a325cc..00000000000 Binary files a/.yarn/cache/remark-parse-npm-10.0.2-9d19189a4e-5041b4b447.zip and /dev/null differ diff --git a/.yarn/cache/remark-parse-npm-11.0.0-6484fba69e-d83d245290.zip b/.yarn/cache/remark-parse-npm-11.0.0-6484fba69e-d83d245290.zip new file mode 100644 index 00000000000..22f945fa368 Binary files /dev/null and b/.yarn/cache/remark-parse-npm-11.0.0-6484fba69e-d83d245290.zip differ diff --git a/.yarn/cache/remark-rehype-npm-10.1.0-bd8e6f7d8b-b9ac8acff3.zip b/.yarn/cache/remark-rehype-npm-10.1.0-bd8e6f7d8b-b9ac8acff3.zip deleted file mode 100644 index 3a586d9d4ee..00000000000 Binary files a/.yarn/cache/remark-rehype-npm-10.1.0-bd8e6f7d8b-b9ac8acff3.zip and /dev/null differ diff --git a/.yarn/cache/remark-rehype-npm-11.1.0-52f1fb906c-f0c731f0ab.zip b/.yarn/cache/remark-rehype-npm-11.1.0-52f1fb906c-f0c731f0ab.zip new file mode 100644 index 00000000000..cc9341bbf58 Binary files /dev/null and b/.yarn/cache/remark-rehype-npm-11.1.0-52f1fb906c-f0c731f0ab.zip differ diff --git a/.yarn/cache/remark-stringify-npm-11.0.0-b41a557b8d-59e07460eb.zip b/.yarn/cache/remark-stringify-npm-11.0.0-b41a557b8d-59e07460eb.zip new file mode 100644 index 00000000000..a17c45ce988 Binary files /dev/null and b/.yarn/cache/remark-stringify-npm-11.0.0-b41a557b8d-59e07460eb.zip differ diff --git a/.yarn/cache/sade-npm-1.8.1-4759dc74c1-0756e5b04c.zip b/.yarn/cache/sade-npm-1.8.1-4759dc74c1-0756e5b04c.zip deleted file mode 100644 index d26d6371729..00000000000 Binary files a/.yarn/cache/sade-npm-1.8.1-4759dc74c1-0756e5b04c.zip and /dev/null differ diff --git a/.yarn/cache/style-to-object-npm-0.4.2-e00b8a7105-314a80bcfa.zip b/.yarn/cache/style-to-object-npm-0.4.2-e00b8a7105-314a80bcfa.zip deleted file mode 100644 index 698b3f25a8b..00000000000 Binary files a/.yarn/cache/style-to-object-npm-0.4.2-e00b8a7105-314a80bcfa.zip and /dev/null differ diff --git a/.yarn/cache/style-to-object-npm-0.4.4-703ebb5748-41656c06f9.zip b/.yarn/cache/style-to-object-npm-0.4.4-703ebb5748-41656c06f9.zip new file mode 100644 index 00000000000..98747897440 Binary files /dev/null and b/.yarn/cache/style-to-object-npm-0.4.4-703ebb5748-41656c06f9.zip differ diff --git a/.yarn/cache/style-to-object-npm-1.0.6-b50013e448-5b58295dcc.zip b/.yarn/cache/style-to-object-npm-1.0.6-b50013e448-5b58295dcc.zip new file mode 100644 index 00000000000..8635363416c Binary files /dev/null and b/.yarn/cache/style-to-object-npm-1.0.6-b50013e448-5b58295dcc.zip differ diff --git a/.yarn/cache/unified-npm-10.1.2-731093c9be-053e7c65ed.zip b/.yarn/cache/unified-npm-10.1.2-731093c9be-053e7c65ed.zip deleted file mode 100644 index a9caecabd5f..00000000000 Binary files a/.yarn/cache/unified-npm-10.1.2-731093c9be-053e7c65ed.zip and /dev/null differ diff --git a/.yarn/cache/unified-npm-11.0.5-ac5333017e-b3bf7fd6f5.zip b/.yarn/cache/unified-npm-11.0.5-ac5333017e-b3bf7fd6f5.zip new file mode 100644 index 00000000000..44d01af93f9 Binary files /dev/null and b/.yarn/cache/unified-npm-11.0.5-ac5333017e-b3bf7fd6f5.zip differ diff --git a/.yarn/cache/unist-util-find-after-npm-5.0.0-04b78835bc-e64bd5ebee.zip b/.yarn/cache/unist-util-find-after-npm-5.0.0-04b78835bc-e64bd5ebee.zip new file mode 100644 index 00000000000..09af3741fab Binary files /dev/null and b/.yarn/cache/unist-util-find-after-npm-5.0.0-04b78835bc-e64bd5ebee.zip differ diff --git a/.yarn/cache/unist-util-generated-npm-2.0.1-cba405dd6d-6221ad0571.zip b/.yarn/cache/unist-util-generated-npm-2.0.1-cba405dd6d-6221ad0571.zip deleted file mode 100644 index e9505850741..00000000000 Binary files a/.yarn/cache/unist-util-generated-npm-2.0.1-cba405dd6d-6221ad0571.zip and /dev/null differ diff --git a/.yarn/cache/unist-util-position-from-estree-npm-1.1.2-2c54b9b445-e3f4060e2a.zip b/.yarn/cache/unist-util-position-from-estree-npm-1.1.2-2c54b9b445-e3f4060e2a.zip deleted file mode 100644 index 72737dcd7d4..00000000000 Binary files a/.yarn/cache/unist-util-position-from-estree-npm-1.1.2-2c54b9b445-e3f4060e2a.zip and /dev/null differ diff --git a/.yarn/cache/unist-util-position-npm-4.0.4-833bfce46c-e7487b6cec.zip b/.yarn/cache/unist-util-position-npm-4.0.4-833bfce46c-e7487b6cec.zip deleted file mode 100644 index 951224989dc..00000000000 Binary files a/.yarn/cache/unist-util-position-npm-4.0.4-833bfce46c-e7487b6cec.zip and /dev/null differ diff --git a/.yarn/cache/unist-util-position-npm-5.0.0-38f216b0a0-f89b27989b.zip b/.yarn/cache/unist-util-position-npm-5.0.0-38f216b0a0-f89b27989b.zip new file mode 100644 index 00000000000..1b9e884b6ce Binary files /dev/null and b/.yarn/cache/unist-util-position-npm-5.0.0-38f216b0a0-f89b27989b.zip differ diff --git a/.yarn/cache/unist-util-remove-npm-3.1.1-4eb96f179f-ed7c762941.zip b/.yarn/cache/unist-util-remove-npm-3.1.1-4eb96f179f-ed7c762941.zip new file mode 100644 index 00000000000..717803e578b Binary files /dev/null and b/.yarn/cache/unist-util-remove-npm-3.1.1-4eb96f179f-ed7c762941.zip differ diff --git a/.yarn/cache/unist-util-remove-position-npm-4.0.2-5806d5548a-989831da91.zip b/.yarn/cache/unist-util-remove-position-npm-4.0.2-5806d5548a-989831da91.zip deleted file mode 100644 index a85cc0cc80b..00000000000 Binary files a/.yarn/cache/unist-util-remove-position-npm-4.0.2-5806d5548a-989831da91.zip and /dev/null differ diff --git a/.yarn/cache/unist-util-stringify-position-npm-3.0.3-3ab0818239-dbd66c1518.zip b/.yarn/cache/unist-util-stringify-position-npm-3.0.3-3ab0818239-dbd66c1518.zip deleted file mode 100644 index 2fde8ff025d..00000000000 Binary files a/.yarn/cache/unist-util-stringify-position-npm-3.0.3-3ab0818239-dbd66c1518.zip and /dev/null differ diff --git a/.yarn/cache/unist-util-visit-npm-4.1.2-6b950e655a-95a34e3f7b.zip b/.yarn/cache/unist-util-visit-npm-4.1.2-6b950e655a-95a34e3f7b.zip deleted file mode 100644 index db0ed8b7f4c..00000000000 Binary files a/.yarn/cache/unist-util-visit-npm-4.1.2-6b950e655a-95a34e3f7b.zip and /dev/null differ diff --git a/.yarn/cache/update-browserslist-db-npm-1.1.0-3d2cb7d955-7b74694d96.zip b/.yarn/cache/update-browserslist-db-npm-1.1.0-3d2cb7d955-7b74694d96.zip new file mode 100644 index 00000000000..302c32377f6 Binary files /dev/null and b/.yarn/cache/update-browserslist-db-npm-1.1.0-3d2cb7d955-7b74694d96.zip differ diff --git a/.yarn/cache/uvu-npm-0.5.6-c8507ad49b-09460a3797.zip b/.yarn/cache/uvu-npm-0.5.6-c8507ad49b-09460a3797.zip deleted file mode 100644 index 133fe2756ef..00000000000 Binary files a/.yarn/cache/uvu-npm-0.5.6-c8507ad49b-09460a3797.zip and /dev/null differ diff --git a/.yarn/cache/vfile-location-npm-5.0.3-f510ce60de-bfb3821b69.zip b/.yarn/cache/vfile-location-npm-5.0.3-f510ce60de-bfb3821b69.zip new file mode 100644 index 00000000000..44c8dc55e9f Binary files /dev/null and b/.yarn/cache/vfile-location-npm-5.0.3-f510ce60de-bfb3821b69.zip differ diff --git a/.yarn/cache/vfile-matter-npm-3.0.1-9d8d8bf427-ced55ed7d7.zip b/.yarn/cache/vfile-matter-npm-3.0.1-9d8d8bf427-ced55ed7d7.zip deleted file mode 100644 index 392b400b5a8..00000000000 Binary files a/.yarn/cache/vfile-matter-npm-3.0.1-9d8d8bf427-ced55ed7d7.zip and /dev/null differ diff --git a/.yarn/cache/vfile-matter-npm-5.0.0-8f034005c1-713327640e.zip b/.yarn/cache/vfile-matter-npm-5.0.0-8f034005c1-713327640e.zip new file mode 100644 index 00000000000..69ca22bc955 Binary files /dev/null and b/.yarn/cache/vfile-matter-npm-5.0.0-8f034005c1-713327640e.zip differ diff --git a/.yarn/cache/vfile-message-npm-3.1.4-47b355eba8-d0ee7da197.zip b/.yarn/cache/vfile-message-npm-3.1.4-47b355eba8-d0ee7da197.zip deleted file mode 100644 index 197f3bddbef..00000000000 Binary files a/.yarn/cache/vfile-message-npm-3.1.4-47b355eba8-d0ee7da197.zip and /dev/null differ diff --git a/.yarn/cache/vfile-npm-5.3.7-3fe49f8a33-642cce703a.zip b/.yarn/cache/vfile-npm-5.3.7-3fe49f8a33-642cce703a.zip deleted file mode 100644 index 022e0eab20d..00000000000 Binary files a/.yarn/cache/vfile-npm-5.3.7-3fe49f8a33-642cce703a.zip and /dev/null differ diff --git a/.yarn/cache/vfile-npm-6.0.2-19da19c73a-2f3f405654.zip b/.yarn/cache/vfile-npm-6.0.2-19da19c73a-2f3f405654.zip new file mode 100644 index 00000000000..ee757e034d3 Binary files /dev/null and b/.yarn/cache/vfile-npm-6.0.2-19da19c73a-2f3f405654.zip differ diff --git a/.yarn/cache/web-namespaces-npm-2.0.1-f7b8233848-b6d9f02f1a.zip b/.yarn/cache/web-namespaces-npm-2.0.1-f7b8233848-b6d9f02f1a.zip new file mode 100644 index 00000000000..171032de302 Binary files /dev/null and b/.yarn/cache/web-namespaces-npm-2.0.1-f7b8233848-b6d9f02f1a.zip differ diff --git a/.yarn/cache/whatwg-url-npm-6.5.0-07c2c28a54-a10bd5e29f.zip b/.yarn/cache/whatwg-url-npm-6.5.0-07c2c28a54-a10bd5e29f.zip new file mode 100644 index 00000000000..795e0291052 Binary files /dev/null and b/.yarn/cache/whatwg-url-npm-6.5.0-07c2c28a54-a10bd5e29f.zip differ diff --git a/fern.schema.dev.json b/fern.schema.dev.json index d417c9ce8b8..87753c82be6 100644 --- a/fern.schema.dev.json +++ b/fern.schema.dev.json @@ -52,6 +52,22 @@ "additionalProperties": false } }, + "encoding": { + "type": "object", + "properties": { + "proto": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "The name of the Protobuf type (e.g. google.protobuf.Struct)." + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, "extensions": { "type": "object", "additionalProperties": {} }, "extends": { "anyOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" } }] @@ -128,6 +144,9 @@ "examples": { "$ref": "#/properties/types/additionalProperties/anyOf/1/anyOf/0/properties/examples" }, + "encoding": { + "$ref": "#/properties/types/additionalProperties/anyOf/1/anyOf/0/properties/encoding" + }, "extensions": { "type": "object", "additionalProperties": {} }, "extends": { "$ref": "#/properties/types/additionalProperties/anyOf/1/anyOf/0/properties/extends" @@ -156,6 +175,7 @@ "$ref": "#/properties/types/additionalProperties/anyOf/1/anyOf/0/properties/audiences" }, "examples": { "$ref": "#/properties/types/additionalProperties/anyOf/1/anyOf/0/properties/examples" }, + "encoding": { "$ref": "#/properties/types/additionalProperties/anyOf/1/anyOf/0/properties/encoding" }, "base-properties": { "type": "object", "additionalProperties": { @@ -236,6 +256,7 @@ "$ref": "#/properties/types/additionalProperties/anyOf/1/anyOf/0/properties/audiences" }, "examples": { "$ref": "#/properties/types/additionalProperties/anyOf/1/anyOf/0/properties/examples" }, + "encoding": { "$ref": "#/properties/types/additionalProperties/anyOf/1/anyOf/0/properties/encoding" }, "type": { "type": "string" }, "validation": { "$ref": "#/properties/types/additionalProperties/anyOf/1/anyOf/0/properties/properties/additionalProperties/anyOf/1/properties/validation" @@ -255,6 +276,7 @@ "$ref": "#/properties/types/additionalProperties/anyOf/1/anyOf/0/properties/audiences" }, "examples": { "$ref": "#/properties/types/additionalProperties/anyOf/1/anyOf/0/properties/examples" }, + "encoding": { "$ref": "#/properties/types/additionalProperties/anyOf/1/anyOf/0/properties/encoding" }, "default": { "type": "string" }, "enum": { "type": "array", @@ -303,6 +325,7 @@ "$ref": "#/properties/types/additionalProperties/anyOf/1/anyOf/0/properties/audiences" }, "examples": { "$ref": "#/properties/types/additionalProperties/anyOf/1/anyOf/0/properties/examples" }, + "encoding": { "$ref": "#/properties/types/additionalProperties/anyOf/1/anyOf/0/properties/encoding" }, "union": { "type": "array", "items": { @@ -414,6 +437,20 @@ ] } }, + "transport": { + "type": "object", + "properties": { + "grpc": { + "type": "object", + "properties": { + "service-name": { "type": "string", "description": "The name of the gRPC service." } + }, + "required": ["service-name"], + "additionalProperties": false + } + }, + "additionalProperties": false + }, "endpoints": { "type": "object", "additionalProperties": { diff --git a/fern.schema.json b/fern.schema.json index d417c9ce8b8..87753c82be6 100644 --- a/fern.schema.json +++ b/fern.schema.json @@ -52,6 +52,22 @@ "additionalProperties": false } }, + "encoding": { + "type": "object", + "properties": { + "proto": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "The name of the Protobuf type (e.g. google.protobuf.Struct)." + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, "extensions": { "type": "object", "additionalProperties": {} }, "extends": { "anyOf": [{ "type": "string" }, { "type": "array", "items": { "type": "string" } }] @@ -128,6 +144,9 @@ "examples": { "$ref": "#/properties/types/additionalProperties/anyOf/1/anyOf/0/properties/examples" }, + "encoding": { + "$ref": "#/properties/types/additionalProperties/anyOf/1/anyOf/0/properties/encoding" + }, "extensions": { "type": "object", "additionalProperties": {} }, "extends": { "$ref": "#/properties/types/additionalProperties/anyOf/1/anyOf/0/properties/extends" @@ -156,6 +175,7 @@ "$ref": "#/properties/types/additionalProperties/anyOf/1/anyOf/0/properties/audiences" }, "examples": { "$ref": "#/properties/types/additionalProperties/anyOf/1/anyOf/0/properties/examples" }, + "encoding": { "$ref": "#/properties/types/additionalProperties/anyOf/1/anyOf/0/properties/encoding" }, "base-properties": { "type": "object", "additionalProperties": { @@ -236,6 +256,7 @@ "$ref": "#/properties/types/additionalProperties/anyOf/1/anyOf/0/properties/audiences" }, "examples": { "$ref": "#/properties/types/additionalProperties/anyOf/1/anyOf/0/properties/examples" }, + "encoding": { "$ref": "#/properties/types/additionalProperties/anyOf/1/anyOf/0/properties/encoding" }, "type": { "type": "string" }, "validation": { "$ref": "#/properties/types/additionalProperties/anyOf/1/anyOf/0/properties/properties/additionalProperties/anyOf/1/properties/validation" @@ -255,6 +276,7 @@ "$ref": "#/properties/types/additionalProperties/anyOf/1/anyOf/0/properties/audiences" }, "examples": { "$ref": "#/properties/types/additionalProperties/anyOf/1/anyOf/0/properties/examples" }, + "encoding": { "$ref": "#/properties/types/additionalProperties/anyOf/1/anyOf/0/properties/encoding" }, "default": { "type": "string" }, "enum": { "type": "array", @@ -303,6 +325,7 @@ "$ref": "#/properties/types/additionalProperties/anyOf/1/anyOf/0/properties/audiences" }, "examples": { "$ref": "#/properties/types/additionalProperties/anyOf/1/anyOf/0/properties/examples" }, + "encoding": { "$ref": "#/properties/types/additionalProperties/anyOf/1/anyOf/0/properties/encoding" }, "union": { "type": "array", "items": { @@ -414,6 +437,20 @@ ] } }, + "transport": { + "type": "object", + "properties": { + "grpc": { + "type": "object", + "properties": { + "service-name": { "type": "string", "description": "The name of the gRPC service." } + }, + "required": ["service-name"], + "additionalProperties": false + } + }, + "additionalProperties": false + }, "endpoints": { "type": "object", "additionalProperties": { diff --git a/fern/docs.yml b/fern/docs.yml index 86315e28638..701f33fe982 100644 --- a/fern/docs.yml +++ b/fern/docs.yml @@ -44,6 +44,8 @@ navigation: contents: - page: What is an API Definition? path: ./pages/api-definition/introduction/what-is-an-api-definition.mdx + - page: What is the Fern Folder? + path: ./pages/api-definition/introduction/what-is-the-fern-folder.mdx - section: OpenAPI Specification slug: openapi contents: diff --git a/fern/fern.config.json b/fern/fern.config.json index 04b90b8a847..7d8a700bd42 100644 --- a/fern/fern.config.json +++ b/fern/fern.config.json @@ -1,4 +1,4 @@ { "organization": "fern", - "version": "0.29.0" + "version": "*" } \ No newline at end of file diff --git a/fern/pages/api-definition/introduction/what-is-the-fern-folder.mdx b/fern/pages/api-definition/introduction/what-is-the-fern-folder.mdx new file mode 100644 index 00000000000..05e46c8c6f1 --- /dev/null +++ b/fern/pages/api-definition/introduction/what-is-the-fern-folder.mdx @@ -0,0 +1,98 @@ +--- +title: The Fern Folder +description: Describes the Fern folder structure +--- + +Configuring fern starts with the `fern` folder. The fern folder contains your API definitions, +generators, and your CLI version. + +## Directory Structure + +When you run `fern init`, your Fern folder will be initialized with the following files: +```yaml +fern/ + ├─ fern.config.json + ├─ generators.yml + └─ definition/ + ├─ api.yml + └─ imdb.yml +``` + +If you want to intialize Fern with an OpenAPI spec, run `fern init --openapi path/to/openapi` instead. +```yaml +fern/ + ├─ fern.config.json + ├─ generators.yml + └─ openapi/ + ├─ openapi.yml +``` + +### `fern.config.json` + +Every fern folder has a single `fern.config.json` file. This file stores the organization and +the version of the Fern CLI that you are using. + +```json +{ + "organization": "imdb", + "version": "0.31.2" +} +``` + +Every time you run a fern CLI command, the CLI downloads itself at the correct version to ensure +determinism. + +To upgrade the CLI, run `fern upgrade`. This will update the version field in `fern.config.json` + +### `generators.yml` + +The `generators.yml` file includes information about which generators you are using, where each package gets published, +as well as configuration specific to each generator. + +Every `generators.yml` file contains groups of generators. In the example below, we have a group called `public` which +generates Python + TypeScript SDKs. +```yaml +groups: + public: + generators: + - name: fernapi/fern-python-sdk + version: 3.0.0 + output: + location: pypi + package-name: imdb + token: ${PYPI_TOKEN} + github: + repository: imdb/imdb-python + config: + client_class_name: imdb + - name: fernapi/fern-typescript-node-sdk + version: 0.31.0 + output: + location: npm + package-name: imdb + token: ${NPM_TOKEN} + github: + repository: imdb/imdb-node + config: + namespaceExport: imdb +``` + +## Multiple APIs + +The Fern folder is capable of housing multiple API definitions. Instead of placing your API definition +at the top-level, you can nest them within an `apis` folder. + +```yaml +fern/ + ├─ fern.config.json + ├─ generators.yml + └─ apis/ + └─ imdb/ + ├─ generators.yml + └─ openapi/ + ├─ openapi.yml + └─ disney/ + ├─ generators.yml + └─ openapi/ + ├─ openapi.yml +``` \ No newline at end of file diff --git a/fern/pages/fern-docs/content/write-markdown.mdx b/fern/pages/fern-docs/content/write-markdown.mdx index 0891e41ea28..725bae5e0ac 100644 --- a/fern/pages/fern-docs/content/write-markdown.mdx +++ b/fern/pages/fern-docs/content/write-markdown.mdx @@ -70,3 +70,24 @@ Read the [Introduction](/learn/overview/introduction). To use images in your Markdown pages, upload them to a service such as [Amazon S3](https://aws.amazon.com/media-sharing/) or [Google Cloud Storage](https://cloud.google.com/storage). Once you have the URL to your image, use [Markdown syntax to insert the image](https://www.markdownguide.org/basic-syntax/#images-1). + +## LaTeX + +Fern supports [LaTeX](https://www.latex-project.org/) math equations. To use LaTeX, wrap your inline math equations in `$`. For example, `$(x^2 + y^2 = z^2)$` will render $x^2 + y^2 = z^2$. + +For display math equations, wrap the equation in `$$`. For example: + +```latex +$$ +% \f is defined as #1f(#2) using the macro +\f\relax{x} = \int_{-\infty}^\infty + \f\hat\xi\,e^{2 \pi i \xi x} + \,d\xi +$$ +``` + +$$ +\f\relax{x} = \int_{-\infty}^\infty + \f\hat\xi\,e^{2 \pi i \xi x} + \,d\xi +$$ diff --git a/fern/pages/sdks/comparison/speakeasy.mdx b/fern/pages/sdks/comparison/speakeasy.mdx index 688e44bf475..621d7786129 100644 --- a/fern/pages/sdks/comparison/speakeasy.mdx +++ b/fern/pages/sdks/comparison/speakeasy.mdx @@ -3,7 +3,7 @@ title: Speakeasy excerpt: How do Fern and Speakeasy differ? --- -[Speakeasy](https://www.speakeasyapi.dev/), similar to Fern, supports generating SDKs for multiple languages. Below, we +[Speakeasy](https://www.speakeasyapi.dev/), similar to Fern, supports generating SDKs for APIs in multiple languages. Below, we walk through the different capabilities of Fern and Speakeasy. ### 1. Fern is an all-in-one: SDKs + Docs diff --git a/fern/pages/welcome.mdx b/fern/pages/welcome.mdx index 48d9032197d..6206f2e0928 100644 --- a/fern/pages/welcome.mdx +++ b/fern/pages/welcome.mdx @@ -3,6 +3,7 @@ title: Welcome description: Input OpenAPI. Output SDKs and Docs. slug: home layout: overview +hide-toc: true --- ## Products diff --git a/generators/go/VERSION b/generators/go/VERSION index d90746a79af..ca222b7cf39 100644 --- a/generators/go/VERSION +++ b/generators/go/VERSION @@ -1 +1 @@ -0.22.3 +0.23.0 diff --git a/generators/go/cmd/fern-go-fiber/main.go b/generators/go/cmd/fern-go-fiber/main.go index 21fabe10357..78203dc3ad5 100644 --- a/generators/go/cmd/fern-go-fiber/main.go +++ b/generators/go/cmd/fern-go-fiber/main.go @@ -28,6 +28,7 @@ func run(config *cmd.Config, coordinator *coordinator.Client) ([]*generator.File config.IncludeLegacyClientOptions, includeReadme, config.Whitelabel, + config.AlwaysSendRequiredProperties, config.Organization, config.Version, config.IrFilepath, diff --git a/generators/go/cmd/fern-go-model/main.go b/generators/go/cmd/fern-go-model/main.go index dde6870f5e0..f2abc2b8bd7 100644 --- a/generators/go/cmd/fern-go-model/main.go +++ b/generators/go/cmd/fern-go-model/main.go @@ -28,6 +28,7 @@ func run(config *cmd.Config, coordinator *coordinator.Client) ([]*generator.File config.IncludeLegacyClientOptions, includeReadme, config.Whitelabel, + config.AlwaysSendRequiredProperties, config.Organization, config.Version, config.IrFilepath, diff --git a/generators/go/cmd/fern-go-sdk/main.go b/generators/go/cmd/fern-go-sdk/main.go index dcca645d96d..ffe3ff5b39e 100644 --- a/generators/go/cmd/fern-go-sdk/main.go +++ b/generators/go/cmd/fern-go-sdk/main.go @@ -28,6 +28,7 @@ func run(config *cmd.Config, coordinator *coordinator.Client) ([]*generator.File config.IncludeLegacyClientOptions, includeReadme, config.Whitelabel, + config.AlwaysSendRequiredProperties, config.Organization, config.Version, config.IrFilepath, diff --git a/generators/go/internal/cmd/cmd.go b/generators/go/internal/cmd/cmd.go index 674ab2515be..1fb428e74a5 100644 --- a/generators/go/internal/cmd/cmd.go +++ b/generators/go/internal/cmd/cmd.go @@ -51,21 +51,22 @@ var ( // Config represents the common configuration required from all of // the commands (e.g. fern-go-{client,model}). type Config struct { - DryRun bool - EnableExplicitNull bool - IncludeLegacyClientOptions bool - Whitelabel bool - Organization string - CoordinatorURL string - CoordinatorTaskID string - Version string - IrFilepath string - SnippetFilepath string - ImportPath string - PackageName string - UnionVersion string - Module *generator.ModuleConfig - Writer *writer.Config + DryRun bool + EnableExplicitNull bool + IncludeLegacyClientOptions bool + Whitelabel bool + AlwaysSendRequiredProperties bool + Organization string + CoordinatorURL string + CoordinatorTaskID string + Version string + IrFilepath string + SnippetFilepath string + ImportPath string + PackageName string + UnionVersion string + Module *generator.ModuleConfig + Writer *writer.Config } // GeneratorFunc is a function that generates files. @@ -197,21 +198,22 @@ func newConfig(configFilename string) (*Config, error) { } return &Config{ - DryRun: config.DryRun, - IncludeLegacyClientOptions: customConfig.IncludeLegacyClientOptions, - EnableExplicitNull: customConfig.EnableExplicitNull, - Organization: config.Organization, - Whitelabel: config.Whitelabel, - CoordinatorURL: coordinatorURL, - CoordinatorTaskID: coordinatorTaskID, - Version: outputVersionFromGeneratorConfig(config), - IrFilepath: config.IrFilepath, - SnippetFilepath: snippetFilepath, - ImportPath: customConfig.ImportPath, - PackageName: customConfig.PackageName, - UnionVersion: customConfig.UnionVersion, - Module: moduleConfig, - Writer: writerConfig, + DryRun: config.DryRun, + IncludeLegacyClientOptions: customConfig.IncludeLegacyClientOptions, + EnableExplicitNull: customConfig.EnableExplicitNull, + Organization: config.Organization, + AlwaysSendRequiredProperties: customConfig.AlwaysSendRequiredProperties, + Whitelabel: config.Whitelabel, + CoordinatorURL: coordinatorURL, + CoordinatorTaskID: coordinatorTaskID, + Version: outputVersionFromGeneratorConfig(config), + IrFilepath: config.IrFilepath, + SnippetFilepath: snippetFilepath, + ImportPath: customConfig.ImportPath, + PackageName: customConfig.PackageName, + UnionVersion: customConfig.UnionVersion, + Module: moduleConfig, + Writer: writerConfig, }, nil } @@ -250,12 +252,13 @@ func readConfig(configFilename string) (*generatorexec.GeneratorConfig, error) { } type customConfig struct { - EnableExplicitNull bool `json:"enableExplicitNull,omitempty"` - IncludeLegacyClientOptions bool `json:"includeLegacyClientOptions,omitempty"` - ImportPath string `json:"importPath,omitempty"` - PackageName string `json:"packageName,omitempty"` - UnionVersion string `json:"union,omitempty"` - Module *moduleConfig `json:"module,omitempty"` + EnableExplicitNull bool `json:"enableExplicitNull,omitempty"` + IncludeLegacyClientOptions bool `json:"includeLegacyClientOptions,omitempty"` + AlwaysSendRequiredProperties bool `json:"alwaysSendRequiredProperties,omitempty"` + ImportPath string `json:"importPath,omitempty"` + PackageName string `json:"packageName,omitempty"` + UnionVersion string `json:"union,omitempty"` + Module *moduleConfig `json:"module,omitempty"` } type moduleConfig struct { diff --git a/generators/go/internal/generator/config.go b/generators/go/internal/generator/config.go index bd55d68da6e..405324818d1 100644 --- a/generators/go/internal/generator/config.go +++ b/generators/go/internal/generator/config.go @@ -14,18 +14,19 @@ const ( // Config represents the Fern generator configuration. type Config struct { - DryRun bool - EnableExplicitNull bool - IncludeLegacyClientOptions bool - IncludeReadme bool - Whitelabel bool - Organization string - Version string - IRFilepath string - SnippetFilepath string - ImportPath string - UnionVersion UnionVersion - PackageName string + DryRun bool + EnableExplicitNull bool + IncludeLegacyClientOptions bool + IncludeReadme bool + Whitelabel bool + AlwaysSendRequiredProperties bool + Organization string + Version string + IRFilepath string + SnippetFilepath string + ImportPath string + UnionVersion UnionVersion + PackageName string // If not specified, a go.mod and go.sum will not be generated. ModuleConfig *ModuleConfig @@ -54,6 +55,7 @@ func NewConfig( includeLegacyClientOptions bool, includeReadme bool, whitelabel bool, + alwaysSendRequiredProperties bool, organization string, version string, irFilepath string, @@ -68,19 +70,20 @@ func NewConfig( return nil, err } return &Config{ - DryRun: dryRun, - EnableExplicitNull: enableExplicitNull, - IncludeLegacyClientOptions: includeLegacyClientOptions, - IncludeReadme: includeReadme, - Organization: organization, - Whitelabel: whitelabel, - Version: version, - IRFilepath: irFilepath, - SnippetFilepath: snippetFilepath, - ImportPath: importPath, - PackageName: packageName, - UnionVersion: uv, - ModuleConfig: moduleConfig, + DryRun: dryRun, + EnableExplicitNull: enableExplicitNull, + IncludeLegacyClientOptions: includeLegacyClientOptions, + IncludeReadme: includeReadme, + Organization: organization, + Whitelabel: whitelabel, + AlwaysSendRequiredProperties: alwaysSendRequiredProperties, + Version: version, + IRFilepath: irFilepath, + SnippetFilepath: snippetFilepath, + ImportPath: importPath, + PackageName: packageName, + UnionVersion: uv, + ModuleConfig: moduleConfig, }, nil } diff --git a/generators/go/internal/generator/file_writer.go b/generators/go/internal/generator/file_writer.go index 344b6a62dab..c440148e070 100644 --- a/generators/go/internal/generator/file_writer.go +++ b/generators/go/internal/generator/file_writer.go @@ -28,16 +28,17 @@ const whitelabelFileHeader = `// This file was auto-generated from our API Defin // fileWriter wries and formats Go files. type fileWriter struct { - filename string - packageName string - baseImportPath string - whitelabel bool - unionVersion UnionVersion - scope *gospec.Scope - types map[ir.TypeId]*ir.TypeDeclaration - errors map[ir.ErrorId]*ir.ErrorDeclaration - coordinator *coordinator.Client - snippetWriter *SnippetWriter + filename string + packageName string + baseImportPath string + whitelabel bool + alwaysSendRequiredProperties bool + unionVersion UnionVersion + scope *gospec.Scope + types map[ir.TypeId]*ir.TypeDeclaration + errors map[ir.ErrorId]*ir.ErrorDeclaration + coordinator *coordinator.Client + snippetWriter *SnippetWriter buffer *bytes.Buffer } @@ -47,6 +48,7 @@ func newFileWriter( packageName string, baseImportPath string, whitelabel bool, + alwaysSendRequiredProperties bool, unionVersion UnionVersion, types map[ir.TypeId]*ir.TypeDeclaration, errors map[ir.ErrorId]*ir.ErrorDeclaration, @@ -81,17 +83,18 @@ func newFileWriter( scope.AddImport(path.Join(baseImportPath, "option")) return &fileWriter{ - filename: filename, - packageName: packageName, - baseImportPath: baseImportPath, - whitelabel: whitelabel, - unionVersion: unionVersion, - scope: scope, - types: types, - errors: errors, - coordinator: coordinator, - snippetWriter: NewSnippetWriter(baseImportPath, unionVersion, types), - buffer: new(bytes.Buffer), + filename: filename, + packageName: packageName, + baseImportPath: baseImportPath, + whitelabel: whitelabel, + alwaysSendRequiredProperties: alwaysSendRequiredProperties, + unionVersion: unionVersion, + scope: scope, + types: types, + errors: errors, + coordinator: coordinator, + snippetWriter: NewSnippetWriter(baseImportPath, unionVersion, types), + buffer: new(bytes.Buffer), } } @@ -158,6 +161,7 @@ func (f *fileWriter) clone() *fileWriter { f.packageName, f.baseImportPath, f.whitelabel, + f.alwaysSendRequiredProperties, f.unionVersion, f.types, f.errors, diff --git a/generators/go/internal/generator/generator.go b/generators/go/internal/generator/generator.go index c6deb44c6a3..86a8b1173ed 100644 --- a/generators/go/internal/generator/generator.go +++ b/generators/go/internal/generator/generator.go @@ -133,6 +133,7 @@ func (g *Generator) generateModelTypes(ir *fernir.IntermediateRepresentation, mo fileInfo.packageName, g.config.ImportPath, g.config.Whitelabel, + g.config.AlwaysSendRequiredProperties, g.config.UnionVersion, ir.Types, ir.Errors, @@ -237,6 +238,7 @@ func (g *Generator) generate(ir *fernir.IntermediateRepresentation, mode Mode) ( fileInfo.packageName, "", g.config.Whitelabel, + g.config.AlwaysSendRequiredProperties, g.config.UnionVersion, nil, nil, @@ -255,6 +257,7 @@ func (g *Generator) generate(ir *fernir.IntermediateRepresentation, mode Mode) ( fileInfo.packageName, "", g.config.Whitelabel, + g.config.AlwaysSendRequiredProperties, g.config.UnionVersion, nil, nil, @@ -296,6 +299,7 @@ func (g *Generator) generate(ir *fernir.IntermediateRepresentation, mode Mode) ( fileInfo.packageName, g.config.ImportPath, g.config.Whitelabel, + g.config.AlwaysSendRequiredProperties, g.config.UnionVersion, ir.Types, ir.Errors, @@ -324,6 +328,7 @@ func (g *Generator) generate(ir *fernir.IntermediateRepresentation, mode Mode) ( fileInfo.packageName, g.config.ImportPath, g.config.Whitelabel, + g.config.AlwaysSendRequiredProperties, g.config.UnionVersion, ir.Types, ir.Errors, @@ -346,6 +351,7 @@ func (g *Generator) generate(ir *fernir.IntermediateRepresentation, mode Mode) ( fileInfo.packageName, g.config.ImportPath, g.config.Whitelabel, + g.config.AlwaysSendRequiredProperties, g.config.UnionVersion, ir.Types, ir.Errors, @@ -372,6 +378,7 @@ func (g *Generator) generate(ir *fernir.IntermediateRepresentation, mode Mode) ( fileInfo.packageName, g.config.ImportPath, g.config.Whitelabel, + g.config.AlwaysSendRequiredProperties, g.config.UnionVersion, ir.Types, ir.Errors, @@ -391,6 +398,7 @@ func (g *Generator) generate(ir *fernir.IntermediateRepresentation, mode Mode) ( fileInfo.packageName, g.config.ImportPath, g.config.Whitelabel, + g.config.AlwaysSendRequiredProperties, g.config.UnionVersion, ir.Types, ir.Errors, @@ -413,6 +421,7 @@ func (g *Generator) generate(ir *fernir.IntermediateRepresentation, mode Mode) ( fileInfo.packageName, g.config.ImportPath, g.config.Whitelabel, + g.config.AlwaysSendRequiredProperties, g.config.UnionVersion, ir.Types, ir.Errors, @@ -434,6 +443,7 @@ func (g *Generator) generate(ir *fernir.IntermediateRepresentation, mode Mode) ( fileInfo.packageName, g.config.ImportPath, g.config.Whitelabel, + g.config.AlwaysSendRequiredProperties, g.config.UnionVersion, ir.Types, ir.Errors, @@ -476,6 +486,7 @@ func (g *Generator) generate(ir *fernir.IntermediateRepresentation, mode Mode) ( fileInfo.packageName, g.config.ImportPath, g.config.Whitelabel, + g.config.AlwaysSendRequiredProperties, g.config.UnionVersion, ir.Types, ir.Errors, @@ -628,6 +639,7 @@ func (g *Generator) generateService( fileInfo.packageName, g.config.ImportPath, g.config.Whitelabel, + g.config.AlwaysSendRequiredProperties, g.config.UnionVersion, ir.Types, ir.Errors, @@ -670,6 +682,7 @@ func (g *Generator) generateServiceWithoutEndpoints( fileInfo.packageName, g.config.ImportPath, g.config.Whitelabel, + g.config.AlwaysSendRequiredProperties, g.config.UnionVersion, ir.Types, ir.Errors, @@ -706,6 +719,7 @@ func (g *Generator) generateRootServiceWithoutEndpoints( fileInfo.packageName, g.config.ImportPath, g.config.Whitelabel, + g.config.AlwaysSendRequiredProperties, g.config.UnionVersion, ir.Types, ir.Errors, @@ -910,6 +924,7 @@ func newClientTestFile( "client", baseImportPath, false, + false, UnionVersionUnspecified, nil, nil, diff --git a/generators/go/internal/generator/model.go b/generators/go/internal/generator/model.go index 13f051dd8da..987a598b9b2 100644 --- a/generators/go/internal/generator/model.go +++ b/generators/go/internal/generator/model.go @@ -32,12 +32,13 @@ func (f *fileWriter) WriteType( includeRawJSON bool, ) error { visitor := &typeVisitor{ - typeName: typeDeclaration.Name.Name.PascalCase.UnsafeName, - baseImportPath: f.baseImportPath, - importPath: fernFilepathToImportPath(f.baseImportPath, typeDeclaration.Name.FernFilepath), - writer: f, - unionVersion: f.unionVersion, - includeRawJSON: includeRawJSON, + typeName: typeDeclaration.Name.Name.PascalCase.UnsafeName, + baseImportPath: f.baseImportPath, + importPath: fernFilepathToImportPath(f.baseImportPath, typeDeclaration.Name.FernFilepath), + writer: f, + unionVersion: f.unionVersion, + alwaysSendRequiredProperties: f.alwaysSendRequiredProperties, + includeRawJSON: includeRawJSON, } f.WriteDocs(typeDeclaration.Docs) return typeDeclaration.Shape.Accept(visitor) @@ -50,8 +51,9 @@ type typeVisitor struct { importPath string writer *fileWriter - unionVersion UnionVersion - includeRawJSON bool + unionVersion UnionVersion + includeRawJSON bool + alwaysSendRequiredProperties bool } // Compile-time assertion. @@ -385,7 +387,7 @@ func (t *typeVisitor) VisitUnion(union *ir.UnionTypeDeclaration) error { continue } propertyNames = append(propertyNames, property.Name.Name.PascalCase.UnsafeName) - t.writer.P(property.Name.Name.PascalCase.UnsafeName, " ", typeReferenceToGoType(property.ValueType, t.writer.types, t.writer.scope, t.baseImportPath, t.importPath, false), jsonTagForType(property.Name.WireValue, property.ValueType, t.writer.types)) + t.writer.P(property.Name.Name.PascalCase.UnsafeName, " ", typeReferenceToGoType(property.ValueType, t.writer.types, t.writer.scope, t.baseImportPath, t.importPath, false), jsonTagForType(property.Name.WireValue, property.ValueType, t.writer.types, t.alwaysSendRequiredProperties)) } t.writer.P("}") t.writer.P("if err := json.Unmarshal(data, &unmarshaler); err != nil {") @@ -423,7 +425,7 @@ func (t *typeVisitor) VisitUnion(union *ir.UnionTypeDeclaration) error { // } t.writer.P("var valueUnmarshaler struct {") singleUnionProperty := singleUnionTypePropertiesToGoType(unionType.Shape, t.writer.types, t.writer.scope, t.baseImportPath, t.importPath) - t.writer.P(unionType.DiscriminantValue.Name.PascalCase.UnsafeName, " ", singleUnionProperty.valueMarshalerGoType, jsonTagForType(unionType.Shape.SingleProperty.Name.WireValue, unionType.Shape.SingleProperty.Type, t.writer.types)) + t.writer.P(unionType.DiscriminantValue.Name.PascalCase.UnsafeName, " ", singleUnionProperty.valueMarshalerGoType, jsonTagForType(unionType.Shape.SingleProperty.Name.WireValue, unionType.Shape.SingleProperty.Type, t.writer.types, t.alwaysSendRequiredProperties)) t.writer.P("}") t.writer.P("if err := json.Unmarshal(data, &valueUnmarshaler); err != nil {") t.writer.P("return err") @@ -503,14 +505,14 @@ func (t *typeVisitor) VisitUnion(union *ir.UnionTypeDeclaration) error { t.writer.types[extend.TypeId].Shape.Object, true, // includeJSONTags true, // includeURLTags - false, // includeOptionals + false, // includeOptionals, ) } for _, property := range union.BaseProperties { if property.ValueType.Container != nil && property.ValueType.Container.Literal != nil { continue } - t.writer.P(property.Name.Name.PascalCase.UnsafeName, " ", typeReferenceToGoType(property.ValueType, t.writer.types, t.writer.scope, t.baseImportPath, t.importPath, false), jsonTagForType(property.Name.WireValue, property.ValueType, t.writer.types)) + t.writer.P(property.Name.Name.PascalCase.UnsafeName, " ", typeReferenceToGoType(property.ValueType, t.writer.types, t.writer.scope, t.baseImportPath, t.importPath, false), jsonTagForType(property.Name.WireValue, property.ValueType, t.writer.types, t.alwaysSendRequiredProperties)) } for _, literal := range literals { t.writer.P(literal.Name.Name.PascalCase.UnsafeName, " ", literalToGoType(literal.Value), " `json:\"", literal.Name.WireValue, "\"`") @@ -519,7 +521,7 @@ func (t *typeVisitor) VisitUnion(union *ir.UnionTypeDeclaration) error { typeName := singleUnionProperty.goType switch unionType.Shape.PropertiesType { case "singleProperty": - t.writer.P(unionType.DiscriminantValue.Name.PascalCase.UnsafeName, " ", singleUnionProperty.valueMarshalerGoType, jsonTagForType(unionType.Shape.SingleProperty.Name.WireValue, unionType.Shape.SingleProperty.Type, t.writer.types)) + t.writer.P(unionType.DiscriminantValue.Name.PascalCase.UnsafeName, " ", singleUnionProperty.valueMarshalerGoType, jsonTagForType(unionType.Shape.SingleProperty.Name.WireValue, unionType.Shape.SingleProperty.Type, t.writer.types, t.alwaysSendRequiredProperties)) case "samePropertiesAsObject": case "noProperties": // For no properties, we always include the omitempty tag. @@ -964,9 +966,9 @@ func (t *typeVisitor) visitObjectProperties( if includeJSONTags { var structTag string if includeURLTags { - structTag = fullFieldTagForType(property.Name.WireValue, property.ValueType, t.writer.types) + structTag = fullFieldTagForType(property.Name.WireValue, property.ValueType, t.writer.types, t.alwaysSendRequiredProperties) } else { - structTag = fullFieldTagForTypeWithIgnoredURL(property.Name.WireValue, property.ValueType, t.writer.types) + structTag = fullFieldTagForTypeWithIgnoredURL(property.Name.WireValue, property.ValueType, t.writer.types, t.alwaysSendRequiredProperties) } t.writer.P(property.Name.Name.PascalCase.UnsafeName, " ", goType, structTag) continue @@ -1408,41 +1410,64 @@ func firstLetterToLower(s string) string { } // fullFieldTagForType returns the JSON struct tag and query URL struct tag for the given type. -func fullFieldTagForType(wireValue string, valueType *ir.TypeReference, types map[ir.TypeId]*ir.TypeDeclaration) string { +func fullFieldTagForType( + wireValue string, + valueType *ir.TypeReference, + types map[ir.TypeId]*ir.TypeDeclaration, + alwaysSendRequiredProperties bool, +) string { return structTagForType( wireValue, valueType, types, []string{"json", "url"}, nil, + alwaysSendRequiredProperties, ) } -func fullFieldTagForTypeWithIgnoredURL(wireValue string, valueType *ir.TypeReference, types map[ir.TypeId]*ir.TypeDeclaration) string { +func fullFieldTagForTypeWithIgnoredURL( + wireValue string, + valueType *ir.TypeReference, + types map[ir.TypeId]*ir.TypeDeclaration, + alwaysSendRequiredProperties bool, +) string { return structTagForType( wireValue, valueType, types, []string{"json"}, []string{"url"}, + alwaysSendRequiredProperties, ) } // jsonTagForType returns the JSON struct tag for the given type. -func jsonTagForType(wireValue string, valueType *ir.TypeReference, types map[ir.TypeId]*ir.TypeDeclaration) string { +func jsonTagForType( + wireValue string, + valueType *ir.TypeReference, + types map[ir.TypeId]*ir.TypeDeclaration, + alwaysSendRequiredProperties bool, +) string { return structTagForType( wireValue, valueType, types, []string{"json"}, nil, + alwaysSendRequiredProperties, ) } // urlTagForType returns the query URL struct tag for the given type. The URL tag // requires special handling because we need to always set the JSON tag to '-'. -func urlTagForType(wireValue string, valueType *ir.TypeReference, types map[ir.TypeId]*ir.TypeDeclaration) string { - tagFormat := tagFormatForType(valueType, types) +func urlTagForType( + wireValue string, + valueType *ir.TypeReference, + types map[ir.TypeId]*ir.TypeDeclaration, + alwaysSendRequiredProperties bool, +) string { + tagFormat := tagFormatForType(valueType, types, alwaysSendRequiredProperties) structTags := []string{ `json:"-"`, } @@ -1462,8 +1487,9 @@ func structTagForType( types map[ir.TypeId]*ir.TypeDeclaration, tags []string, ignoreTags []string, + alwaysSendRequiredProperties bool, ) string { - tagFormat := tagFormatForType(valueType, types) + tagFormat := tagFormatForType(valueType, types, alwaysSendRequiredProperties) var structTags []string for _, tag := range tags { structTags = append(structTags, fmt.Sprintf(tagFormat, tag, wireValue)) @@ -1484,7 +1510,16 @@ func structTagForType( func tagFormatForType( valueType *ir.TypeReference, types map[ir.TypeId]*ir.TypeDeclaration, + alwaysSendRequiredProperties bool, ) string { + if alwaysSendRequiredProperties { + if isOptionalType(valueType, types) { + return `%s:"%s,omitempty"` + } + return "%s:%q" + } + // The following behavior is the legacy behavior of the SDK, i.e. + // we omit values for required objects, lists, and maps. if valueType != nil { primitive := valueType.Primitive if valueType.Named != nil { diff --git a/generators/go/internal/generator/sdk.go b/generators/go/internal/generator/sdk.go index 402cf91270e..5efa2385d24 100644 --- a/generators/go/internal/generator/sdk.go +++ b/generators/go/internal/generator/sdk.go @@ -2444,7 +2444,7 @@ func (f *fileWriter) WriteRequestType( ) continue } - f.P(queryParam.Name.Name.PascalCase.UnsafeName, " ", value, urlTagForType(queryParam.Name.WireValue, queryParam.ValueType, f.types)) + f.P(queryParam.Name.Name.PascalCase.UnsafeName, " ", value, urlTagForType(queryParam.Name.WireValue, queryParam.ValueType, f.types, f.alwaysSendRequiredProperties)) } if endpoint.RequestBody == nil { // If the request doesn't have a body, we don't need any custom [de]serialization logic. diff --git a/generators/go/sdk/CHANGELOG.md b/generators/go/sdk/CHANGELOG.md index 402528695f2..8d449bc421a 100644 --- a/generators/go/sdk/CHANGELOG.md +++ b/generators/go/sdk/CHANGELOG.md @@ -7,6 +7,62 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 +## [0.23.0 - 2024-08-07] + +- Fix: When `alwaysSendRequiredProperties` is enabled, required properties are never omitted in + the type's wire representation. Any required property that is not explicitly set will + send the default value for that type. For example, + + ```yaml + - name: fernapi/fern-go-sdk + version: 0.23.0 + config: + alwaysSendRequiredProperties: true + ``` + + ```go + type CreateUserRequest struct { + Admin *User `json:"admin"` + Members []*User `json:"members"` + } + + type User struct { + Name string `json:"name"` + } + ``` + + If the `CreateUserRequest` is constructed without any values, it will send `null` + for both fields. If it is instead constructed with the zero value of the underlying + type, the empty representation will be sent: + + ```go + // The following is serialized as `{"admin": null, "members": null}` + nullRequest := &CreateUserRequest{} + + // The following is serialized as `{"admin": null, "members": []}` + noMembersRequest := &CreateUserRequest{ + Members: []*User{}, + } + + // The following is serialized as `{"admin":{"name":""}, "members":[]}` + emptyValueRequest := &CreateUserRequest{ + Admin: &User{}, + Members: []*User{}, + } + + // The following is serialized as `{"admin":{"name": "john.doe"}, "members":[{"name": "jane.doe"}]}` + simpleRequest := &CreateUserRequest{ + Admin: &User{ + Name: "john.doe", + }, + Members: []*User{ + { + Name: "jane.doe", + }, + }, + } + ``` + ## [0.22.3 - 2024-07-22] - Fix: Fix an issue where APIs that specify the `property-name` error discrimination strategy would @@ -61,7 +117,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.21.3 - 2024-05-17] -- Internal: The generator now uses the latest FDR SDK. +- Internal: The gnnerator now uses the latest FDR SDK. ## [0.21.2 - 2024-05-07] diff --git a/generators/python/core_utilities/sdk/http_client.py b/generators/python/core_utilities/sdk/http_client.py index 0d4f2d5566a..e8e0f28fae9 100644 --- a/generators/python/core_utilities/sdk/http_client.py +++ b/generators/python/core_utilities/sdk/http_client.py @@ -140,7 +140,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/generators/python/pydantic/CHANGELOG.md b/generators/python/pydantic/CHANGELOG.md index 737a6574c8d..e90584389b3 100644 --- a/generators/python/pydantic/CHANGELOG.md +++ b/generators/python/pydantic/CHANGELOG.md @@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.4.0] - 2024-08-06 + +- Improvement: expose `package_name` configuration option for pydantic models. This field controls the package from which users will import your client, for example, the following config would allow users to use: `from my_custom_package import Client` + ```yaml + generators: + - name: fernapi/fern-pydantic-model + config: + package_name: my_custom_package + ``` + ## [1.3.1] - 2024-08-05 - Fix: The root type for unions with visitors now has it's parent typed correctly. This allows auto-complete to work once again on the union when it's nested within other pydantic models. diff --git a/generators/python/pydantic/VERSION b/generators/python/pydantic/VERSION index 3a3cd8cc8b0..88c5fb891dc 100644 --- a/generators/python/pydantic/VERSION +++ b/generators/python/pydantic/VERSION @@ -1 +1 @@ -1.3.1 +1.4.0 diff --git a/generators/python/sdk/CHANGELOG.md b/generators/python/sdk/CHANGELOG.md index cec1604fb84..37c675101a8 100644 --- a/generators/python/sdk/CHANGELOG.md +++ b/generators/python/sdk/CHANGELOG.md @@ -5,10 +5,35 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [3.6.0] - 2024-08-06 +## [3.7.0] - 2024-08-08 - Improvement: Python circular referencing types are more robust. +## [3.6.0] - 2024-08-08 + +- Feat: The generator now respects returning nested properties, these can be specified via: + + In OpenAPI below, we'd like to only return the property `jobId` from the `Job` object we get back from our server to our SDK users: + ```yaml + my/endpoint: + get: + x-fern-sdk-return-value: jobId + response: Job + ``` + For a similar situation using the Fern definition: + ```yaml + endpoints: + getJob: + method: GET + path: my/endpoint + response: + type: Job + property: jobId + ``` + + +- Fix: The underlying content no longer sends empty JSON bodies, instead it'll pass a `None` value to httpx + ## [3.5.1] - 2024-08-05 - Fix: The root type for unions with visitors now has it's parent typed correctly. This allows auto-complete to work once again on the union when it's nested within other pydantic models. diff --git a/generators/python/src/fern_python/cli/abstract_generator.py b/generators/python/src/fern_python/cli/abstract_generator.py index d36943c547f..cfd5de19c01 100644 --- a/generators/python/src/fern_python/cli/abstract_generator.py +++ b/generators/python/src/fern_python/cli/abstract_generator.py @@ -1,6 +1,7 @@ from __future__ import annotations import os +import re from abc import ABC, abstractmethod from typing import Literal, Optional, Sequence, Tuple, cast @@ -91,6 +92,13 @@ def generate_project( ), ) + # We're trying not to change the casing more than we need to, so here + # we're using the same casing as is given but just removing `-` and other special characters as + # python does not allow `-` in package names. Note pypi should be fine with it + def _clean_organization_name(self, organization: str) -> str: + # Replace non-alphanumeric characters with underscores + return re.sub("[^a-zA-Z0-9]", "_", organization) + def _get_github_publish_config( self, generator_config: GeneratorConfig, output_mode: GithubOutputMode ) -> ProjectConfig: diff --git a/generators/python/src/fern_python/generators/fastapi/custom_config.py b/generators/python/src/fern_python/generators/fastapi/custom_config.py index d09c98db02f..c94c7960765 100644 --- a/generators/python/src/fern_python/generators/fastapi/custom_config.py +++ b/generators/python/src/fern_python/generators/fastapi/custom_config.py @@ -16,3 +16,6 @@ class FastAPICustomConfig(pydantic.BaseModel): skip_formatting: bool = False async_handlers: Union[bool, List[str]] = False pydantic_config: FastApiPydanticModelCustomConfig = FastApiPydanticModelCustomConfig() + + class Config: + extra = pydantic.Extra.forbid diff --git a/generators/python/src/fern_python/generators/pydantic_model/custom_config.py b/generators/python/src/fern_python/generators/pydantic_model/custom_config.py index 402e550139d..bc205182e22 100644 --- a/generators/python/src/fern_python/generators/pydantic_model/custom_config.py +++ b/generators/python/src/fern_python/generators/pydantic_model/custom_config.py @@ -37,6 +37,7 @@ class PydanticModelCustomConfig(BasePydanticModelCustomConfig): extra_fields: Optional[Literal["allow", "forbid"]] = "allow" skip_formatting: bool = False include_union_utils: bool = False + package_name: Optional[str] = None # Skip validation of fields (automatically includes additional fields) skip_validation: bool = False # Leverage defaults specified in the API specification @@ -45,3 +46,6 @@ class PydanticModelCustomConfig(BasePydanticModelCustomConfig): # Whether or not to generate TypedDicts instead of Pydantic # Models for request objects. use_typeddict_requests: bool = False + + class Config: + extra = pydantic.Extra.forbid diff --git a/generators/python/src/fern_python/generators/pydantic_model/pydantic_model_generator.py b/generators/python/src/fern_python/generators/pydantic_model/pydantic_model_generator.py index e283235fe01..e14457b5def 100644 --- a/generators/python/src/fern_python/generators/pydantic_model/pydantic_model_generator.py +++ b/generators/python/src/fern_python/generators/pydantic_model/pydantic_model_generator.py @@ -37,8 +37,13 @@ def get_relative_path_to_project_for_publish( generator_config: GeneratorConfig, ir: ir_types.IntermediateRepresentation, ) -> Tuple[str, ...]: + custom_config = PydanticModelCustomConfig.parse_obj(generator_config.custom_config or {}) + if custom_config.package_name is not None: + return (custom_config.package_name,) + + cleaned_org_name = self._clean_organization_name(generator_config.organization) return ( - generator_config.organization, + cleaned_org_name, ir.api_name.snake_case.safe_name, ) diff --git a/generators/python/src/fern_python/generators/sdk/client_generator/endpoint_function_generator.py b/generators/python/src/fern_python/generators/sdk/client_generator/endpoint_function_generator.py index 4571c83b352..d05c225b6b0 100644 --- a/generators/python/src/fern_python/generators/sdk/client_generator/endpoint_function_generator.py +++ b/generators/python/src/fern_python/generators/sdk/client_generator/endpoint_function_generator.py @@ -2,7 +2,6 @@ from typing import Dict, List, Optional, Set, Tuple, Union import fern.ir.resources as ir_types -from typing_extensions import Never from fern_python.codegen import AST from fern_python.codegen.ast.ast_node.node_writer import NodeWriter @@ -1020,9 +1019,25 @@ def _get_json_response_body_type( response=lambda response: self._context.pydantic_generator_context.get_type_hint_for_type_reference( response.response_body_type, ), - nested_property_as_response=lambda _: raise_json_nested_property_as_response_unsupported(), + # TODO: What is the case where you have a nested property as response, but no response property configured? + nested_property_as_response=lambda response: self._get_nested_json_response_type(response), ) + def _get_nested_json_response_type(self, response: ir_types.JsonResponseBodyWithProperty) -> AST.TypeHint: + response_type = self._context.pydantic_generator_context.get_type_hint_for_type_reference( + response.response_body_type + ) + property_type = self._context.pydantic_generator_context.get_type_hint_for_type_reference( + response.response_property.value_type + if response.response_property is not None + else response.response_body_type + ) + + if response_type.is_optional and not property_type.is_optional: + return AST.TypeHint.optional(property_type) + + return property_type + def _get_streaming_response_body_type( self, *, stream_response: ir_types.StreamingResponse, is_async: bool ) -> AST.TypeHint: @@ -1661,7 +1676,3 @@ def unwrap_optional_type(type_reference: ir_types.TypeReference) -> ir_types.Typ if container_as_union.type == "optional": return unwrap_optional_type(container_as_union.optional) return type_reference - - -def raise_json_nested_property_as_response_unsupported() -> Never: - raise RuntimeError("nested property json response is unsupported") diff --git a/generators/python/src/fern_python/generators/sdk/client_generator/endpoint_response_code_writer.py b/generators/python/src/fern_python/generators/sdk/client_generator/endpoint_response_code_writer.py index 9bf3750465c..3f03fc10aee 100644 --- a/generators/python/src/fern_python/generators/sdk/client_generator/endpoint_response_code_writer.py +++ b/generators/python/src/fern_python/generators/sdk/client_generator/endpoint_response_code_writer.py @@ -1,7 +1,7 @@ from typing import Callable, Optional +from urllib import response import fern.ir.resources as ir_types -from typing_extensions import Never from fern_python.codegen import AST from fern_python.external_dependencies.httpx_sse import HttpxSSE @@ -158,6 +158,50 @@ def _handle_success_json( else f"{EndpointResponseCodeWriter.RESPONSE_VARIABLE}.json()" ), ) + + # Validation rules limit the type of the response object to be either + # an object or optional object + property_access_expression: Optional[AST.Expression] = None + response_union = json_response.get_as_union() + if response_union.type == "nestedPropertyAsResponse": + response_body: ir_types.TypeReference = response_union.response_body_type + response_body_union = response_body.get_as_union() + response_property = ( + response_union.response_property.name.name.snake_case.safe_name + if response_union.response_property is not None + else None + ) + if response_body_union.type == "container": + response_container = response_body_union.container.get_as_union() + if response_container.type == "optional" and response_property is not None: + property_access_expression = AST.Expression( + f"{EndpointResponseCodeWriter.PARSED_RESPONSE_VARIABLE}.{response_property} if {EndpointResponseCodeWriter.PARSED_RESPONSE_VARIABLE} is not None else {EndpointResponseCodeWriter.PARSED_RESPONSE_VARIABLE}" + ) + elif response_body_union.type == "named": + response_named = self._context.pydantic_generator_context.get_declaration_for_type_id( + response_body_union.type_id + ) + property_access_expression = response_named.shape.visit( + object=lambda _: AST.Expression( + f"{EndpointResponseCodeWriter.PARSED_RESPONSE_VARIABLE}.{response_property}" + ), + alias=lambda _: None, + enum=lambda _: None, + union=lambda _: None, + undiscriminated_union=lambda _: None, + ) + + if property_access_expression is not None: + # If you are indeed accessing a property, set the parsed response to an intermediate variable + writer.write_node( + AST.VariableDeclaration( + name=EndpointResponseCodeWriter.PARSED_RESPONSE_VARIABLE, initializer=pydantic_parse_expression + ) + ) + + # Then use the property accessed expression moving forward + pydantic_parse_expression = property_access_expression + if self._pagination is not None: paginator = self._pagination.visit( cursor=lambda cursor: CursorPagination( @@ -404,7 +448,10 @@ def _get_json_response_body_type( response=lambda response: self._context.pydantic_generator_context.get_type_hint_for_type_reference( response.response_body_type ), - nested_property_as_response=lambda _: raise_json_nested_property_as_response_unsupported(), + # TODO: What is the case where you have a nested property as response, but no response property configured? + nested_property_as_response=lambda response: self._context.pydantic_generator_context.get_type_hint_for_type_reference( + response.response_body_type + ), ) def _get_streaming_response_data_type(self, streaming_response: ir_types.StreamingResponse) -> AST.TypeHint: @@ -416,7 +463,3 @@ def _get_streaming_response_data_type(self, streaming_response: ir_types.Streami if union.type == "text": return AST.TypeHint.str_() raise RuntimeError(f"{union.type} streaming response is unsupported") - - -def raise_json_nested_property_as_response_unsupported() -> Never: - raise RuntimeError("nested property json response is unsupported") diff --git a/generators/python/src/fern_python/generators/sdk/sdk_generator.py b/generators/python/src/fern_python/generators/sdk/sdk_generator.py index b31eadad5a7..7eb89e32429 100644 --- a/generators/python/src/fern_python/generators/sdk/sdk_generator.py +++ b/generators/python/src/fern_python/generators/sdk/sdk_generator.py @@ -67,13 +67,15 @@ def get_relative_path_to_project_for_publish( custom_config = SDKCustomConfig.parse_obj(generator_config.custom_config or {}) if custom_config.package_name is not None: return (custom_config.package_name,) + + cleaned_org_name = self._clean_organization_name(generator_config.organization) return ( ( - generator_config.organization, + cleaned_org_name, ir.api_name.snake_case.safe_name, ) if custom_config.use_api_name_in_package - else (generator_config.organization,) + else (cleaned_org_name,) ) def run( diff --git a/generators/python/src/fern_python/snippet/snippet_test_factory.py b/generators/python/src/fern_python/snippet/snippet_test_factory.py index 23f0554cf22..2d0dac9616a 100644 --- a/generators/python/src/fern_python/snippet/snippet_test_factory.py +++ b/generators/python/src/fern_python/snippet/snippet_test_factory.py @@ -462,6 +462,15 @@ def _generate_service_test(self, service: ir_types.HttpService, snippet_writer: or endpoint.request_body.get_as_union().type == "bytes" ) ) + # TODO(FER-2852): support test generation for nested property responses + or ( + endpoint.response is not None + and endpoint.response.body + and ( + endpoint.response.body.get_as_union().type == "json" + and endpoint.response.body.get_as_union().value.get_as_union().type == "nestedPropertyAsResponse" # type: ignore + ) + ) ): continue endpoint_name = endpoint.name.snake_case.safe_name diff --git a/generators/python/tests/utils/test_http_client.py b/generators/python/tests/utils/test_http_client.py index 58feee97c16..670e0f2ad1b 100644 --- a/generators/python/tests/utils/test_http_client.py +++ b/generators/python/tests/utils/test_http_client.py @@ -63,3 +63,24 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body( + json=None, + data=None, + request_options=unrelated_request_options, + omit=None + ) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, + data=None, + request_options=unrelated_request_options, + omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None \ No newline at end of file diff --git a/generators/typescript/express/cli/package.json b/generators/typescript/express/cli/package.json index 2967caac7c8..93f16ce3856 100644 --- a/generators/typescript/express/cli/package.json +++ b/generators/typescript/express/cli/package.json @@ -30,7 +30,7 @@ }, "devDependencies": { "@fern-fern/generator-exec-sdk": "^0.0.898", - "@fern-fern/ir-sdk": "53.1.0", + "@fern-fern/ir-sdk": "53.4.0", "@fern-typescript/abstract-generator-cli": "workspace:*", "@fern-typescript/commons": "workspace:*", "@fern-typescript/contexts": "workspace:*", diff --git a/generators/typescript/express/express-endpoint-type-schemas-generator/package.json b/generators/typescript/express/express-endpoint-type-schemas-generator/package.json index ec1ae72c3f5..c65c793026b 100644 --- a/generators/typescript/express/express-endpoint-type-schemas-generator/package.json +++ b/generators/typescript/express/express-endpoint-type-schemas-generator/package.json @@ -27,7 +27,7 @@ }, "dependencies": { "@fern-api/core-utils": "workspace:*", - "@fern-fern/ir-sdk": "53.1.0", + "@fern-fern/ir-sdk": "53.4.0", "@fern-typescript/abstract-schema-generator": "workspace:*", "@fern-typescript/commons": "workspace:*", "@fern-typescript/contexts": "workspace:*", diff --git a/generators/typescript/express/express-error-generator/package.json b/generators/typescript/express/express-error-generator/package.json index 1993fb68151..e4f376dc243 100644 --- a/generators/typescript/express/express-error-generator/package.json +++ b/generators/typescript/express/express-error-generator/package.json @@ -26,7 +26,7 @@ "depcheck": "depcheck" }, "dependencies": { - "@fern-fern/ir-sdk": "53.1.0", + "@fern-fern/ir-sdk": "53.4.0", "@fern-typescript/abstract-error-class-generator": "workspace:*", "@fern-typescript/commons": "workspace:*", "@fern-typescript/contexts": "workspace:*", diff --git a/generators/typescript/express/express-error-schema-generator/package.json b/generators/typescript/express/express-error-schema-generator/package.json index bc466f98056..256c47c8df2 100644 --- a/generators/typescript/express/express-error-schema-generator/package.json +++ b/generators/typescript/express/express-error-schema-generator/package.json @@ -27,7 +27,7 @@ }, "dependencies": { "@fern-api/core-utils": "workspace:*", - "@fern-fern/ir-sdk": "53.1.0", + "@fern-fern/ir-sdk": "53.4.0", "@fern-typescript/abstract-schema-generator": "workspace:*", "@fern-typescript/commons": "workspace:*", "@fern-typescript/contexts": "workspace:*", diff --git a/generators/typescript/express/express-inlined-request-body-generator/package.json b/generators/typescript/express/express-inlined-request-body-generator/package.json index d894b2c134b..261a4f0a880 100644 --- a/generators/typescript/express/express-inlined-request-body-generator/package.json +++ b/generators/typescript/express/express-inlined-request-body-generator/package.json @@ -26,7 +26,7 @@ "depcheck": "depcheck" }, "dependencies": { - "@fern-fern/ir-sdk": "53.1.0", + "@fern-fern/ir-sdk": "53.4.0", "@fern-typescript/commons": "workspace:*", "@fern-typescript/contexts": "workspace:*" }, diff --git a/generators/typescript/express/express-inlined-request-body-schema-generator/package.json b/generators/typescript/express/express-inlined-request-body-schema-generator/package.json index d7a32412075..2f8838dbcc6 100644 --- a/generators/typescript/express/express-inlined-request-body-schema-generator/package.json +++ b/generators/typescript/express/express-inlined-request-body-schema-generator/package.json @@ -26,7 +26,7 @@ "depcheck": "depcheck" }, "dependencies": { - "@fern-fern/ir-sdk": "53.1.0", + "@fern-fern/ir-sdk": "53.4.0", "@fern-typescript/abstract-schema-generator": "workspace:*", "@fern-typescript/commons": "workspace:*", "@fern-typescript/contexts": "workspace:*", diff --git a/generators/typescript/express/express-register-generator/package.json b/generators/typescript/express/express-register-generator/package.json index 981c0b2c10c..0db64d01366 100644 --- a/generators/typescript/express/express-register-generator/package.json +++ b/generators/typescript/express/express-register-generator/package.json @@ -26,7 +26,7 @@ "depcheck": "depcheck" }, "dependencies": { - "@fern-fern/ir-sdk": "53.1.0", + "@fern-fern/ir-sdk": "53.4.0", "@fern-typescript/commons": "workspace:*", "@fern-typescript/contexts": "workspace:*", "@fern-typescript/resolvers": "workspace:*", diff --git a/generators/typescript/express/express-service-generator/package.json b/generators/typescript/express/express-service-generator/package.json index 052459b3abe..ee81b2cbe6c 100644 --- a/generators/typescript/express/express-service-generator/package.json +++ b/generators/typescript/express/express-service-generator/package.json @@ -26,7 +26,7 @@ "depcheck": "depcheck" }, "dependencies": { - "@fern-fern/ir-sdk": "53.1.0", + "@fern-fern/ir-sdk": "53.4.0", "@fern-typescript/commons": "workspace:*", "@fern-typescript/contexts": "workspace:*", "@fern-typescript/resolvers": "workspace:*", diff --git a/generators/typescript/express/generator/package.json b/generators/typescript/express/generator/package.json index 9b68b96c556..53d94e83e39 100644 --- a/generators/typescript/express/generator/package.json +++ b/generators/typescript/express/generator/package.json @@ -28,7 +28,7 @@ "dependencies": { "@fern-api/core-utils": "workspace:*", "@fern-api/fs-utils": "workspace:*", - "@fern-fern/ir-sdk": "53.1.0", + "@fern-fern/ir-sdk": "53.4.0", "@fern-typescript/commons": "workspace:*", "@fern-typescript/contexts": "workspace:*", "@fern-typescript/express-endpoint-type-schemas-generator": "workspace:*", diff --git a/generators/typescript/model/type-generator/package.json b/generators/typescript/model/type-generator/package.json index d4bbf314243..7a1799af151 100644 --- a/generators/typescript/model/type-generator/package.json +++ b/generators/typescript/model/type-generator/package.json @@ -26,7 +26,7 @@ "depcheck": "depcheck" }, "dependencies": { - "@fern-fern/ir-sdk": "53.1.0", + "@fern-fern/ir-sdk": "53.4.0", "@fern-typescript/commons": "workspace:*", "@fern-typescript/contexts": "workspace:*", "@fern-typescript/union-generator": "workspace:*", diff --git a/generators/typescript/model/type-reference-converters/package.json b/generators/typescript/model/type-reference-converters/package.json index 42b4d7db685..69d77842d86 100644 --- a/generators/typescript/model/type-reference-converters/package.json +++ b/generators/typescript/model/type-reference-converters/package.json @@ -26,7 +26,7 @@ "depcheck": "depcheck" }, "dependencies": { - "@fern-fern/ir-sdk": "53.1.0", + "@fern-fern/ir-sdk": "53.4.0", "@fern-typescript/commons": "workspace:*", "@fern-typescript/resolvers": "workspace:*", "ts-morph": "^15.1.0" diff --git a/generators/typescript/model/type-reference-example-generator/package.json b/generators/typescript/model/type-reference-example-generator/package.json index 09233024639..5d0c4b780de 100644 --- a/generators/typescript/model/type-reference-example-generator/package.json +++ b/generators/typescript/model/type-reference-example-generator/package.json @@ -27,7 +27,7 @@ }, "dependencies": { "@fern-api/core-utils": "workspace:*", - "@fern-fern/ir-sdk": "53.1.0", + "@fern-fern/ir-sdk": "53.4.0", "@fern-typescript/commons": "workspace:*", "@fern-typescript/contexts": "workspace:*", "ts-morph": "^15.1.0" diff --git a/generators/typescript/model/type-schema-generator/package.json b/generators/typescript/model/type-schema-generator/package.json index 248b3ebaa04..0e167e5f075 100644 --- a/generators/typescript/model/type-schema-generator/package.json +++ b/generators/typescript/model/type-schema-generator/package.json @@ -26,7 +26,7 @@ "depcheck": "depcheck" }, "dependencies": { - "@fern-fern/ir-sdk": "53.1.0", + "@fern-fern/ir-sdk": "53.4.0", "@fern-typescript/abstract-schema-generator": "workspace:*", "@fern-typescript/commons": "workspace:*", "@fern-typescript/contexts": "workspace:*", diff --git a/generators/typescript/model/union-generator/package.json b/generators/typescript/model/union-generator/package.json index f2135bf3f46..620e9250d51 100644 --- a/generators/typescript/model/union-generator/package.json +++ b/generators/typescript/model/union-generator/package.json @@ -27,7 +27,7 @@ }, "dependencies": { "@fern-api/core-utils": "workspace:*", - "@fern-fern/ir-sdk": "53.1.0", + "@fern-fern/ir-sdk": "53.4.0", "@fern-typescript/commons": "workspace:*", "@fern-typescript/contexts": "workspace:*", "ts-morph": "^15.1.0" diff --git a/generators/typescript/model/union-schema-generator/package.json b/generators/typescript/model/union-schema-generator/package.json index e50d8296045..c55fc658afd 100644 --- a/generators/typescript/model/union-schema-generator/package.json +++ b/generators/typescript/model/union-schema-generator/package.json @@ -26,7 +26,7 @@ "depcheck": "depcheck" }, "dependencies": { - "@fern-fern/ir-sdk": "53.1.0", + "@fern-fern/ir-sdk": "53.4.0", "@fern-typescript/abstract-schema-generator": "workspace:*", "@fern-typescript/commons": "workspace:*", "@fern-typescript/contexts": "workspace:*", diff --git a/generators/typescript/sdk/CHANGELOG.md b/generators/typescript/sdk/CHANGELOG.md index 9fa5feb12c8..55cf6066e53 100644 --- a/generators/typescript/sdk/CHANGELOG.md +++ b/generators/typescript/sdk/CHANGELOG.md @@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.38.6] - 2024-08-07 + +- Feature: The SDK generator now sends a `User-Agent` header on each request that is set to + `/`. For example if your package is called `imdb` and is versioned `0.1.0`, then + the user agent header will be `imdb/0.1.0`. + +## [0.38.5] - 2024-08-07 + +- Fix: Addressed fetcher unit test flakiness by using a mock fetcher + ## [0.38.4] - 2024-08-04 - Fix: Literal templates are generated if they are union members diff --git a/generators/typescript/sdk/VERSION b/generators/typescript/sdk/VERSION index 87f0b954e35..ba79df13677 100644 --- a/generators/typescript/sdk/VERSION +++ b/generators/typescript/sdk/VERSION @@ -1 +1 @@ -0.38.4 +0.38.6 diff --git a/generators/typescript/sdk/cli/package.json b/generators/typescript/sdk/cli/package.json index a7c581fe1b3..52435f730fa 100644 --- a/generators/typescript/sdk/cli/package.json +++ b/generators/typescript/sdk/cli/package.json @@ -34,7 +34,7 @@ "devDependencies": { "@fern-api/fs-utils": "workspace:*", "@fern-api/generator-commons": "workspace:*", - "@fern-fern/ir-sdk": "53.1.0", + "@fern-fern/ir-sdk": "53.4.0", "@fern-typescript/abstract-generator-cli": "workspace:*", "@fern-typescript/commons": "workspace:*", "@fern-typescript/contexts": "workspace:*", diff --git a/generators/typescript/sdk/client-class-generator/package.json b/generators/typescript/sdk/client-class-generator/package.json index 542a633c315..ab1cc80f0da 100644 --- a/generators/typescript/sdk/client-class-generator/package.json +++ b/generators/typescript/sdk/client-class-generator/package.json @@ -27,7 +27,7 @@ }, "dependencies": { "@fern-api/core-utils": "workspace:*", - "@fern-fern/ir-sdk": "53.1.0", + "@fern-fern/ir-sdk": "53.4.0", "@fern-typescript/commons": "workspace:*", "@fern-typescript/contexts": "workspace:*", "@fern-typescript/resolvers": "workspace:*", diff --git a/generators/typescript/sdk/client-class-generator/src/GeneratedSdkClientClassImpl.ts b/generators/typescript/sdk/client-class-generator/src/GeneratedSdkClientClassImpl.ts index 719b175af20..3d131d39b34 100644 --- a/generators/typescript/sdk/client-class-generator/src/GeneratedSdkClientClassImpl.ts +++ b/generators/typescript/sdk/client-class-generator/src/GeneratedSdkClientClassImpl.ts @@ -814,6 +814,13 @@ export class GeneratedSdkClientClassImpl implements GeneratedSdkClientClass { ); } + if (context.ir.sdkConfig.platformHeaders.userAgent != null) { + headers.push({ + header: context.ir.sdkConfig.platformHeaders.userAgent.header, + value: ts.factory.createStringLiteral(context.ir.sdkConfig.platformHeaders.userAgent.value) + }); + } + const generatedVersion = context.version.getGeneratedVersion(); if (generatedVersion != null) { const header = generatedVersion.getHeader(); diff --git a/generators/typescript/sdk/endpoint-error-union-generator/package.json b/generators/typescript/sdk/endpoint-error-union-generator/package.json index 031148ee459..edd976a3be2 100644 --- a/generators/typescript/sdk/endpoint-error-union-generator/package.json +++ b/generators/typescript/sdk/endpoint-error-union-generator/package.json @@ -26,7 +26,7 @@ "depcheck": "depcheck" }, "dependencies": { - "@fern-fern/ir-sdk": "53.1.0", + "@fern-fern/ir-sdk": "53.4.0", "@fern-typescript/commons": "workspace:*", "@fern-typescript/contexts": "workspace:*", "@fern-typescript/resolvers": "workspace:*", diff --git a/generators/typescript/sdk/environments-generator/package.json b/generators/typescript/sdk/environments-generator/package.json index bcc75c637b6..a6ac2ddc4c4 100644 --- a/generators/typescript/sdk/environments-generator/package.json +++ b/generators/typescript/sdk/environments-generator/package.json @@ -26,7 +26,7 @@ "depcheck": "depcheck" }, "dependencies": { - "@fern-fern/ir-sdk": "53.1.0", + "@fern-fern/ir-sdk": "53.4.0", "@fern-typescript/commons": "workspace:*", "@fern-typescript/contexts": "workspace:*", "ts-morph": "^15.1.0" diff --git a/generators/typescript/sdk/generator/package.json b/generators/typescript/sdk/generator/package.json index 1fc19ab2ca9..2d7035dc51b 100644 --- a/generators/typescript/sdk/generator/package.json +++ b/generators/typescript/sdk/generator/package.json @@ -32,7 +32,7 @@ "@fern-api/logging-execa": "workspace:*", "@fern-fern/generator-cli-sdk": "0.0.56", "@fern-fern/generator-exec-sdk": "^0.0.898", - "@fern-fern/ir-sdk": "53.1.0", + "@fern-fern/ir-sdk": "53.4.0", "@fern-fern/snippet-sdk": "^0.0.5526", "@fern-typescript/commons": "workspace:*", "@fern-typescript/contexts": "workspace:*", diff --git a/generators/typescript/sdk/request-wrapper-generator/package.json b/generators/typescript/sdk/request-wrapper-generator/package.json index 617fcb3064f..53c0ec46aaf 100644 --- a/generators/typescript/sdk/request-wrapper-generator/package.json +++ b/generators/typescript/sdk/request-wrapper-generator/package.json @@ -27,7 +27,7 @@ }, "dependencies": { "@fern-api/core-utils": "workspace:*", - "@fern-fern/ir-sdk": "53.1.0", + "@fern-fern/ir-sdk": "53.4.0", "@fern-typescript/commons": "workspace:*", "@fern-typescript/contexts": "workspace:*", "ts-morph": "^15.1.0" diff --git a/generators/typescript/sdk/sdk-endpoint-type-schemas-generator/package.json b/generators/typescript/sdk/sdk-endpoint-type-schemas-generator/package.json index aff2db1934c..fa5058c5139 100644 --- a/generators/typescript/sdk/sdk-endpoint-type-schemas-generator/package.json +++ b/generators/typescript/sdk/sdk-endpoint-type-schemas-generator/package.json @@ -27,7 +27,7 @@ }, "dependencies": { "@fern-api/core-utils": "workspace:*", - "@fern-fern/ir-sdk": "53.1.0", + "@fern-fern/ir-sdk": "53.4.0", "@fern-typescript/abstract-schema-generator": "workspace:*", "@fern-typescript/commons": "workspace:*", "@fern-typescript/contexts": "workspace:*", diff --git a/generators/typescript/sdk/sdk-error-generator/package.json b/generators/typescript/sdk/sdk-error-generator/package.json index 7caaf650098..0a3b99b51b1 100644 --- a/generators/typescript/sdk/sdk-error-generator/package.json +++ b/generators/typescript/sdk/sdk-error-generator/package.json @@ -26,7 +26,7 @@ "depcheck": "depcheck" }, "dependencies": { - "@fern-fern/ir-sdk": "53.1.0", + "@fern-fern/ir-sdk": "53.4.0", "@fern-typescript/abstract-error-class-generator": "workspace:*", "@fern-typescript/commons": "workspace:*", "@fern-typescript/contexts": "workspace:*", diff --git a/generators/typescript/sdk/sdk-error-schema-generator/package.json b/generators/typescript/sdk/sdk-error-schema-generator/package.json index e07955ac849..534a1d2a077 100644 --- a/generators/typescript/sdk/sdk-error-schema-generator/package.json +++ b/generators/typescript/sdk/sdk-error-schema-generator/package.json @@ -27,7 +27,7 @@ }, "dependencies": { "@fern-api/core-utils": "workspace:*", - "@fern-fern/ir-sdk": "53.1.0", + "@fern-fern/ir-sdk": "53.4.0", "@fern-typescript/abstract-schema-generator": "workspace:*", "@fern-typescript/commons": "workspace:*", "@fern-typescript/contexts": "workspace:*", diff --git a/generators/typescript/sdk/sdk-inlined-request-body-schema-generator/package.json b/generators/typescript/sdk/sdk-inlined-request-body-schema-generator/package.json index f0033f92a81..0146e75ea08 100644 --- a/generators/typescript/sdk/sdk-inlined-request-body-schema-generator/package.json +++ b/generators/typescript/sdk/sdk-inlined-request-body-schema-generator/package.json @@ -26,7 +26,7 @@ "depcheck": "depcheck" }, "dependencies": { - "@fern-fern/ir-sdk": "53.1.0", + "@fern-fern/ir-sdk": "53.4.0", "@fern-typescript/abstract-schema-generator": "workspace:*", "@fern-typescript/commons": "workspace:*", "@fern-typescript/contexts": "workspace:*", diff --git a/generators/typescript/utils/abstract-generator-cli/package.json b/generators/typescript/utils/abstract-generator-cli/package.json index 38815c94365..37301142a7d 100644 --- a/generators/typescript/utils/abstract-generator-cli/package.json +++ b/generators/typescript/utils/abstract-generator-cli/package.json @@ -30,7 +30,7 @@ "@fern-api/generator-commons": "workspace:*", "@fern-api/logger": "workspace:*", "@fern-fern/generator-exec-sdk": "^0.0.898", - "@fern-fern/ir-sdk": "53.1.0", + "@fern-fern/ir-sdk": "53.4.0", "@fern-typescript/commons": "workspace:*", "@fern-typescript/contexts": "workspace:*" }, diff --git a/generators/typescript/utils/commons/package.json b/generators/typescript/utils/commons/package.json index af3b13fdf23..ae22ea0ab3c 100644 --- a/generators/typescript/utils/commons/package.json +++ b/generators/typescript/utils/commons/package.json @@ -30,7 +30,7 @@ "@fern-api/fs-utils": "workspace:*", "@fern-api/logger": "workspace:*", "@fern-api/logging-execa": "workspace:*", - "@fern-fern/ir-sdk": "53.1.0", + "@fern-fern/ir-sdk": "53.4.0", "@fern-typescript/fetcher": "workspace:*", "@fern-typescript/zurg": "workspace:*", "decompress": "^4.2.1", diff --git a/generators/typescript/utils/commons/src/core-utilities/fetcher/FetcherImpl.ts b/generators/typescript/utils/commons/src/core-utilities/fetcher/FetcherImpl.ts index cf0deb2b3a4..daeb30bdf24 100644 --- a/generators/typescript/utils/commons/src/core-utilities/fetcher/FetcherImpl.ts +++ b/generators/typescript/utils/commons/src/core-utilities/fetcher/FetcherImpl.ts @@ -47,6 +47,9 @@ export class FetcherImpl extends CoreUtility implements Fetcher { dependencyManager.addDependency("@types/node-fetch", "2.6.9", { type: DependencyType.DEV }); + dependencyManager.addDependency("fetch-mock-jest", "^1.5.1", { + type: DependencyType.DEV + }); } }; public readonly Fetcher: Fetcher["Fetcher"] = { diff --git a/generators/typescript/utils/contexts/package.json b/generators/typescript/utils/contexts/package.json index c1930480417..246c19e811c 100644 --- a/generators/typescript/utils/contexts/package.json +++ b/generators/typescript/utils/contexts/package.json @@ -28,7 +28,7 @@ "dependencies": { "@fern-api/logger": "workspace:*", "@fern-fern/generator-exec-sdk": "^0.0.898", - "@fern-fern/ir-sdk": "53.1.0", + "@fern-fern/ir-sdk": "53.4.0", "@fern-typescript/commons": "workspace:*", "ts-morph": "^15.1.0" }, diff --git a/generators/typescript/utils/core-utilities/fetcher/package.json b/generators/typescript/utils/core-utilities/fetcher/package.json index 650f84b5a6d..d0174a2bb9f 100644 --- a/generators/typescript/utils/core-utilities/fetcher/package.json +++ b/generators/typescript/utils/core-utilities/fetcher/package.json @@ -43,6 +43,7 @@ "depcheck": "^1.4.6", "eslint": "^8.56.0", "express": "^4.19.2", + "fetch-mock-jest": "^1.5.1", "form-data": "4.0.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", diff --git a/generators/typescript/utils/core-utilities/fetcher/src/fetcher/__test__/Fetcher.test.ts b/generators/typescript/utils/core-utilities/fetcher/src/fetcher/__test__/Fetcher.test.ts index f2b324666a2..c40ccfa03f4 100644 --- a/generators/typescript/utils/core-utilities/fetcher/src/fetcher/__test__/Fetcher.test.ts +++ b/generators/typescript/utils/core-utilities/fetcher/src/fetcher/__test__/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json" }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }) + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/generators/typescript/utils/resolvers/package.json b/generators/typescript/utils/resolvers/package.json index dff85f70a68..48a94c77062 100644 --- a/generators/typescript/utils/resolvers/package.json +++ b/generators/typescript/utils/resolvers/package.json @@ -26,7 +26,7 @@ "depcheck": "depcheck" }, "dependencies": { - "@fern-fern/ir-sdk": "53.1.0", + "@fern-fern/ir-sdk": "53.4.0", "@fern-typescript/commons": "workspace:*" }, "devDependencies": { diff --git a/packages/cli/cli/src/commands/generate-ir/generateIrForFernWorkspace.ts b/packages/cli/cli/src/commands/generate-ir/generateIrForFernWorkspace.ts index 4aff85d8502..a2aa45e40b4 100644 --- a/packages/cli/cli/src/commands/generate-ir/generateIrForFernWorkspace.ts +++ b/packages/cli/cli/src/commands/generate-ir/generateIrForFernWorkspace.ts @@ -32,6 +32,8 @@ export async function generateIrForFernWorkspace({ smartCasing, disableExamples, audiences, - readme + readme, + version: undefined, + packageName: undefined }); } diff --git a/packages/cli/cli/src/commands/generate-openapi-ir/generateOpenAPIIrForWorkspaces.ts b/packages/cli/cli/src/commands/generate-openapi-ir/generateOpenAPIIrForWorkspaces.ts index 6ce847011d5..18b292ab1a7 100644 --- a/packages/cli/cli/src/commands/generate-openapi-ir/generateOpenAPIIrForWorkspaces.ts +++ b/packages/cli/cli/src/commands/generate-openapi-ir/generateOpenAPIIrForWorkspaces.ts @@ -3,7 +3,7 @@ import { AbsoluteFilePath, stringifyLargeObject } from "@fern-api/fs-utils"; import { serialization } from "@fern-api/openapi-ir-sdk"; import { parse } from "@fern-api/openapi-parser"; import { Project } from "@fern-api/project-loader"; -import { LazyFernWorkspace } from "@fern-api/workspace-loader"; +import { getAllOpenAPISpecs, LazyFernWorkspace } from "@fern-api/workspace-loader"; import { writeFile } from "fs/promises"; import path from "path"; import { CliContext } from "../../cli-context/CliContext"; @@ -26,8 +26,9 @@ export async function generateOpenAPIIrForWorkspaces({ context.logger.info("Skipping, API is specified as a Fern Definition."); return; } + const openAPISpecs = await getAllOpenAPISpecs({ context, specs: workspace.specs }); const openAPIIr = await parse({ - specs: workspace.specs, + specs: openAPISpecs, taskContext: context }); diff --git a/packages/cli/cli/src/commands/generate-overrides/writeOverridesForWorkspaces.ts b/packages/cli/cli/src/commands/generate-overrides/writeOverridesForWorkspaces.ts index 2eabd8f8337..b21c2bf2c63 100644 --- a/packages/cli/cli/src/commands/generate-overrides/writeOverridesForWorkspaces.ts +++ b/packages/cli/cli/src/commands/generate-overrides/writeOverridesForWorkspaces.ts @@ -3,7 +3,7 @@ import { getEndpointLocation } from "@fern-api/openapi-ir-to-fern"; import { parse } from "@fern-api/openapi-parser"; import { Project } from "@fern-api/project-loader"; import { TaskContext } from "@fern-api/task-context"; -import { OSSWorkspace } from "@fern-api/workspace-loader"; +import { getAllOpenAPISpecs, OSSWorkspace } from "@fern-api/workspace-loader"; import { readFile, writeFile } from "fs/promises"; import yaml from "js-yaml"; import { CliContext } from "../../cli-context/CliContext"; @@ -58,7 +58,8 @@ async function writeDefinitionForOpenAPIWorkspace({ includeModels: boolean; context: TaskContext; }): Promise { - for (const spec of workspace.specs) { + const specs = await getAllOpenAPISpecs({ context, specs: workspace.specs }); + for (const spec of specs) { const ir = await parse({ specs: [spec], taskContext: context diff --git a/packages/cli/cli/src/commands/mock/mockServer.ts b/packages/cli/cli/src/commands/mock/mockServer.ts index 01fa72785c8..ab798dcdee0 100644 --- a/packages/cli/cli/src/commands/mock/mockServer.ts +++ b/packages/cli/cli/src/commands/mock/mockServer.ts @@ -42,7 +42,9 @@ export async function mockServer({ keywords: undefined, smartCasing: false, disableExamples: false, - readme: undefined + readme: undefined, + version: undefined, + packageName: undefined }); const mockServer = new MockServer({ diff --git a/packages/cli/cli/src/commands/test/testOutput.ts b/packages/cli/cli/src/commands/test/testOutput.ts index 203fafb0311..4dddf25d7dc 100644 --- a/packages/cli/cli/src/commands/test/testOutput.ts +++ b/packages/cli/cli/src/commands/test/testOutput.ts @@ -50,7 +50,9 @@ export async function testOutput({ keywords: undefined, smartCasing: false, disableExamples: false, - readme: undefined + readme: undefined, + version: undefined, + packageName: undefined }); const mockServer = new MockServer({ diff --git a/packages/cli/cli/src/commands/upgrade/updateApiSpec.ts b/packages/cli/cli/src/commands/upgrade/updateApiSpec.ts index 2ae7af198dd..9d384edebcf 100644 --- a/packages/cli/cli/src/commands/upgrade/updateApiSpec.ts +++ b/packages/cli/cli/src/commands/upgrade/updateApiSpec.ts @@ -61,6 +61,9 @@ export async function updateApiSpec({ apis = [generatorConfig.api]; } for (const api of apis) { + if (generatorsYml.isRawProtobufAPIDefinitionSchema(api)) { + continue; + } if (typeof api !== "string" && api.origin != null) { cliContext.logger.info(`Origin found, fetching spec from ${api.origin}`); await fetchAndWriteFile( diff --git a/packages/cli/configuration/fern/definition/docs.yml b/packages/cli/configuration/fern/definition/docs.yml index 3ec615cd702..8aba81e9b05 100644 --- a/packages/cli/configuration/fern/definition/docs.yml +++ b/packages/cli/configuration/fern/definition/docs.yml @@ -4,6 +4,23 @@ docs: |- The docs schema is intended to be human-readable and hand-maintained, by using human-friendly undiscriminated unions and optional properties. types: + ProgrammingLanguage: + enum: + - typescript + - javascript + - python + - java + - go + - ruby + - csharp + - nodets + - nodejs + - dotnet + - curl + - jvm + - ts + - js + DocsConfiguration: properties: instances: list @@ -21,6 +38,7 @@ types: "navbar-links": optional> "footer-links": optional experimental: optional + "default-language": optional # seo metadata: optional diff --git a/packages/cli/configuration/package.json b/packages/cli/configuration/package.json index cf7b5d43dc3..25ef2c4a561 100644 --- a/packages/cli/configuration/package.json +++ b/packages/cli/configuration/package.json @@ -29,7 +29,7 @@ }, "dependencies": { "@fern-api/core-utils": "workspace:*", - "@fern-api/fdr-sdk": "0.98.16-3955e989a", + "@fern-api/fdr-sdk": "0.98.18-aaf13f7f5", "@fern-api/fs-utils": "workspace:*", "@fern-api/task-context": "workspace:*", "@fern-fern/fiddle-sdk": "0.0.584", diff --git a/packages/cli/configuration/src/docs-yml/ParsedDocsConfiguration.ts b/packages/cli/configuration/src/docs-yml/ParsedDocsConfiguration.ts index fb01e02caba..f8da33ca53d 100644 --- a/packages/cli/configuration/src/docs-yml/ParsedDocsConfiguration.ts +++ b/packages/cli/configuration/src/docs-yml/ParsedDocsConfiguration.ts @@ -28,6 +28,7 @@ export interface ParsedDocsConfiguration { colors: DocsV1Write.ColorsConfigV3 | undefined; typography: TypographyConfig | undefined; layout: WithoutQuestionMarks | undefined; + defaultLanguage: DocsV1Write.ProgrammingLanguage | undefined; /* integrations */ integrations: DocsV1Write.IntegrationsConfig | undefined; diff --git a/packages/cli/configuration/src/docs-yml/parseDocsConfiguration.ts b/packages/cli/configuration/src/docs-yml/parseDocsConfiguration.ts index 052b97075c0..693248592d3 100644 --- a/packages/cli/configuration/src/docs-yml/parseDocsConfiguration.ts +++ b/packages/cli/configuration/src/docs-yml/parseDocsConfiguration.ts @@ -46,6 +46,7 @@ export async function parseDocsConfiguration({ navigation: rawNavigation, navbarLinks, footerLinks, + defaultLanguage, /* seo */ metadata: rawMetadata, @@ -128,6 +129,7 @@ export async function parseDocsConfiguration({ navigation, navbarLinks: convertNavbarLinks(navbarLinks), footerLinks: convertFooterLinks(footerLinks), + defaultLanguage, /* seo */ metadata, diff --git a/packages/cli/configuration/src/docs-yml/schemas/sdk/api/resources/docs/types/DocsConfiguration.ts b/packages/cli/configuration/src/docs-yml/schemas/sdk/api/resources/docs/types/DocsConfiguration.ts index b78b47be416..b129007cf0d 100644 --- a/packages/cli/configuration/src/docs-yml/schemas/sdk/api/resources/docs/types/DocsConfiguration.ts +++ b/packages/cli/configuration/src/docs-yml/schemas/sdk/api/resources/docs/types/DocsConfiguration.ts @@ -16,6 +16,7 @@ export interface DocsConfiguration { navbarLinks?: FernDocsConfig.NavbarLink[]; footerLinks?: FernDocsConfig.FooterLinksConfig; experimental?: FernDocsConfig.ExperimentalConfig; + defaultLanguage?: FernDocsConfig.ProgrammingLanguage; metadata?: FernDocsConfig.MetadataConfig; redirects?: FernDocsConfig.RedirectConfig[]; logo?: FernDocsConfig.LogoConfiguration; diff --git a/packages/cli/configuration/src/docs-yml/schemas/sdk/api/resources/docs/types/ProgrammingLanguage.ts b/packages/cli/configuration/src/docs-yml/schemas/sdk/api/resources/docs/types/ProgrammingLanguage.ts new file mode 100644 index 00000000000..6c1ad7a148a --- /dev/null +++ b/packages/cli/configuration/src/docs-yml/schemas/sdk/api/resources/docs/types/ProgrammingLanguage.ts @@ -0,0 +1,36 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +export type ProgrammingLanguage = + | "typescript" + | "javascript" + | "python" + | "java" + | "go" + | "ruby" + | "csharp" + | "nodets" + | "nodejs" + | "dotnet" + | "curl" + | "jvm" + | "ts" + | "js"; + +export const ProgrammingLanguage = { + Typescript: "typescript", + Javascript: "javascript", + Python: "python", + Java: "java", + Go: "go", + Ruby: "ruby", + Csharp: "csharp", + Nodets: "nodets", + Nodejs: "nodejs", + Dotnet: "dotnet", + Curl: "curl", + Jvm: "jvm", + Ts: "ts", + Js: "js", +} as const; diff --git a/packages/cli/configuration/src/docs-yml/schemas/sdk/api/resources/docs/types/index.ts b/packages/cli/configuration/src/docs-yml/schemas/sdk/api/resources/docs/types/index.ts index 790f4f3cd8a..7f02f785a00 100644 --- a/packages/cli/configuration/src/docs-yml/schemas/sdk/api/resources/docs/types/index.ts +++ b/packages/cli/configuration/src/docs-yml/schemas/sdk/api/resources/docs/types/index.ts @@ -1,3 +1,4 @@ +export * from "./ProgrammingLanguage"; export * from "./DocsConfiguration"; export * from "./TabId"; export * from "./TabConfig"; diff --git a/packages/cli/configuration/src/docs-yml/schemas/sdk/serialization/resources/docs/types/DocsConfiguration.ts b/packages/cli/configuration/src/docs-yml/schemas/sdk/serialization/resources/docs/types/DocsConfiguration.ts index 4c4f3d49164..ed6bb339812 100644 --- a/packages/cli/configuration/src/docs-yml/schemas/sdk/serialization/resources/docs/types/DocsConfiguration.ts +++ b/packages/cli/configuration/src/docs-yml/schemas/sdk/serialization/resources/docs/types/DocsConfiguration.ts @@ -37,6 +37,10 @@ export const DocsConfiguration: core.serialization.ObjectSchema< core.serialization.lazyObject(async () => (await import("../../..")).FooterLinksConfig).optional() ), experimental: core.serialization.lazyObject(async () => (await import("../../..")).ExperimentalConfig).optional(), + defaultLanguage: core.serialization.property( + "default-language", + core.serialization.lazy(async () => (await import("../../..")).ProgrammingLanguage).optional() + ), metadata: core.serialization.lazyObject(async () => (await import("../../..")).MetadataConfig).optional(), redirects: core.serialization .list(core.serialization.lazyObject(async () => (await import("../../..")).RedirectConfig)) @@ -66,6 +70,7 @@ export declare namespace DocsConfiguration { "navbar-links"?: serializers.NavbarLink.Raw[] | null; "footer-links"?: serializers.FooterLinksConfig.Raw | null; experimental?: serializers.ExperimentalConfig.Raw | null; + "default-language"?: serializers.ProgrammingLanguage.Raw | null; metadata?: serializers.MetadataConfig.Raw | null; redirects?: serializers.RedirectConfig.Raw[] | null; logo?: serializers.LogoConfiguration.Raw | null; diff --git a/packages/cli/configuration/src/docs-yml/schemas/sdk/serialization/resources/docs/types/ProgrammingLanguage.ts b/packages/cli/configuration/src/docs-yml/schemas/sdk/serialization/resources/docs/types/ProgrammingLanguage.ts new file mode 100644 index 00000000000..c08606240d9 --- /dev/null +++ b/packages/cli/configuration/src/docs-yml/schemas/sdk/serialization/resources/docs/types/ProgrammingLanguage.ts @@ -0,0 +1,45 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../.."; +import * as FernDocsConfig from "../../../../api"; +import * as core from "../../../../core"; + +export const ProgrammingLanguage: core.serialization.Schema< + serializers.ProgrammingLanguage.Raw, + FernDocsConfig.ProgrammingLanguage +> = core.serialization.enum_([ + "typescript", + "javascript", + "python", + "java", + "go", + "ruby", + "csharp", + "nodets", + "nodejs", + "dotnet", + "curl", + "jvm", + "ts", + "js", +]); + +export declare namespace ProgrammingLanguage { + type Raw = + | "typescript" + | "javascript" + | "python" + | "java" + | "go" + | "ruby" + | "csharp" + | "nodets" + | "nodejs" + | "dotnet" + | "curl" + | "jvm" + | "ts" + | "js"; +} diff --git a/packages/cli/configuration/src/docs-yml/schemas/sdk/serialization/resources/docs/types/index.ts b/packages/cli/configuration/src/docs-yml/schemas/sdk/serialization/resources/docs/types/index.ts index 790f4f3cd8a..7f02f785a00 100644 --- a/packages/cli/configuration/src/docs-yml/schemas/sdk/serialization/resources/docs/types/index.ts +++ b/packages/cli/configuration/src/docs-yml/schemas/sdk/serialization/resources/docs/types/index.ts @@ -1,3 +1,4 @@ +export * from "./ProgrammingLanguage"; export * from "./DocsConfiguration"; export * from "./TabId"; export * from "./TabConfig"; diff --git a/packages/cli/configuration/src/generators-yml/GeneratorsConfiguration.ts b/packages/cli/configuration/src/generators-yml/GeneratorsConfiguration.ts index b4ec037227f..2660802865b 100644 --- a/packages/cli/configuration/src/generators-yml/GeneratorsConfiguration.ts +++ b/packages/cli/configuration/src/generators-yml/GeneratorsConfiguration.ts @@ -30,13 +30,27 @@ export interface APIDefinitionSettings { } export interface APIDefinitionLocation { - path: string; + schema: APIDefinitionSchema; origin: string | undefined; overrides: string | undefined; audiences: string[] | undefined; settings: APIDefinitionSettings | undefined; } +export type APIDefinitionSchema = ProtoAPIDefinitionSchema | OSSAPIDefinitionSchema; + +export interface ProtoAPIDefinitionSchema { + type: "protobuf"; + root: string; + target: string; + localGeneration: boolean; +} + +export interface OSSAPIDefinitionSchema { + type: "oss"; + path: string; +} + export interface GeneratorGroup { groupName: string; audiences: Audiences; @@ -83,3 +97,36 @@ export const GenerationLanguage = { } as const; export type GenerationLanguage = Values; + +export function getPackageName({ + generatorInvocation +}: { + generatorInvocation: GeneratorInvocation; +}): string | undefined { + return generatorInvocation.outputMode._visit({ + downloadFiles: () => undefined, + github: (val) => + val.publishInfo?._visit({ + maven: (val) => val.coordinate, + npm: (val) => val.packageName, + pypi: (val) => val.packageName, + postman: () => undefined, + rubygems: (val) => val.packageName, + nuget: (val) => val.packageName, + _other: () => undefined + }), + githubV2: (val) => + val.publishInfo?._visit({ + maven: (val) => val.coordinate, + npm: (val) => val.packageName, + pypi: (val) => val.packageName, + postman: () => undefined, + rubygems: (val) => val.packageName, + nuget: (val) => val.packageName, + _other: () => undefined + }), + publish: () => undefined, + publishV2: () => undefined, + _other: () => undefined + }); +} diff --git a/packages/cli/configuration/src/generators-yml/convertGeneratorsConfiguration.ts b/packages/cli/configuration/src/generators-yml/convertGeneratorsConfiguration.ts index 3a1a2ea6dd5..cfd7c0c6428 100644 --- a/packages/cli/configuration/src/generators-yml/convertGeneratorsConfiguration.ts +++ b/packages/cli/configuration/src/generators-yml/convertGeneratorsConfiguration.ts @@ -12,6 +12,7 @@ import { GeneratorInvocation, GeneratorsConfiguration } from "./GeneratorsConfiguration"; +import { isRawProtobufAPIDefinitionSchema } from "./isRawProtobufAPIDefinitionSchema"; import { GeneratorGroupSchema } from "./schemas/GeneratorGroupSchema"; import { GeneratorInvocationSchema } from "./schemas/GeneratorInvocationSchema"; import { GeneratorOutputSchema } from "./schemas/GeneratorOutputSchema"; @@ -79,17 +80,36 @@ async function parseAPIConfiguration( if (apiConfiguration != null) { if (typeof apiConfiguration === "string") { apiDefinitions.push({ - path: apiConfiguration, + schema: { + type: "oss", + path: apiConfiguration + }, origin: undefined, overrides: undefined, audiences: [], settings: { shouldUseTitleAsName: undefined, shouldUseUndiscriminatedUnionsWithLiterals: undefined } }); + } else if (isRawProtobufAPIDefinitionSchema(apiConfiguration)) { + apiDefinitions.push({ + schema: { + type: "protobuf", + root: apiConfiguration.proto.root, + target: apiConfiguration.proto.target, + localGeneration: apiConfiguration.proto["local-generation"] ?? false + }, + origin: undefined, + overrides: apiConfiguration.proto.overrides, + audiences: [], + settings: { shouldUseTitleAsName: undefined, shouldUseUndiscriminatedUnionsWithLiterals: undefined } + }); } else if (Array.isArray(apiConfiguration)) { for (const definition of apiConfiguration) { if (typeof definition === "string") { apiDefinitions.push({ - path: definition, + schema: { + type: "oss", + path: definition + }, origin: undefined, overrides: undefined, audiences: [], @@ -98,9 +118,28 @@ async function parseAPIConfiguration( shouldUseUndiscriminatedUnionsWithLiterals: undefined } }); + } else if (isRawProtobufAPIDefinitionSchema(definition)) { + apiDefinitions.push({ + schema: { + type: "protobuf", + root: definition.proto.root, + target: definition.proto.target, + localGeneration: definition.proto["local-generation"] ?? false + }, + origin: undefined, + overrides: definition.proto.overrides, + audiences: [], + settings: { + shouldUseTitleAsName: undefined, + shouldUseUndiscriminatedUnionsWithLiterals: undefined + } + }); } else { apiDefinitions.push({ - path: definition.path, + schema: { + type: "oss", + path: definition.path + }, origin: definition.origin, overrides: definition.overrides, audiences: definition.audiences, @@ -113,7 +152,10 @@ async function parseAPIConfiguration( } } else { apiDefinitions.push({ - path: apiConfiguration.path, + schema: { + type: "oss", + path: apiConfiguration.path + }, origin: apiConfiguration.origin, overrides: apiConfiguration.overrides, audiences: apiConfiguration.audiences, @@ -132,7 +174,10 @@ async function parseAPIConfiguration( if (openapi != null && typeof openapi === "string") { apiDefinitions.push({ - path: openapi, + schema: { + type: "oss", + path: openapi + }, origin: apiOrigin, overrides: openapiOverrides, audiences: [], @@ -143,7 +188,10 @@ async function parseAPIConfiguration( }); } else if (openapi != null) { apiDefinitions.push({ - path: openapi.path, + schema: { + type: "oss", + path: openapi.path + }, origin: openapi.origin, overrides: openapi.overrides, audiences: [], @@ -156,7 +204,10 @@ async function parseAPIConfiguration( if (asyncapi != null) { apiDefinitions.push({ - path: asyncapi, + schema: { + type: "oss", + path: asyncapi + }, origin: apiOrigin, overrides: undefined, audiences: [], diff --git a/packages/cli/configuration/src/generators-yml/index.ts b/packages/cli/configuration/src/generators-yml/index.ts index 2514c43fc9f..324350948df 100644 --- a/packages/cli/configuration/src/generators-yml/index.ts +++ b/packages/cli/configuration/src/generators-yml/index.ts @@ -4,18 +4,21 @@ export { GENERATOR_INVOCATIONS } from "./generatorInvocations"; export { GeneratorName } from "./GeneratorName"; export { GenerationLanguage, + getPackageName, type GeneratorGroup, type GeneratorInvocation, - type GeneratorsConfiguration + type GeneratorsConfiguration, + type ProtoAPIDefinitionSchema } from "./GeneratorsConfiguration"; export { getGeneratorNameOrThrow } from "./getGeneratorName"; export { getLatestGeneratorVersion } from "./getGeneratorVersions"; +export { isRawProtobufAPIDefinitionSchema } from "./isRawProtobufAPIDefinitionSchema"; export { getPathToGeneratorsConfiguration, loadGeneratorsConfiguration, loadRawGeneratorsConfiguration } from "./loadGeneratorsConfiguration"; -export { type APIConfigurationSchema } from "./schemas/APIConfigurationSchema"; +export { type APIConfigurationSchema, type ProtobufAPIDefinitionSchema } from "./schemas/APIConfigurationSchema"; export { type GeneratorInvocationSchema } from "./schemas/GeneratorInvocationSchema"; export { type GeneratorPublishMetadataSchema } from "./schemas/GeneratorPublishMetadataSchema"; export { diff --git a/packages/cli/configuration/src/generators-yml/isRawProtobufAPIDefinitionSchema.ts b/packages/cli/configuration/src/generators-yml/isRawProtobufAPIDefinitionSchema.ts new file mode 100644 index 00000000000..75cbacddf1b --- /dev/null +++ b/packages/cli/configuration/src/generators-yml/isRawProtobufAPIDefinitionSchema.ts @@ -0,0 +1,7 @@ +import { APIConfigurationSchema, ProtobufAPIDefinitionSchema } from "./schemas/APIConfigurationSchema"; + +export function isRawProtobufAPIDefinitionSchema( + rawApiConfiguration: APIConfigurationSchema +): rawApiConfiguration is ProtobufAPIDefinitionSchema { + return typeof rawApiConfiguration !== "string" && "proto" in rawApiConfiguration; +} diff --git a/packages/cli/configuration/src/generators-yml/schemas/APIConfigurationSchema.ts b/packages/cli/configuration/src/generators-yml/schemas/APIConfigurationSchema.ts index 734ed6871f9..76fa21dff6d 100644 --- a/packages/cli/configuration/src/generators-yml/schemas/APIConfigurationSchema.ts +++ b/packages/cli/configuration/src/generators-yml/schemas/APIConfigurationSchema.ts @@ -45,19 +45,59 @@ export const APIDefintionWithOverridesSchema = z.object({ settings: z.optional(APIDefinitionSettingsSchema) }); +/** + * @example + * api: + * proto: + * root: proto + * target: proto/user/v1/user.proto + * local-generation: true + */ +export const ProtobufDefinitionSchema = z.strictObject({ + root: z.string().describe("The path to the `.proto` directroy root (e.g. `proto`)."), + target: z + .string() + .describe("The path to the target `.proto` file that defines the API (e.g. `proto/user/v1/user.proto`)."), + overrides: z.optional(z.string()).describe("Path to the overrides configuration"), + "local-generation": z + .optional(z.boolean()) + .describe("Whether to compile the `.proto` files locally. By default, we generate remotely.") +}); + +export type ProtobufDefinitionSchema = z.infer; + +/** + * @example + * api: + * proto: + * root: proto + * target: proto/user/v1/user.proto + */ +export const ProtobufAPIDefinitionSchema = z.strictObject({ + proto: ProtobufDefinitionSchema +}); + +export type ProtobufAPIDefinitionSchema = z.infer; + /** * @example * api: * - path: openapi.yml * overrides: overrides.yml * - openapi.yml + * - proto: + * root: proto + * target: proto/user/v1/user.proto */ -export const APIDefinitionList = z.array(z.union([APIDefinitionPathSchema, APIDefintionWithOverridesSchema])); +export const APIDefinitionList = z.array( + z.union([APIDefinitionPathSchema, APIDefintionWithOverridesSchema, ProtobufAPIDefinitionSchema]) +); export const APIConfigurationSchema = z.union([ APIDefinitionPathSchema, APIDefintionWithOverridesSchema, - APIDefinitionList + APIDefinitionList, + ProtobufAPIDefinitionSchema ]); export type APIConfigurationSchema = z.infer; diff --git a/packages/cli/docs-markdown-utils/package.json b/packages/cli/docs-markdown-utils/package.json index d2aeb4e481f..89359cf4a8c 100644 --- a/packages/cli/docs-markdown-utils/package.json +++ b/packages/cli/docs-markdown-utils/package.json @@ -27,7 +27,7 @@ "depcheck": "depcheck" }, "dependencies": { - "@fern-api/fdr-sdk": "0.98.16-3955e989a", + "@fern-api/fdr-sdk": "0.98.18-aaf13f7f5", "@fern-api/fs-utils": "workspace:*", "@fern-api/task-context": "workspace:*", "gray-matter": "^4.0.3", diff --git a/packages/cli/docs-markdown-utils/src/replaceReferencedMarkdown.ts b/packages/cli/docs-markdown-utils/src/replaceReferencedMarkdown.ts index a9472fa8448..a5a55469062 100644 --- a/packages/cli/docs-markdown-utils/src/replaceReferencedMarkdown.ts +++ b/packages/cli/docs-markdown-utils/src/replaceReferencedMarkdown.ts @@ -56,7 +56,7 @@ export async function replaceReferencedMarkdown({ .join("\n"); newMarkdown = newMarkdown.replace(matchString, replaceString); } catch (e) { - context.failAndThrow(`Failed to read markdown file "${src}" referenced in ${absolutePathToMdx}`, e); + context.logger.warn(`Failed to read markdown file "${src}" referenced in ${absolutePathToMdx}`); break; } } diff --git a/packages/cli/docs-preview/package.json b/packages/cli/docs-preview/package.json index b8b3af6c923..529b6ae9224 100644 --- a/packages/cli/docs-preview/package.json +++ b/packages/cli/docs-preview/package.json @@ -28,7 +28,7 @@ }, "dependencies": { "@fern-api/docs-resolver": "workspace:*", - "@fern-api/fdr-sdk": "0.98.16-3955e989a", + "@fern-api/fdr-sdk": "0.98.18-aaf13f7f5", "@fern-api/fs-utils": "workspace:*", "@fern-api/ir-sdk": "workspace:*", "@fern-api/logger": "workspace:*", diff --git a/packages/cli/docs-preview/src/runPreviewServer.ts b/packages/cli/docs-preview/src/runPreviewServer.ts index 5807397ff4d..6fdbf772ebc 100644 --- a/packages/cli/docs-preview/src/runPreviewServer.ts +++ b/packages/cli/docs-preview/src/runPreviewServer.ts @@ -114,7 +114,9 @@ export async function runPreviewServer({ // initialize docs definition docsDefinition = await reloadDocsDefinition(); - const watcher = new Watcher(absoluteFilePathToFern, { + const additionalFilepaths = project.apiWorkspaces.flatMap((workspace) => workspace.getAbsoluteFilepaths()); + + const watcher = new Watcher([absoluteFilePathToFern, ...additionalFilepaths], { recursive: true, ignoreInitial: true, debounce: 1000, diff --git a/packages/cli/docs-resolver/package.json b/packages/cli/docs-resolver/package.json index c88c097fd26..5d588b84a21 100644 --- a/packages/cli/docs-resolver/package.json +++ b/packages/cli/docs-resolver/package.json @@ -30,7 +30,7 @@ "@fern-api/configuration": "workspace:*", "@fern-api/core-utils": "workspace:*", "@fern-api/docs-markdown-utils": "workspace:*", - "@fern-api/fdr-sdk": "0.98.16-3955e989a", + "@fern-api/fdr-sdk": "0.98.18-aaf13f7f5", "@fern-api/fs-utils": "workspace:*", "@fern-api/ir-generator": "workspace:*", "@fern-api/ir-sdk": "workspace:*", diff --git a/packages/cli/docs-resolver/src/DocsDefinitionResolver.ts b/packages/cli/docs-resolver/src/DocsDefinitionResolver.ts index 7787fa641a0..82d63c60d3f 100644 --- a/packages/cli/docs-resolver/src/DocsDefinitionResolver.ts +++ b/packages/cli/docs-resolver/src/DocsDefinitionResolver.ts @@ -255,6 +255,7 @@ export class DocsDefinitionResolver { redirects: this.parsedDocsConfig.redirects, integrations: this.parsedDocsConfig.integrations, footerLinks: this.parsedDocsConfig.footerLinks, + defaultLanguage: this.parsedDocsConfig.defaultLanguage, // deprecated logo: undefined, @@ -369,7 +370,9 @@ export class DocsDefinitionResolver { keywords: undefined, smartCasing: false, disableExamples: false, - readme: undefined + readme: undefined, + version: undefined, + packageName: undefined }); const apiDefinitionId = await this.registerApi({ ir, snippetsConfig }); const api = convertIrToApiDefinition(ir, apiDefinitionId); diff --git a/packages/cli/docs-resolver/src/__test__/api-resolver.test.ts b/packages/cli/docs-resolver/src/__test__/api-resolver.test.ts index 840e6dea28c..0dd8a10cefb 100644 --- a/packages/cli/docs-resolver/src/__test__/api-resolver.test.ts +++ b/packages/cli/docs-resolver/src/__test__/api-resolver.test.ts @@ -61,7 +61,9 @@ it("converts to api reference node", async () => { keywords: undefined, smartCasing: false, disableExamples: false, - readme: undefined + readme: undefined, + version: undefined, + packageName: undefined }); const apiDefinition = convertIrToApiDefinition(ir, apiDefinitionId); diff --git a/packages/cli/docs-resolver/src/__test__/stream.test.ts b/packages/cli/docs-resolver/src/__test__/stream.test.ts index c9b5d5ebfaf..3069d43c219 100644 --- a/packages/cli/docs-resolver/src/__test__/stream.test.ts +++ b/packages/cli/docs-resolver/src/__test__/stream.test.ts @@ -65,7 +65,9 @@ it("converts to api reference node", async () => { keywords: undefined, smartCasing: false, disableExamples: false, - readme: undefined + readme: undefined, + version: undefined, + packageName: undefined }); const apiDefinition = convertIrToApiDefinition(ir, apiDefinitionId); diff --git a/packages/cli/ete-tests/package.json b/packages/cli/ete-tests/package.json index 24ae2271dff..ea9d98d11ae 100644 --- a/packages/cli/ete-tests/package.json +++ b/packages/cli/ete-tests/package.json @@ -28,7 +28,7 @@ }, "dependencies": { "@fern-api/configuration": "workspace:*", - "@fern-api/fdr-sdk": "0.98.16-3955e989a", + "@fern-api/fdr-sdk": "0.98.18-aaf13f7f5", "@fern-api/fs-utils": "workspace:*", "@fern-api/logging-execa": "workspace:*", "@fern-typescript/fetcher": "workspace:*", diff --git a/packages/cli/ete-tests/src/tests/dependencies/__snapshots__/dependencies.test.ts.snap b/packages/cli/ete-tests/src/tests/dependencies/__snapshots__/dependencies.test.ts.snap index da93b7dfe96..7e0edd9a31a 100644 --- a/packages/cli/ete-tests/src/tests/dependencies/__snapshots__/dependencies.test.ts.snap +++ b/packages/cli/ete-tests/src/tests/dependencies/__snapshots__/dependencies.test.ts.snap @@ -127,6 +127,8 @@ exports[`dependencies correctly incorporates dependencies 1`] = ` } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -224,6 +226,8 @@ exports[`dependencies correctly incorporates dependencies 1`] = ` } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -360,6 +364,8 @@ exports[`dependencies correctly incorporates dependencies 1`] = ` } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -424,6 +430,8 @@ exports[`dependencies correctly incorporates dependencies 1`] = ` }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_imported.rootEndpoint", @@ -544,6 +552,7 @@ exports[`dependencies correctly incorporates dependencies 1`] = ` "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_imported": { "name": { @@ -841,10 +850,11 @@ exports[`dependencies correctly incorporates dependencies 1`] = ` "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } }" `; -exports[`dependencies file dependencies 1`] = `3030381`; +exports[`dependencies file dependencies 1`] = `3033692`; diff --git a/packages/cli/ete-tests/src/tests/ir/__snapshots__/ir.test.ts.snap b/packages/cli/ete-tests/src/tests/ir/__snapshots__/ir.test.ts.snap index c5e7a955ae3..0947efbe5ff 100644 --- a/packages/cli/ete-tests/src/tests/ir/__snapshots__/ir.test.ts.snap +++ b/packages/cli/ete-tests/src/tests/ir/__snapshots__/ir.test.ts.snap @@ -113,6 +113,7 @@ exports[`ir {"name":"auth-header-prefix"} 1`] = ` "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": {}, "rootPackage": { "fernFilepath": { @@ -138,7 +139,8 @@ exports[`ir {"name":"auth-header-prefix"} 1`] = ` "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } }" @@ -478,6 +480,8 @@ exports[`ir {"name":"extended-examples"} 1`] = ` "type_commons:Deployment", "type_commons:AppId" ], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -883,6 +887,8 @@ exports[`ir {"name":"extended-examples"} 1`] = ` } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -1098,6 +1104,8 @@ exports[`ir {"name":"extended-examples"} 1`] = ` "referencedTypes": [ "type_commons:AppId" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1148,6 +1156,7 @@ exports[`ir {"name":"extended-examples"} 1`] = ` "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_app": { "name": { @@ -1328,7 +1337,8 @@ exports[`ir {"name":"extended-examples"} 1`] = ` "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } }" @@ -1423,6 +1433,8 @@ exports[`ir {"name":"file-upload"} 1`] = ` }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_file-upload.fileUpload", @@ -1756,6 +1768,7 @@ exports[`ir {"name":"file-upload"} 1`] = ` "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_file-upload": { "name": { @@ -1857,7 +1870,8 @@ exports[`ir {"name":"file-upload"} 1`] = ` "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } }" @@ -8020,6 +8034,8 @@ exports[`ir {"name":"multiple-environment-urls"} 1`] = ` }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_endpoint-urls.test1", @@ -8225,6 +8241,8 @@ exports[`ir {"name":"multiple-environment-urls"} 1`] = ` }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_service-url.test", @@ -8448,6 +8466,7 @@ exports[`ir {"name":"multiple-environment-urls"} 1`] = ` "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_endpoint-urls": { "name": { @@ -8623,7 +8642,8 @@ exports[`ir {"name":"multiple-environment-urls"} 1`] = ` "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } }" @@ -8702,6 +8722,7 @@ exports[`ir {"name":"navigation-points-to"} 1`] = ` "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_subpackage": { "name": { @@ -8921,7 +8942,8 @@ exports[`ir {"name":"navigation-points-to"} 1`] = ` "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } }" @@ -9199,6 +9221,8 @@ exports[`ir {"name":"nested-example-reference"} 1`] = ` "referencedTypes": [ "type_nested:Product" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -9314,6 +9338,8 @@ exports[`ir {"name":"nested-example-reference"} 1`] = ` "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -9411,6 +9437,8 @@ exports[`ir {"name":"nested-example-reference"} 1`] = ` } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -9512,6 +9540,8 @@ exports[`ir {"name":"nested-example-reference"} 1`] = ` }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_nested.calculate", @@ -10927,6 +10957,7 @@ exports[`ir {"name":"nested-example-reference"} 1`] = ` "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_nested": { "name": { @@ -11032,7 +11063,8 @@ exports[`ir {"name":"nested-example-reference"} 1`] = ` "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } }" @@ -11125,6 +11157,8 @@ exports[`ir {"name":"packages"} 1`] = ` } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -11224,6 +11258,8 @@ exports[`ir {"name":"packages"} 1`] = ` } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -11459,6 +11495,8 @@ exports[`ir {"name":"packages"} 1`] = ` "type_:RootString", "type_package:PackageString" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -11595,6 +11633,8 @@ exports[`ir {"name":"packages"} 1`] = ` } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -11619,6 +11659,8 @@ exports[`ir {"name":"packages"} 1`] = ` }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_.rootEndpoint", @@ -11752,6 +11794,8 @@ exports[`ir {"name":"packages"} 1`] = ` }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [] } }, @@ -11798,6 +11842,7 @@ exports[`ir {"name":"packages"} 1`] = ` "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_package": { "name": { @@ -12210,7 +12255,8 @@ exports[`ir {"name":"packages"} 1`] = ` "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } }" @@ -12361,6 +12407,8 @@ exports[`ir {"name":"response-property"} 1`] = ` "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -12478,6 +12526,8 @@ exports[`ir {"name":"response-property"} 1`] = ` "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -12759,6 +12809,8 @@ exports[`ir {"name":"response-property"} 1`] = ` "type_service:WithDocs", "type_service:Movie" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -12821,6 +12873,8 @@ exports[`ir {"name":"response-property"} 1`] = ` }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_service.getMovie", @@ -13620,6 +13674,7 @@ exports[`ir {"name":"response-property"} 1`] = ` "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_service": { "name": { @@ -13725,7 +13780,8 @@ exports[`ir {"name":"response-property"} 1`] = ` "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } }" @@ -13921,6 +13977,8 @@ exports[`ir {"name":"simple","audiences":["internal"]} 1`] = ` "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -14030,6 +14088,8 @@ exports[`ir {"name":"simple","audiences":["internal"]} 1`] = ` "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -14186,6 +14246,8 @@ exports[`ir {"name":"simple","audiences":["internal"]} 1`] = ` }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_imdb.internalEndpoint", @@ -15215,6 +15277,7 @@ exports[`ir {"name":"simple","audiences":["internal"]} 1`] = ` "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_commons": { "name": { @@ -15396,7 +15459,8 @@ exports[`ir {"name":"simple","audiences":["internal"]} 1`] = ` "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } }" @@ -15592,6 +15656,8 @@ exports[`ir {"name":"simple","audiences":["test"]} 1`] = ` "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -15689,6 +15755,8 @@ exports[`ir {"name":"simple","audiences":["test"]} 1`] = ` } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": null, @@ -15890,6 +15958,8 @@ exports[`ir {"name":"simple","audiences":["test"]} 1`] = ` "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -16328,6 +16398,8 @@ exports[`ir {"name":"simple","audiences":["test"]} 1`] = ` }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_imdb.createMovie", @@ -19248,6 +19320,7 @@ exports[`ir {"name":"simple","audiences":["test"]} 1`] = ` "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_commons": { "name": { @@ -19430,7 +19503,8 @@ exports[`ir {"name":"simple","audiences":["test"]} 1`] = ` "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } }" @@ -19626,6 +19700,8 @@ exports[`ir {"name":"simple"} 1`] = ` "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -19743,6 +19819,8 @@ exports[`ir {"name":"simple"} 1`] = ` "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -19894,6 +19972,8 @@ exports[`ir {"name":"simple"} 1`] = ` ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -20107,6 +20187,8 @@ exports[`ir {"name":"simple"} 1`] = ` "referencedTypes": [ "type_director:Age" ], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -20510,6 +20592,8 @@ exports[`ir {"name":"simple"} 1`] = ` } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -20642,6 +20726,8 @@ exports[`ir {"name":"simple"} 1`] = ` } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -20737,6 +20823,8 @@ exports[`ir {"name":"simple"} 1`] = ` } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -20834,6 +20922,8 @@ exports[`ir {"name":"simple"} 1`] = ` } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": null, @@ -20952,6 +21042,8 @@ exports[`ir {"name":"simple"} 1`] = ` } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": null, @@ -21090,6 +21182,8 @@ exports[`ir {"name":"simple"} 1`] = ` } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -21340,6 +21434,8 @@ exports[`ir {"name":"simple"} 1`] = ` "referencedTypes": [ "type_imdb:MovieId" ], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": null, @@ -21890,6 +21986,8 @@ exports[`ir {"name":"simple"} 1`] = ` "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -22349,6 +22447,8 @@ exports[`ir {"name":"simple"} 1`] = ` "type_director:Director", "type_director:Age" ], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": null, @@ -23177,6 +23277,8 @@ exports[`ir {"name":"simple"} 1`] = ` "type_director:Director", "type_director:Age" ], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -23848,6 +23950,8 @@ exports[`ir {"name":"simple"} 1`] = ` "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -24178,6 +24282,8 @@ exports[`ir {"name":"simple"} 1`] = ` "type_imdb:CreateMovieRequest", "type_imdb:RecursiveType" ], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": null, @@ -26272,6 +26378,8 @@ exports[`ir {"name":"simple"} 1`] = ` }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_imdb.internalEndpoint", @@ -34211,6 +34319,7 @@ exports[`ir {"name":"simple"} 1`] = ` "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_commons": { "name": { @@ -34483,7 +34592,8 @@ exports[`ir {"name":"simple"} 1`] = ` "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } }" @@ -34578,6 +34688,8 @@ exports[`ir {"name":"streaming"} 1`] = ` }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_streaming.streaming", @@ -35017,6 +35129,7 @@ exports[`ir {"name":"streaming"} 1`] = ` "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_streaming": { "name": { @@ -35118,7 +35231,8 @@ exports[`ir {"name":"streaming"} 1`] = ` "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } }" @@ -35249,6 +35363,8 @@ exports[`ir {"name":"variables"} 1`] = ` } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -35311,6 +35427,8 @@ exports[`ir {"name":"variables"} 1`] = ` }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_service.test", @@ -36132,6 +36250,7 @@ exports[`ir {"name":"variables"} 1`] = ` "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_commons": { "name": { @@ -36309,7 +36428,8 @@ exports[`ir {"name":"variables"} 1`] = ` "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } }" @@ -36512,6 +36632,7 @@ exports[`ir {"name":"webhooks"} 1`] = ` }, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_webhooks": { "name": { @@ -36613,7 +36734,8 @@ exports[`ir {"name":"webhooks"} 1`] = ` "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } }" diff --git a/packages/cli/ete-tests/src/tests/validate/__snapshots__/validate.test.ts.snap b/packages/cli/ete-tests/src/tests/validate/__snapshots__/validate.test.ts.snap index 21810c216c9..b87d3212d6f 100644 --- a/packages/cli/ete-tests/src/tests/validate/__snapshots__/validate.test.ts.snap +++ b/packages/cli/ete-tests/src/tests/validate/__snapshots__/validate.test.ts.snap @@ -1,15 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`validate docs 1`] = ` -"[docs]:Found 3 errors and 0 warnings. Run fern check --warnings to print out the warnings. -[docs]:docs.yml -> favicon - Path favicon.ico does not exist -[docs]:docs.yml -> logo -> dark - Path logo.png does not exist -[docs]:docs.yml -> navigation -> 0 -> layout -> 0 -> section -> contents -> 0 -> page - Path ./dummy-page.mdx does not exist" +"Misconfigured fern directory: please see the docs at https://buildwithfern.com/learn/api-definition/introduction/what-is-the-fern-folder +[docs]: ✓ All checks passed" `; +exports[`validate no-api 1`] = ` +"[docs]:✓ All checks passed +[api]: Missing file: api.yml" +`; + +exports[`validate no-generator 1`] = `"Misconfigured fern directory: please see the docs at https://buildwithfern.com/learn/api-definition/introduction/what-is-the-fern-folder"`; + exports[`validate simple 1`] = ` "[api]: Found 2 errors and 0 warnings. Run fern check --warnings to print out the warnings. [api]: api.yml -> error-discrimination diff --git a/packages/cli/ete-tests/src/tests/validate/fixtures/docs/fern/docs.yml b/packages/cli/ete-tests/src/tests/validate/fixtures/docs/fern/docs.yml index d1023be449e..7b4a029d712 100644 --- a/packages/cli/ete-tests/src/tests/validate/fixtures/docs/fern/docs.yml +++ b/packages/cli/ete-tests/src/tests/validate/fixtures/docs/fern/docs.yml @@ -1,21 +1,2 @@ instances: - - url: dummy.docs.buildwithfern.com - -tabs: - help: - display-name: Help Center - icon: "fa-solid fa-cube" -navigation: - - tab: help - layout: - - section: Dummy Section - contents: - - page: Dummy Page - path: ./dummy-page.mdx -title: Dummy | Documentation -colors: - accentPrimary: "#a3d3ff" -logo: - dark: logo.png - href: https://www.dummy.com/ -favicon: favicon.ico + - url: ferndevtest.docs.dev.buildwithfern.com diff --git a/packages/cli/ete-tests/src/tests/validate/fixtures/no-api/fern/definition/other.yml b/packages/cli/ete-tests/src/tests/validate/fixtures/no-api/fern/definition/other.yml new file mode 100644 index 00000000000..02f7f727b93 --- /dev/null +++ b/packages/cli/ete-tests/src/tests/validate/fixtures/no-api/fern/definition/other.yml @@ -0,0 +1,6 @@ +types: + MyType: MissingType +errors: + MyError: + type: string + status-code: 400 diff --git a/packages/cli/ete-tests/src/tests/validate/fixtures/no-api/fern/docs.yml b/packages/cli/ete-tests/src/tests/validate/fixtures/no-api/fern/docs.yml new file mode 100644 index 00000000000..7b4a029d712 --- /dev/null +++ b/packages/cli/ete-tests/src/tests/validate/fixtures/no-api/fern/docs.yml @@ -0,0 +1,2 @@ +instances: + - url: ferndevtest.docs.dev.buildwithfern.com diff --git a/packages/cli/ete-tests/src/tests/validate/fixtures/no-api/fern/fern.config.json b/packages/cli/ete-tests/src/tests/validate/fixtures/no-api/fern/fern.config.json new file mode 100644 index 00000000000..9538944f200 --- /dev/null +++ b/packages/cli/ete-tests/src/tests/validate/fixtures/no-api/fern/fern.config.json @@ -0,0 +1,4 @@ +{ + "version": "*", + "organization": "fern" +} diff --git a/packages/cli/ete-tests/src/tests/validate/fixtures/no-generator/fern/apis/api1/api.yml b/packages/cli/ete-tests/src/tests/validate/fixtures/no-generator/fern/apis/api1/api.yml new file mode 100644 index 00000000000..eee3f038239 --- /dev/null +++ b/packages/cli/ete-tests/src/tests/validate/fixtures/no-generator/fern/apis/api1/api.yml @@ -0,0 +1 @@ +name: simple-api diff --git a/packages/cli/ete-tests/src/tests/validate/fixtures/no-generator/fern/fern.config.json b/packages/cli/ete-tests/src/tests/validate/fixtures/no-generator/fern/fern.config.json new file mode 100644 index 00000000000..9538944f200 --- /dev/null +++ b/packages/cli/ete-tests/src/tests/validate/fixtures/no-generator/fern/fern.config.json @@ -0,0 +1,4 @@ +{ + "version": "*", + "organization": "fern" +} diff --git a/packages/cli/ete-tests/src/tests/validate/validate.test.ts b/packages/cli/ete-tests/src/tests/validate/validate.test.ts index b676b91d889..de2b6f5a2df 100644 --- a/packages/cli/ete-tests/src/tests/validate/validate.test.ts +++ b/packages/cli/ete-tests/src/tests/validate/validate.test.ts @@ -8,6 +8,8 @@ const FIXTURES_DIR = path.join(__dirname, "fixtures"); describe("validate", () => { itFixture("simple"); itFixture("docs"); + itFixture("no-api"); + itFixture("no-generator"); }); function itFixture(fixtureName: string) { diff --git a/packages/cli/generation/ir-generator/src/__test__/fixtures/audiences/fern-hack-override-environment-audience/definition/api.yml b/packages/cli/generation/ir-generator/src/__test__/fixtures/audiences/fern-hack-override-environment-audience/definition/api.yml new file mode 100644 index 00000000000..98b6f6f2840 --- /dev/null +++ b/packages/cli/generation/ir-generator/src/__test__/fixtures/audiences/fern-hack-override-environment-audience/definition/api.yml @@ -0,0 +1,10 @@ +name: api +environments: + Production: https://www.yoursite.com + Staging: https://www.staging.yoursite.com +error-discrimination: + strategy: status-code +audiences: + - external + - internal + - development diff --git a/packages/cli/generation/ir-generator/src/__test__/fixtures/audiences/fern-hack-override-environment-audience/definition/imdb.yml b/packages/cli/generation/ir-generator/src/__test__/fixtures/audiences/fern-hack-override-environment-audience/definition/imdb.yml new file mode 100644 index 00000000000..c70253f276e --- /dev/null +++ b/packages/cli/generation/ir-generator/src/__test__/fixtures/audiences/fern-hack-override-environment-audience/definition/imdb.yml @@ -0,0 +1,62 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/fern-api/fern/main/fern.schema.json + +service: + auth: false + base-path: /movies + endpoints: + createMovie: + audiences: + - external + docs: Add a movie to the database + method: POST + path: /create-movie + request: CreateMovieRequest + response: MovieId + + getMovie: + docs: Retrieve a movie from the database based on the ID + method: GET + path: /{id} + path-parameters: + id: MovieId + response: Movie + errors: + - MovieDoesNotExistError + examples: + # Success response + - path-parameters: + id: tt0111161 + response: + body: + id: tt0111161 + title: The Shawshank Redemption + rating: 9.3 + # Error response + - path-parameters: + id: tt1234 + response: + error: MovieDoesNotExistError + body: tt1234 + +types: + MovieId: + type: string + docs: The unique identifier for a Movie in the database + + Movie: + properties: + id: MovieId + title: string + rating: + type: double + docs: The rating scale out of ten stars + + CreateMovieRequest: + properties: + title: string + rating: double + +errors: + MovieDoesNotExistError: + status-code: 404 + type: MovieId diff --git a/packages/cli/generation/ir-generator/src/__test__/fixtures/audiences/fern-hack-override-environment-audience/definition/webhooks.yml b/packages/cli/generation/ir-generator/src/__test__/fixtures/audiences/fern-hack-override-environment-audience/definition/webhooks.yml new file mode 100644 index 00000000000..b66213e933d --- /dev/null +++ b/packages/cli/generation/ir-generator/src/__test__/fixtures/audiences/fern-hack-override-environment-audience/definition/webhooks.yml @@ -0,0 +1,17 @@ +webhooks: + userAdded: + audiences: + - external + display-name: User Added + method: POST + payload: User + + userDeleted: + display-name: User Added + method: POST + payload: User + +types: + User: + properties: + name: string \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/fixtures/audiences/fern-hack-override-environment-audience/fern.config.json b/packages/cli/generation/ir-generator/src/__test__/fixtures/audiences/fern-hack-override-environment-audience/fern.config.json new file mode 100644 index 00000000000..02a4c0e85c2 --- /dev/null +++ b/packages/cli/generation/ir-generator/src/__test__/fixtures/audiences/fern-hack-override-environment-audience/fern.config.json @@ -0,0 +1,4 @@ +{ + "organization": "dsinghvi", + "version": "0.30.7" +} \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/fixtures/audiences/fern-hack-override-environment-audience/generators.yml b/packages/cli/generation/ir-generator/src/__test__/fixtures/audiences/fern-hack-override-environment-audience/generators.yml new file mode 100644 index 00000000000..b25b3736b28 --- /dev/null +++ b/packages/cli/generation/ir-generator/src/__test__/fixtures/audiences/fern-hack-override-environment-audience/generators.yml @@ -0,0 +1,11 @@ +default-group: local +groups: + local: + audiences: + - external + generators: + - name: fernapi/fern-typescript-node-sdk + version: 0.9.5 + output: + location: local-file-system + path: ../sdks/typescript diff --git a/packages/cli/generation/ir-generator/src/__test__/fixtures/audiences/fern/definition/imdb.yml b/packages/cli/generation/ir-generator/src/__test__/fixtures/audiences/fern/definition/imdb.yml index c70253f276e..69f6050ffb7 100644 --- a/packages/cli/generation/ir-generator/src/__test__/fixtures/audiences/fern/definition/imdb.yml +++ b/packages/cli/generation/ir-generator/src/__test__/fixtures/audiences/fern/definition/imdb.yml @@ -50,12 +50,22 @@ types: rating: type: double docs: The rating scale out of ten stars + cast: list CreateMovieRequest: properties: title: string rating: double - + + Cast: + base-properties: + name: Name + union: + actor: {} + actress: {} + + Name: string + errors: MovieDoesNotExistError: status-code: 404 diff --git a/packages/cli/generation/ir-generator/src/__test__/generateAndSnapshotIR.ts b/packages/cli/generation/ir-generator/src/__test__/generateAndSnapshotIR.ts index 034fbf886db..0464e88ae1e 100644 --- a/packages/cli/generation/ir-generator/src/__test__/generateAndSnapshotIR.ts +++ b/packages/cli/generation/ir-generator/src/__test__/generateAndSnapshotIR.ts @@ -54,7 +54,9 @@ export async function generateAndSnapshotIR({ keywords: undefined, smartCasing: true, disableExamples: false, - readme: undefined + readme: undefined, + version: undefined, + packageName: undefined }); const intermediateRepresentationJson = await IrSerialization.IntermediateRepresentation.jsonOrThrow( diff --git a/packages/cli/generation/ir-generator/src/__test__/generateIntermediateRepresentation.test.ts b/packages/cli/generation/ir-generator/src/__test__/generateIntermediateRepresentation.test.ts index 5ee33ab90b5..8449a89c1c8 100644 --- a/packages/cli/generation/ir-generator/src/__test__/generateIntermediateRepresentation.test.ts +++ b/packages/cli/generation/ir-generator/src/__test__/generateIntermediateRepresentation.test.ts @@ -28,6 +28,26 @@ it("environments no audiences", async () => { }); }); +it("environments no audiences on environments but all hack", async () => { + const AUDIENCES_DIR = path.join(__dirname, "fixtures/audiences/fern-hack-override-environment-audience"); + await generateAndSnapshotIRFromPath({ + absolutePathToIr: AbsoluteFilePath.of(IR_DIR), + absolutePathToWorkspace: AbsoluteFilePath.of(AUDIENCES_DIR), + audiences: { type: "all" }, + workspaceName: "environmentAudiencesAllHack" + }); +}); + +it("environments no audiences on environments but selected hack", async () => { + const AUDIENCES_DIR = path.join(__dirname, "fixtures/audiences/fern-hack-override-environment-audience"); + await generateAndSnapshotIRFromPath({ + absolutePathToIr: AbsoluteFilePath.of(IR_DIR), + absolutePathToWorkspace: AbsoluteFilePath.of(AUDIENCES_DIR), + audiences: { type: "select", audiences: ["external"] }, + workspaceName: "environmentAudiencesSelectHack" + }); +}); + it("fhir", async () => { const FHIR_DIR = path.join(__dirname, "../../../../../../fern/apis/fhir"); await generateAndSnapshotIRFromPath({ diff --git a/packages/cli/generation/ir-generator/src/__test__/irs/audiences.json b/packages/cli/generation/ir-generator/src/__test__/irs/audiences.json index 97d86a0176a..c276d0105c4 100644 --- a/packages/cli/generation/ir-generator/src/__test__/irs/audiences.json +++ b/packages/cli/generation/ir-generator/src/__test__/irs/audiences.json @@ -122,6 +122,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -276,6 +278,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -393,6 +397,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -455,6 +461,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_imdb.createMovie", @@ -1319,6 +1327,7 @@ }, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_imdb": { "name": { @@ -1499,7 +1508,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/irs/environmentAudiences.json b/packages/cli/generation/ir-generator/src/__test__/irs/environmentAudiences.json index 9b113bf9406..32319433f01 100644 --- a/packages/cli/generation/ir-generator/src/__test__/irs/environmentAudiences.json +++ b/packages/cli/generation/ir-generator/src/__test__/irs/environmentAudiences.json @@ -122,6 +122,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -364,14 +366,118 @@ }, "availability": null, "docs": "The rating scale out of ten stars" + }, + { + "name": { + "name": { + "originalName": "cast", + "camelCase": { + "unsafeName": "cast", + "safeName": "cast" + }, + "snakeCase": { + "unsafeName": "cast", + "safeName": "cast" + }, + "screamingSnakeCase": { + "unsafeName": "CAST", + "safeName": "CAST" + }, + "pascalCase": { + "unsafeName": "Cast", + "safeName": "Cast" + } + }, + "wireValue": "cast" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "list", + "list": { + "_type": "named", + "name": { + "originalName": "Cast", + "camelCase": { + "unsafeName": "cast", + "safeName": "cast" + }, + "snakeCase": { + "unsafeName": "cast", + "safeName": "cast" + }, + "screamingSnakeCase": { + "unsafeName": "CAST", + "safeName": "CAST" + }, + "pascalCase": { + "unsafeName": "Cast", + "safeName": "Cast" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "typeId": "type_imdb:Cast", + "default": null, + "inline": null + } + } + }, + "availability": null, + "docs": null } ], "extra-properties": false, "extendedProperties": [] }, "referencedTypes": [ - "type_imdb:MovieId" + "type_imdb:MovieId", + "type_imdb:Cast", + "type_imdb:Name" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -526,6 +632,363 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "availability": null, + "docs": null + }, + "type_imdb:Cast": { + "name": { + "name": { + "originalName": "Cast", + "camelCase": { + "unsafeName": "cast", + "safeName": "cast" + }, + "snakeCase": { + "unsafeName": "cast", + "safeName": "cast" + }, + "screamingSnakeCase": { + "unsafeName": "CAST", + "safeName": "CAST" + }, + "pascalCase": { + "unsafeName": "Cast", + "safeName": "Cast" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "typeId": "type_imdb:Cast" + }, + "shape": { + "_type": "union", + "discriminant": { + "name": { + "originalName": "type", + "camelCase": { + "unsafeName": "type", + "safeName": "type" + }, + "snakeCase": { + "unsafeName": "type", + "safeName": "type" + }, + "screamingSnakeCase": { + "unsafeName": "TYPE", + "safeName": "TYPE" + }, + "pascalCase": { + "unsafeName": "Type", + "safeName": "Type" + } + }, + "wireValue": "type" + }, + "extends": [], + "baseProperties": [ + { + "name": { + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + }, + "wireValue": "name" + }, + "valueType": { + "_type": "named", + "name": { + "originalName": "Name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "typeId": "type_imdb:Name", + "default": null, + "inline": null + }, + "availability": null, + "docs": null + } + ], + "types": [ + { + "discriminantValue": { + "name": { + "originalName": "actor", + "camelCase": { + "unsafeName": "actor", + "safeName": "actor" + }, + "snakeCase": { + "unsafeName": "actor", + "safeName": "actor" + }, + "screamingSnakeCase": { + "unsafeName": "ACTOR", + "safeName": "ACTOR" + }, + "pascalCase": { + "unsafeName": "Actor", + "safeName": "Actor" + } + }, + "wireValue": "actor" + }, + "shape": { + "_type": "noProperties" + }, + "docs": null + }, + { + "discriminantValue": { + "name": { + "originalName": "actress", + "camelCase": { + "unsafeName": "actress", + "safeName": "actress" + }, + "snakeCase": { + "unsafeName": "actress", + "safeName": "actress" + }, + "screamingSnakeCase": { + "unsafeName": "ACTRESS", + "safeName": "ACTRESS" + }, + "pascalCase": { + "unsafeName": "Actress", + "safeName": "Actress" + } + }, + "wireValue": "actress" + }, + "shape": { + "_type": "noProperties" + }, + "docs": null + } + ] + }, + "referencedTypes": [ + "type_imdb:Name" + ], + "encoding": null, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "availability": null, + "docs": null + }, + "type_imdb:Name": { + "name": { + "name": { + "originalName": "Name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "typeId": "type_imdb:Name" + }, + "shape": { + "_type": "alias", + "aliasOf": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "resolvedType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + }, + "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -643,6 +1106,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -866,6 +1331,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_imdb.createMovie", @@ -3950,6 +4417,303 @@ }, "typeId": "type_imdb:Movie" } + }, + { + "name": { + "name": { + "originalName": "cast", + "camelCase": { + "unsafeName": "cast", + "safeName": "cast" + }, + "snakeCase": { + "unsafeName": "cast", + "safeName": "cast" + }, + "screamingSnakeCase": { + "unsafeName": "CAST", + "safeName": "CAST" + }, + "pascalCase": { + "unsafeName": "Cast", + "safeName": "Cast" + } + }, + "wireValue": "cast" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "list", + "list": [ + { + "shape": { + "type": "named", + "typeName": { + "name": { + "originalName": "Cast", + "camelCase": { + "unsafeName": "cast", + "safeName": "cast" + }, + "snakeCase": { + "unsafeName": "cast", + "safeName": "cast" + }, + "screamingSnakeCase": { + "unsafeName": "CAST", + "safeName": "CAST" + }, + "pascalCase": { + "unsafeName": "Cast", + "safeName": "Cast" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "typeId": "type_imdb:Cast" + }, + "shape": { + "type": "union", + "discriminant": { + "name": { + "originalName": "type", + "camelCase": { + "unsafeName": "type", + "safeName": "type" + }, + "snakeCase": { + "unsafeName": "type", + "safeName": "type" + }, + "screamingSnakeCase": { + "unsafeName": "TYPE", + "safeName": "TYPE" + }, + "pascalCase": { + "unsafeName": "Type", + "safeName": "Type" + } + }, + "wireValue": "type" + }, + "singleUnionType": { + "wireDiscriminantValue": { + "name": { + "originalName": "actor", + "camelCase": { + "unsafeName": "actor", + "safeName": "actor" + }, + "snakeCase": { + "unsafeName": "actor", + "safeName": "actor" + }, + "screamingSnakeCase": { + "unsafeName": "ACTOR", + "safeName": "ACTOR" + }, + "pascalCase": { + "unsafeName": "Actor", + "safeName": "Actor" + } + }, + "wireValue": "actor" + }, + "shape": { + "type": "noProperties" + } + } + } + }, + "jsonExample": { + "type": "actor" + } + } + ], + "itemType": { + "_type": "named", + "name": { + "originalName": "Cast", + "camelCase": { + "unsafeName": "cast", + "safeName": "cast" + }, + "snakeCase": { + "unsafeName": "cast", + "safeName": "cast" + }, + "screamingSnakeCase": { + "unsafeName": "CAST", + "safeName": "CAST" + }, + "pascalCase": { + "unsafeName": "Cast", + "safeName": "Cast" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "typeId": "type_imdb:Cast", + "default": null, + "inline": null + } + } + }, + "jsonExample": [ + { + "type": "actor" + } + ] + }, + "originalTypeDeclaration": { + "name": { + "originalName": "Movie", + "camelCase": { + "unsafeName": "movie", + "safeName": "movie" + }, + "snakeCase": { + "unsafeName": "movie", + "safeName": "movie" + }, + "screamingSnakeCase": { + "unsafeName": "MOVIE", + "safeName": "MOVIE" + }, + "pascalCase": { + "unsafeName": "Movie", + "safeName": "Movie" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "typeId": "type_imdb:Movie" + } } ] } @@ -3957,12 +4721,17 @@ "jsonExample": { "id": "string", "title": "string", - "rating": 1.1 + "rating": 1.1, + "cast": [ + { + "type": "actor" + } + ] } } } }, - "id": "bddb18e0df4a21ec0a1d0f2de6f81effa3fe4a52", + "id": "6065f6ad44910bc0e8d06da3d1f11c8a08d338c9", "docs": null } }, @@ -4261,6 +5030,8 @@ ] }, "sharedTypes": [ + "type_imdb:Cast", + "type_imdb:Name", "type_webhooks:User" ] }, @@ -4470,6 +5241,7 @@ }, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_imdb": { "name": { @@ -4538,7 +5310,9 @@ "types": [ "type_imdb:MovieId", "type_imdb:Movie", - "type_imdb:CreateMovieRequest" + "type_imdb:CreateMovieRequest", + "type_imdb:Cast", + "type_imdb:Name" ], "errors": [ "error_imdb:MovieDoesNotExistError" @@ -4653,7 +5427,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/irs/environmentAudiencesAllHack.json b/packages/cli/generation/ir-generator/src/__test__/irs/environmentAudiencesAllHack.json new file mode 100644 index 00000000000..ca2066c6ae2 --- /dev/null +++ b/packages/cli/generation/ir-generator/src/__test__/irs/environmentAudiencesAllHack.json @@ -0,0 +1,4671 @@ +{ + "fdrApiDefinitionId": null, + "apiVersion": null, + "apiName": { + "originalName": "api", + "camelCase": { + "unsafeName": "api", + "safeName": "api" + }, + "snakeCase": { + "unsafeName": "api", + "safeName": "api" + }, + "screamingSnakeCase": { + "unsafeName": "API", + "safeName": "API" + }, + "pascalCase": { + "unsafeName": "API", + "safeName": "API" + } + }, + "apiDisplayName": null, + "apiDocs": null, + "auth": { + "requirement": "ALL", + "schemes": [], + "docs": null + }, + "headers": [], + "idempotencyHeaders": [], + "types": { + "type_imdb:MovieId": { + "name": { + "name": { + "originalName": "MovieId", + "camelCase": { + "unsafeName": "movieID", + "safeName": "movieID" + }, + "snakeCase": { + "unsafeName": "movie_id", + "safeName": "movie_id" + }, + "screamingSnakeCase": { + "unsafeName": "MOVIE_ID", + "safeName": "MOVIE_ID" + }, + "pascalCase": { + "unsafeName": "MovieID", + "safeName": "MovieID" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "typeId": "type_imdb:MovieId" + }, + "shape": { + "_type": "alias", + "aliasOf": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "resolvedType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + }, + "referencedTypes": [], + "encoding": null, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "availability": null, + "docs": "The unique identifier for a Movie in the database" + }, + "type_imdb:Movie": { + "name": { + "name": { + "originalName": "Movie", + "camelCase": { + "unsafeName": "movie", + "safeName": "movie" + }, + "snakeCase": { + "unsafeName": "movie", + "safeName": "movie" + }, + "screamingSnakeCase": { + "unsafeName": "MOVIE", + "safeName": "MOVIE" + }, + "pascalCase": { + "unsafeName": "Movie", + "safeName": "Movie" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "typeId": "type_imdb:Movie" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": { + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + }, + "wireValue": "id" + }, + "valueType": { + "_type": "named", + "name": { + "originalName": "MovieId", + "camelCase": { + "unsafeName": "movieID", + "safeName": "movieID" + }, + "snakeCase": { + "unsafeName": "movie_id", + "safeName": "movie_id" + }, + "screamingSnakeCase": { + "unsafeName": "MOVIE_ID", + "safeName": "MOVIE_ID" + }, + "pascalCase": { + "unsafeName": "MovieID", + "safeName": "MovieID" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "typeId": "type_imdb:MovieId", + "default": null, + "inline": null + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "title", + "camelCase": { + "unsafeName": "title", + "safeName": "title" + }, + "snakeCase": { + "unsafeName": "title", + "safeName": "title" + }, + "screamingSnakeCase": { + "unsafeName": "TITLE", + "safeName": "TITLE" + }, + "pascalCase": { + "unsafeName": "Title", + "safeName": "Title" + } + }, + "wireValue": "title" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "rating", + "camelCase": { + "unsafeName": "rating", + "safeName": "rating" + }, + "snakeCase": { + "unsafeName": "rating", + "safeName": "rating" + }, + "screamingSnakeCase": { + "unsafeName": "RATING", + "safeName": "RATING" + }, + "pascalCase": { + "unsafeName": "Rating", + "safeName": "Rating" + } + }, + "wireValue": "rating" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "DOUBLE", + "v2": { + "type": "double", + "default": null, + "validation": null + } + } + }, + "availability": null, + "docs": "The rating scale out of ten stars" + } + ], + "extra-properties": false, + "extendedProperties": [] + }, + "referencedTypes": [ + "type_imdb:MovieId" + ], + "encoding": null, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "availability": null, + "docs": null + }, + "type_imdb:CreateMovieRequest": { + "name": { + "name": { + "originalName": "CreateMovieRequest", + "camelCase": { + "unsafeName": "createMovieRequest", + "safeName": "createMovieRequest" + }, + "snakeCase": { + "unsafeName": "create_movie_request", + "safeName": "create_movie_request" + }, + "screamingSnakeCase": { + "unsafeName": "CREATE_MOVIE_REQUEST", + "safeName": "CREATE_MOVIE_REQUEST" + }, + "pascalCase": { + "unsafeName": "CreateMovieRequest", + "safeName": "CreateMovieRequest" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "typeId": "type_imdb:CreateMovieRequest" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": { + "name": { + "originalName": "title", + "camelCase": { + "unsafeName": "title", + "safeName": "title" + }, + "snakeCase": { + "unsafeName": "title", + "safeName": "title" + }, + "screamingSnakeCase": { + "unsafeName": "TITLE", + "safeName": "TITLE" + }, + "pascalCase": { + "unsafeName": "Title", + "safeName": "Title" + } + }, + "wireValue": "title" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "rating", + "camelCase": { + "unsafeName": "rating", + "safeName": "rating" + }, + "snakeCase": { + "unsafeName": "rating", + "safeName": "rating" + }, + "screamingSnakeCase": { + "unsafeName": "RATING", + "safeName": "RATING" + }, + "pascalCase": { + "unsafeName": "Rating", + "safeName": "Rating" + } + }, + "wireValue": "rating" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "DOUBLE", + "v2": { + "type": "double", + "default": null, + "validation": null + } + } + }, + "availability": null, + "docs": null + } + ], + "extra-properties": false, + "extendedProperties": [] + }, + "referencedTypes": [], + "encoding": null, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "availability": null, + "docs": null + }, + "type_webhooks:User": { + "name": { + "name": { + "originalName": "User", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "webhooks", + "camelCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "snakeCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "screamingSnakeCase": { + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" + }, + "pascalCase": { + "unsafeName": "Webhooks", + "safeName": "Webhooks" + } + } + ], + "packagePath": [], + "file": { + "originalName": "webhooks", + "camelCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "snakeCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "screamingSnakeCase": { + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" + }, + "pascalCase": { + "unsafeName": "Webhooks", + "safeName": "Webhooks" + } + } + }, + "typeId": "type_webhooks:User" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": { + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + }, + "wireValue": "name" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "availability": null, + "docs": null + } + ], + "extra-properties": false, + "extendedProperties": [] + }, + "referencedTypes": [], + "encoding": null, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "availability": null, + "docs": null + } + }, + "errors": { + "error_imdb:MovieDoesNotExistError": { + "name": { + "name": { + "originalName": "MovieDoesNotExistError", + "camelCase": { + "unsafeName": "movieDoesNotExistError", + "safeName": "movieDoesNotExistError" + }, + "snakeCase": { + "unsafeName": "movie_does_not_exist_error", + "safeName": "movie_does_not_exist_error" + }, + "screamingSnakeCase": { + "unsafeName": "MOVIE_DOES_NOT_EXIST_ERROR", + "safeName": "MOVIE_DOES_NOT_EXIST_ERROR" + }, + "pascalCase": { + "unsafeName": "MovieDoesNotExistError", + "safeName": "MovieDoesNotExistError" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "errorId": "error_imdb:MovieDoesNotExistError" + }, + "discriminantValue": { + "name": { + "originalName": "MovieDoesNotExistError", + "camelCase": { + "unsafeName": "movieDoesNotExistError", + "safeName": "movieDoesNotExistError" + }, + "snakeCase": { + "unsafeName": "movie_does_not_exist_error", + "safeName": "movie_does_not_exist_error" + }, + "screamingSnakeCase": { + "unsafeName": "MOVIE_DOES_NOT_EXIST_ERROR", + "safeName": "MOVIE_DOES_NOT_EXIST_ERROR" + }, + "pascalCase": { + "unsafeName": "MovieDoesNotExistError", + "safeName": "MovieDoesNotExistError" + } + }, + "wireValue": "MovieDoesNotExistError" + }, + "statusCode": 404, + "type": { + "_type": "named", + "name": { + "originalName": "MovieId", + "camelCase": { + "unsafeName": "movieID", + "safeName": "movieID" + }, + "snakeCase": { + "unsafeName": "movie_id", + "safeName": "movie_id" + }, + "screamingSnakeCase": { + "unsafeName": "MOVIE_ID", + "safeName": "MOVIE_ID" + }, + "pascalCase": { + "unsafeName": "MovieID", + "safeName": "MovieID" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "typeId": "type_imdb:MovieId", + "default": null, + "inline": null + }, + "examples": [], + "docs": null + } + }, + "services": { + "service_imdb": { + "availability": null, + "name": { + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + } + }, + "displayName": null, + "basePath": { + "head": "/movies", + "parts": [] + }, + "headers": [], + "pathParameters": [], + "encoding": null, + "transport": null, + "endpoints": [ + { + "id": "endpoint_imdb.createMovie", + "name": { + "originalName": "createMovie", + "camelCase": { + "unsafeName": "createMovie", + "safeName": "createMovie" + }, + "snakeCase": { + "unsafeName": "create_movie", + "safeName": "create_movie" + }, + "screamingSnakeCase": { + "unsafeName": "CREATE_MOVIE", + "safeName": "CREATE_MOVIE" + }, + "pascalCase": { + "unsafeName": "CreateMovie", + "safeName": "CreateMovie" + } + }, + "displayName": null, + "auth": false, + "idempotent": false, + "baseUrl": null, + "method": "POST", + "path": { + "head": "/create-movie", + "parts": [] + }, + "fullPath": { + "head": "/movies/create-movie", + "parts": [] + }, + "pathParameters": [], + "allPathParameters": [], + "queryParameters": [], + "headers": [], + "requestBody": { + "type": "reference", + "requestBodyType": { + "_type": "named", + "name": { + "originalName": "CreateMovieRequest", + "camelCase": { + "unsafeName": "createMovieRequest", + "safeName": "createMovieRequest" + }, + "snakeCase": { + "unsafeName": "create_movie_request", + "safeName": "create_movie_request" + }, + "screamingSnakeCase": { + "unsafeName": "CREATE_MOVIE_REQUEST", + "safeName": "CREATE_MOVIE_REQUEST" + }, + "pascalCase": { + "unsafeName": "CreateMovieRequest", + "safeName": "CreateMovieRequest" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "typeId": "type_imdb:CreateMovieRequest", + "default": null, + "inline": null + }, + "contentType": null, + "docs": null + }, + "sdkRequest": { + "shape": { + "type": "justRequestBody", + "value": { + "type": "typeReference", + "requestBodyType": { + "_type": "named", + "name": { + "originalName": "CreateMovieRequest", + "camelCase": { + "unsafeName": "createMovieRequest", + "safeName": "createMovieRequest" + }, + "snakeCase": { + "unsafeName": "create_movie_request", + "safeName": "create_movie_request" + }, + "screamingSnakeCase": { + "unsafeName": "CREATE_MOVIE_REQUEST", + "safeName": "CREATE_MOVIE_REQUEST" + }, + "pascalCase": { + "unsafeName": "CreateMovieRequest", + "safeName": "CreateMovieRequest" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "typeId": "type_imdb:CreateMovieRequest", + "default": null, + "inline": null + }, + "contentType": null, + "docs": null + } + }, + "requestParameterName": { + "originalName": "request", + "camelCase": { + "unsafeName": "request", + "safeName": "request" + }, + "snakeCase": { + "unsafeName": "request", + "safeName": "request" + }, + "screamingSnakeCase": { + "unsafeName": "REQUEST", + "safeName": "REQUEST" + }, + "pascalCase": { + "unsafeName": "Request", + "safeName": "Request" + } + }, + "streamParameter": null + }, + "response": { + "body": { + "type": "json", + "value": { + "type": "response", + "responseBodyType": { + "_type": "named", + "name": { + "originalName": "MovieId", + "camelCase": { + "unsafeName": "movieID", + "safeName": "movieID" + }, + "snakeCase": { + "unsafeName": "movie_id", + "safeName": "movie_id" + }, + "screamingSnakeCase": { + "unsafeName": "MOVIE_ID", + "safeName": "MOVIE_ID" + }, + "pascalCase": { + "unsafeName": "MovieID", + "safeName": "MovieID" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "typeId": "type_imdb:MovieId", + "default": null, + "inline": null + }, + "docs": null + } + }, + "status-code": null + }, + "errors": [], + "userSpecifiedExamples": [], + "autogeneratedExamples": [ + { + "example": { + "url": "/movies/create-movie", + "rootPathParameters": [], + "servicePathParameters": [], + "endpointPathParameters": [], + "serviceHeaders": [], + "endpointHeaders": [], + "queryParameters": [], + "request": { + "type": "reference", + "shape": { + "type": "named", + "typeName": { + "name": { + "originalName": "CreateMovieRequest", + "camelCase": { + "unsafeName": "createMovieRequest", + "safeName": "createMovieRequest" + }, + "snakeCase": { + "unsafeName": "create_movie_request", + "safeName": "create_movie_request" + }, + "screamingSnakeCase": { + "unsafeName": "CREATE_MOVIE_REQUEST", + "safeName": "CREATE_MOVIE_REQUEST" + }, + "pascalCase": { + "unsafeName": "CreateMovieRequest", + "safeName": "CreateMovieRequest" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "typeId": "type_imdb:CreateMovieRequest" + }, + "shape": { + "type": "object", + "properties": [ + { + "name": { + "name": { + "originalName": "title", + "camelCase": { + "unsafeName": "title", + "safeName": "title" + }, + "snakeCase": { + "unsafeName": "title", + "safeName": "title" + }, + "screamingSnakeCase": { + "unsafeName": "TITLE", + "safeName": "TITLE" + }, + "pascalCase": { + "unsafeName": "Title", + "safeName": "Title" + } + }, + "wireValue": "title" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "string" + } + } + }, + "jsonExample": "string" + }, + "originalTypeDeclaration": { + "name": { + "originalName": "CreateMovieRequest", + "camelCase": { + "unsafeName": "createMovieRequest", + "safeName": "createMovieRequest" + }, + "snakeCase": { + "unsafeName": "create_movie_request", + "safeName": "create_movie_request" + }, + "screamingSnakeCase": { + "unsafeName": "CREATE_MOVIE_REQUEST", + "safeName": "CREATE_MOVIE_REQUEST" + }, + "pascalCase": { + "unsafeName": "CreateMovieRequest", + "safeName": "CreateMovieRequest" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "typeId": "type_imdb:CreateMovieRequest" + } + }, + { + "name": { + "name": { + "originalName": "rating", + "camelCase": { + "unsafeName": "rating", + "safeName": "rating" + }, + "snakeCase": { + "unsafeName": "rating", + "safeName": "rating" + }, + "screamingSnakeCase": { + "unsafeName": "RATING", + "safeName": "RATING" + }, + "pascalCase": { + "unsafeName": "Rating", + "safeName": "Rating" + } + }, + "wireValue": "rating" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "double", + "double": 1.1 + } + }, + "jsonExample": 1.1 + }, + "originalTypeDeclaration": { + "name": { + "originalName": "CreateMovieRequest", + "camelCase": { + "unsafeName": "createMovieRequest", + "safeName": "createMovieRequest" + }, + "snakeCase": { + "unsafeName": "create_movie_request", + "safeName": "create_movie_request" + }, + "screamingSnakeCase": { + "unsafeName": "CREATE_MOVIE_REQUEST", + "safeName": "CREATE_MOVIE_REQUEST" + }, + "pascalCase": { + "unsafeName": "CreateMovieRequest", + "safeName": "CreateMovieRequest" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "typeId": "type_imdb:CreateMovieRequest" + } + } + ] + } + }, + "jsonExample": { + "title": "string", + "rating": 1.1 + } + }, + "name": null, + "response": { + "type": "ok", + "value": { + "type": "body", + "value": { + "shape": { + "type": "named", + "typeName": { + "name": { + "originalName": "MovieId", + "camelCase": { + "unsafeName": "movieID", + "safeName": "movieID" + }, + "snakeCase": { + "unsafeName": "movie_id", + "safeName": "movie_id" + }, + "screamingSnakeCase": { + "unsafeName": "MOVIE_ID", + "safeName": "MOVIE_ID" + }, + "pascalCase": { + "unsafeName": "MovieID", + "safeName": "MovieID" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "typeId": "type_imdb:MovieId" + }, + "shape": { + "type": "alias", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "string" + } + } + }, + "jsonExample": "string" + } + } + }, + "jsonExample": "string" + } + } + }, + "id": "0fd01d62bca0126c84c6dae0eb32bd903b886a36", + "docs": null + } + } + ], + "pagination": null, + "availability": null, + "docs": "Add a movie to the database" + }, + { + "id": "endpoint_imdb.getMovie", + "name": { + "originalName": "getMovie", + "camelCase": { + "unsafeName": "getMovie", + "safeName": "getMovie" + }, + "snakeCase": { + "unsafeName": "get_movie", + "safeName": "get_movie" + }, + "screamingSnakeCase": { + "unsafeName": "GET_MOVIE", + "safeName": "GET_MOVIE" + }, + "pascalCase": { + "unsafeName": "GetMovie", + "safeName": "GetMovie" + } + }, + "displayName": null, + "auth": false, + "idempotent": false, + "baseUrl": null, + "method": "GET", + "path": { + "head": "/", + "parts": [ + { + "pathParameter": "id", + "tail": "" + } + ] + }, + "fullPath": { + "head": "/movies/", + "parts": [ + { + "pathParameter": "id", + "tail": "" + } + ] + }, + "pathParameters": [ + { + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + }, + "valueType": { + "_type": "named", + "name": { + "originalName": "MovieId", + "camelCase": { + "unsafeName": "movieID", + "safeName": "movieID" + }, + "snakeCase": { + "unsafeName": "movie_id", + "safeName": "movie_id" + }, + "screamingSnakeCase": { + "unsafeName": "MOVIE_ID", + "safeName": "MOVIE_ID" + }, + "pascalCase": { + "unsafeName": "MovieID", + "safeName": "MovieID" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "typeId": "type_imdb:MovieId", + "default": null, + "inline": null + }, + "location": "ENDPOINT", + "variable": null, + "docs": null + } + ], + "allPathParameters": [ + { + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + }, + "valueType": { + "_type": "named", + "name": { + "originalName": "MovieId", + "camelCase": { + "unsafeName": "movieID", + "safeName": "movieID" + }, + "snakeCase": { + "unsafeName": "movie_id", + "safeName": "movie_id" + }, + "screamingSnakeCase": { + "unsafeName": "MOVIE_ID", + "safeName": "MOVIE_ID" + }, + "pascalCase": { + "unsafeName": "MovieID", + "safeName": "MovieID" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "typeId": "type_imdb:MovieId", + "default": null, + "inline": null + }, + "location": "ENDPOINT", + "variable": null, + "docs": null + } + ], + "queryParameters": [], + "headers": [], + "requestBody": null, + "sdkRequest": null, + "response": { + "body": { + "type": "json", + "value": { + "type": "response", + "responseBodyType": { + "_type": "named", + "name": { + "originalName": "Movie", + "camelCase": { + "unsafeName": "movie", + "safeName": "movie" + }, + "snakeCase": { + "unsafeName": "movie", + "safeName": "movie" + }, + "screamingSnakeCase": { + "unsafeName": "MOVIE", + "safeName": "MOVIE" + }, + "pascalCase": { + "unsafeName": "Movie", + "safeName": "Movie" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "typeId": "type_imdb:Movie", + "default": null, + "inline": null + }, + "docs": null + } + }, + "status-code": null + }, + "errors": [ + { + "error": { + "name": { + "originalName": "MovieDoesNotExistError", + "camelCase": { + "unsafeName": "movieDoesNotExistError", + "safeName": "movieDoesNotExistError" + }, + "snakeCase": { + "unsafeName": "movie_does_not_exist_error", + "safeName": "movie_does_not_exist_error" + }, + "screamingSnakeCase": { + "unsafeName": "MOVIE_DOES_NOT_EXIST_ERROR", + "safeName": "MOVIE_DOES_NOT_EXIST_ERROR" + }, + "pascalCase": { + "unsafeName": "MovieDoesNotExistError", + "safeName": "MovieDoesNotExistError" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "errorId": "error_imdb:MovieDoesNotExistError" + }, + "docs": null + } + ], + "userSpecifiedExamples": [ + { + "example": { + "id": null, + "name": null, + "url": "/movies/tt0111161", + "rootPathParameters": [], + "endpointPathParameters": [ + { + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + }, + "value": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_imdb:MovieId", + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "name": { + "originalName": "MovieId", + "camelCase": { + "unsafeName": "movieID", + "safeName": "movieID" + }, + "snakeCase": { + "unsafeName": "movie_id", + "safeName": "movie_id" + }, + "screamingSnakeCase": { + "unsafeName": "MOVIE_ID", + "safeName": "MOVIE_ID" + }, + "pascalCase": { + "unsafeName": "MovieID", + "safeName": "MovieID" + } + } + }, + "shape": { + "type": "alias", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "tt0111161" + } + } + }, + "jsonExample": "tt0111161" + } + } + }, + "jsonExample": "tt0111161" + } + } + ], + "servicePathParameters": [], + "endpointHeaders": [], + "serviceHeaders": [], + "queryParameters": [], + "request": null, + "response": { + "type": "ok", + "value": { + "type": "body", + "value": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_imdb:Movie", + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "name": { + "originalName": "Movie", + "camelCase": { + "unsafeName": "movie", + "safeName": "movie" + }, + "snakeCase": { + "unsafeName": "movie", + "safeName": "movie" + }, + "screamingSnakeCase": { + "unsafeName": "MOVIE", + "safeName": "MOVIE" + }, + "pascalCase": { + "unsafeName": "Movie", + "safeName": "Movie" + } + } + }, + "shape": { + "type": "object", + "properties": [ + { + "name": { + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + }, + "wireValue": "id" + }, + "value": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_imdb:MovieId", + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "name": { + "originalName": "MovieId", + "camelCase": { + "unsafeName": "movieID", + "safeName": "movieID" + }, + "snakeCase": { + "unsafeName": "movie_id", + "safeName": "movie_id" + }, + "screamingSnakeCase": { + "unsafeName": "MOVIE_ID", + "safeName": "MOVIE_ID" + }, + "pascalCase": { + "unsafeName": "MovieID", + "safeName": "MovieID" + } + } + }, + "shape": { + "type": "alias", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "tt0111161" + } + } + }, + "jsonExample": "tt0111161" + } + } + }, + "jsonExample": "tt0111161" + }, + "originalTypeDeclaration": { + "typeId": "type_imdb:Movie", + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "name": { + "originalName": "Movie", + "camelCase": { + "unsafeName": "movie", + "safeName": "movie" + }, + "snakeCase": { + "unsafeName": "movie", + "safeName": "movie" + }, + "screamingSnakeCase": { + "unsafeName": "MOVIE", + "safeName": "MOVIE" + }, + "pascalCase": { + "unsafeName": "Movie", + "safeName": "Movie" + } + } + } + }, + { + "name": { + "name": { + "originalName": "title", + "camelCase": { + "unsafeName": "title", + "safeName": "title" + }, + "snakeCase": { + "unsafeName": "title", + "safeName": "title" + }, + "screamingSnakeCase": { + "unsafeName": "TITLE", + "safeName": "TITLE" + }, + "pascalCase": { + "unsafeName": "Title", + "safeName": "Title" + } + }, + "wireValue": "title" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "The Shawshank Redemption" + } + } + }, + "jsonExample": "The Shawshank Redemption" + }, + "originalTypeDeclaration": { + "typeId": "type_imdb:Movie", + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "name": { + "originalName": "Movie", + "camelCase": { + "unsafeName": "movie", + "safeName": "movie" + }, + "snakeCase": { + "unsafeName": "movie", + "safeName": "movie" + }, + "screamingSnakeCase": { + "unsafeName": "MOVIE", + "safeName": "MOVIE" + }, + "pascalCase": { + "unsafeName": "Movie", + "safeName": "Movie" + } + } + } + }, + { + "name": { + "name": { + "originalName": "rating", + "camelCase": { + "unsafeName": "rating", + "safeName": "rating" + }, + "snakeCase": { + "unsafeName": "rating", + "safeName": "rating" + }, + "screamingSnakeCase": { + "unsafeName": "RATING", + "safeName": "RATING" + }, + "pascalCase": { + "unsafeName": "Rating", + "safeName": "Rating" + } + }, + "wireValue": "rating" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "double", + "double": 9.3 + } + }, + "jsonExample": 9.3 + }, + "originalTypeDeclaration": { + "typeId": "type_imdb:Movie", + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "name": { + "originalName": "Movie", + "camelCase": { + "unsafeName": "movie", + "safeName": "movie" + }, + "snakeCase": { + "unsafeName": "movie", + "safeName": "movie" + }, + "screamingSnakeCase": { + "unsafeName": "MOVIE", + "safeName": "MOVIE" + }, + "pascalCase": { + "unsafeName": "Movie", + "safeName": "Movie" + } + } + } + } + ] + } + }, + "jsonExample": { + "id": "tt0111161", + "title": "The Shawshank Redemption", + "rating": 9.3 + } + } + } + }, + "docs": null + }, + "codeSamples": null + }, + { + "example": { + "id": null, + "name": null, + "url": "/movies/tt1234", + "rootPathParameters": [], + "endpointPathParameters": [ + { + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + }, + "value": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_imdb:MovieId", + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "name": { + "originalName": "MovieId", + "camelCase": { + "unsafeName": "movieID", + "safeName": "movieID" + }, + "snakeCase": { + "unsafeName": "movie_id", + "safeName": "movie_id" + }, + "screamingSnakeCase": { + "unsafeName": "MOVIE_ID", + "safeName": "MOVIE_ID" + }, + "pascalCase": { + "unsafeName": "MovieID", + "safeName": "MovieID" + } + } + }, + "shape": { + "type": "alias", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "tt1234" + } + } + }, + "jsonExample": "tt1234" + } + } + }, + "jsonExample": "tt1234" + } + } + ], + "servicePathParameters": [], + "endpointHeaders": [], + "serviceHeaders": [], + "queryParameters": [], + "request": null, + "response": { + "type": "error", + "error": { + "name": { + "originalName": "MovieDoesNotExistError", + "camelCase": { + "unsafeName": "movieDoesNotExistError", + "safeName": "movieDoesNotExistError" + }, + "snakeCase": { + "unsafeName": "movie_does_not_exist_error", + "safeName": "movie_does_not_exist_error" + }, + "screamingSnakeCase": { + "unsafeName": "MOVIE_DOES_NOT_EXIST_ERROR", + "safeName": "MOVIE_DOES_NOT_EXIST_ERROR" + }, + "pascalCase": { + "unsafeName": "MovieDoesNotExistError", + "safeName": "MovieDoesNotExistError" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "errorId": "error_imdb:MovieDoesNotExistError" + }, + "body": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_imdb:MovieId", + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "name": { + "originalName": "MovieId", + "camelCase": { + "unsafeName": "movieID", + "safeName": "movieID" + }, + "snakeCase": { + "unsafeName": "movie_id", + "safeName": "movie_id" + }, + "screamingSnakeCase": { + "unsafeName": "MOVIE_ID", + "safeName": "MOVIE_ID" + }, + "pascalCase": { + "unsafeName": "MovieID", + "safeName": "MovieID" + } + } + }, + "shape": { + "type": "alias", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "tt1234" + } + } + }, + "jsonExample": "tt1234" + } + } + }, + "jsonExample": "tt1234" + } + }, + "docs": null + }, + "codeSamples": null + } + ], + "autogeneratedExamples": [ + { + "example": { + "url": "/movies/string", + "rootPathParameters": [], + "servicePathParameters": [], + "endpointPathParameters": [ + { + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + }, + "value": { + "shape": { + "type": "named", + "typeName": { + "name": { + "originalName": "MovieId", + "camelCase": { + "unsafeName": "movieID", + "safeName": "movieID" + }, + "snakeCase": { + "unsafeName": "movie_id", + "safeName": "movie_id" + }, + "screamingSnakeCase": { + "unsafeName": "MOVIE_ID", + "safeName": "MOVIE_ID" + }, + "pascalCase": { + "unsafeName": "MovieID", + "safeName": "MovieID" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "typeId": "type_imdb:MovieId" + }, + "shape": { + "type": "alias", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "string" + } + } + }, + "jsonExample": "string" + } + } + }, + "jsonExample": "string" + } + } + ], + "serviceHeaders": [], + "endpointHeaders": [], + "queryParameters": [], + "request": null, + "name": null, + "response": { + "type": "ok", + "value": { + "type": "body", + "value": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_imdb:Movie", + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "name": { + "originalName": "Movie", + "camelCase": { + "unsafeName": "movie", + "safeName": "movie" + }, + "snakeCase": { + "unsafeName": "movie", + "safeName": "movie" + }, + "screamingSnakeCase": { + "unsafeName": "MOVIE", + "safeName": "MOVIE" + }, + "pascalCase": { + "unsafeName": "Movie", + "safeName": "Movie" + } + } + }, + "shape": { + "type": "object", + "properties": [ + { + "name": { + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + }, + "wireValue": "id" + }, + "value": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_imdb:MovieId", + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "name": { + "originalName": "MovieId", + "camelCase": { + "unsafeName": "movieID", + "safeName": "movieID" + }, + "snakeCase": { + "unsafeName": "movie_id", + "safeName": "movie_id" + }, + "screamingSnakeCase": { + "unsafeName": "MOVIE_ID", + "safeName": "MOVIE_ID" + }, + "pascalCase": { + "unsafeName": "MovieID", + "safeName": "MovieID" + } + } + }, + "shape": { + "type": "alias", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "tt0111161" + } + } + }, + "jsonExample": "tt0111161" + } + } + }, + "jsonExample": "tt0111161" + }, + "originalTypeDeclaration": { + "typeId": "type_imdb:Movie", + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "name": { + "originalName": "Movie", + "camelCase": { + "unsafeName": "movie", + "safeName": "movie" + }, + "snakeCase": { + "unsafeName": "movie", + "safeName": "movie" + }, + "screamingSnakeCase": { + "unsafeName": "MOVIE", + "safeName": "MOVIE" + }, + "pascalCase": { + "unsafeName": "Movie", + "safeName": "Movie" + } + } + } + }, + { + "name": { + "name": { + "originalName": "title", + "camelCase": { + "unsafeName": "title", + "safeName": "title" + }, + "snakeCase": { + "unsafeName": "title", + "safeName": "title" + }, + "screamingSnakeCase": { + "unsafeName": "TITLE", + "safeName": "TITLE" + }, + "pascalCase": { + "unsafeName": "Title", + "safeName": "Title" + } + }, + "wireValue": "title" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "The Shawshank Redemption" + } + } + }, + "jsonExample": "The Shawshank Redemption" + }, + "originalTypeDeclaration": { + "typeId": "type_imdb:Movie", + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "name": { + "originalName": "Movie", + "camelCase": { + "unsafeName": "movie", + "safeName": "movie" + }, + "snakeCase": { + "unsafeName": "movie", + "safeName": "movie" + }, + "screamingSnakeCase": { + "unsafeName": "MOVIE", + "safeName": "MOVIE" + }, + "pascalCase": { + "unsafeName": "Movie", + "safeName": "Movie" + } + } + } + }, + { + "name": { + "name": { + "originalName": "rating", + "camelCase": { + "unsafeName": "rating", + "safeName": "rating" + }, + "snakeCase": { + "unsafeName": "rating", + "safeName": "rating" + }, + "screamingSnakeCase": { + "unsafeName": "RATING", + "safeName": "RATING" + }, + "pascalCase": { + "unsafeName": "Rating", + "safeName": "Rating" + } + }, + "wireValue": "rating" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "double", + "double": 9.3 + } + }, + "jsonExample": 9.3 + }, + "originalTypeDeclaration": { + "typeId": "type_imdb:Movie", + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "name": { + "originalName": "Movie", + "camelCase": { + "unsafeName": "movie", + "safeName": "movie" + }, + "snakeCase": { + "unsafeName": "movie", + "safeName": "movie" + }, + "screamingSnakeCase": { + "unsafeName": "MOVIE", + "safeName": "MOVIE" + }, + "pascalCase": { + "unsafeName": "Movie", + "safeName": "Movie" + } + } + } + } + ] + } + }, + "jsonExample": { + "id": "tt0111161", + "title": "The Shawshank Redemption", + "rating": 9.3 + } + } + } + }, + "id": "2fbbd1d9a47777de004873d1c386e0e010f84d09", + "docs": null + } + }, + { + "example": { + "url": "/movies/string", + "rootPathParameters": [], + "servicePathParameters": [], + "endpointPathParameters": [ + { + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + }, + "value": { + "shape": { + "type": "named", + "typeName": { + "name": { + "originalName": "MovieId", + "camelCase": { + "unsafeName": "movieID", + "safeName": "movieID" + }, + "snakeCase": { + "unsafeName": "movie_id", + "safeName": "movie_id" + }, + "screamingSnakeCase": { + "unsafeName": "MOVIE_ID", + "safeName": "MOVIE_ID" + }, + "pascalCase": { + "unsafeName": "MovieID", + "safeName": "MovieID" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "typeId": "type_imdb:MovieId" + }, + "shape": { + "type": "alias", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "string" + } + } + }, + "jsonExample": "string" + } + } + }, + "jsonExample": "string" + } + } + ], + "serviceHeaders": [], + "endpointHeaders": [], + "queryParameters": [], + "request": null, + "name": null, + "response": { + "type": "ok", + "value": { + "type": "body", + "value": { + "shape": { + "type": "named", + "typeName": { + "name": { + "originalName": "Movie", + "camelCase": { + "unsafeName": "movie", + "safeName": "movie" + }, + "snakeCase": { + "unsafeName": "movie", + "safeName": "movie" + }, + "screamingSnakeCase": { + "unsafeName": "MOVIE", + "safeName": "MOVIE" + }, + "pascalCase": { + "unsafeName": "Movie", + "safeName": "Movie" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "typeId": "type_imdb:Movie" + }, + "shape": { + "type": "object", + "properties": [ + { + "name": { + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + }, + "wireValue": "id" + }, + "value": { + "shape": { + "type": "named", + "typeName": { + "name": { + "originalName": "MovieId", + "camelCase": { + "unsafeName": "movieID", + "safeName": "movieID" + }, + "snakeCase": { + "unsafeName": "movie_id", + "safeName": "movie_id" + }, + "screamingSnakeCase": { + "unsafeName": "MOVIE_ID", + "safeName": "MOVIE_ID" + }, + "pascalCase": { + "unsafeName": "MovieID", + "safeName": "MovieID" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "typeId": "type_imdb:MovieId" + }, + "shape": { + "type": "alias", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "string" + } + } + }, + "jsonExample": "string" + } + } + }, + "jsonExample": "string" + }, + "originalTypeDeclaration": { + "name": { + "originalName": "Movie", + "camelCase": { + "unsafeName": "movie", + "safeName": "movie" + }, + "snakeCase": { + "unsafeName": "movie", + "safeName": "movie" + }, + "screamingSnakeCase": { + "unsafeName": "MOVIE", + "safeName": "MOVIE" + }, + "pascalCase": { + "unsafeName": "Movie", + "safeName": "Movie" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "typeId": "type_imdb:Movie" + } + }, + { + "name": { + "name": { + "originalName": "title", + "camelCase": { + "unsafeName": "title", + "safeName": "title" + }, + "snakeCase": { + "unsafeName": "title", + "safeName": "title" + }, + "screamingSnakeCase": { + "unsafeName": "TITLE", + "safeName": "TITLE" + }, + "pascalCase": { + "unsafeName": "Title", + "safeName": "Title" + } + }, + "wireValue": "title" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "string" + } + } + }, + "jsonExample": "string" + }, + "originalTypeDeclaration": { + "name": { + "originalName": "Movie", + "camelCase": { + "unsafeName": "movie", + "safeName": "movie" + }, + "snakeCase": { + "unsafeName": "movie", + "safeName": "movie" + }, + "screamingSnakeCase": { + "unsafeName": "MOVIE", + "safeName": "MOVIE" + }, + "pascalCase": { + "unsafeName": "Movie", + "safeName": "Movie" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "typeId": "type_imdb:Movie" + } + }, + { + "name": { + "name": { + "originalName": "rating", + "camelCase": { + "unsafeName": "rating", + "safeName": "rating" + }, + "snakeCase": { + "unsafeName": "rating", + "safeName": "rating" + }, + "screamingSnakeCase": { + "unsafeName": "RATING", + "safeName": "RATING" + }, + "pascalCase": { + "unsafeName": "Rating", + "safeName": "Rating" + } + }, + "wireValue": "rating" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "double", + "double": 1.1 + } + }, + "jsonExample": 1.1 + }, + "originalTypeDeclaration": { + "name": { + "originalName": "Movie", + "camelCase": { + "unsafeName": "movie", + "safeName": "movie" + }, + "snakeCase": { + "unsafeName": "movie", + "safeName": "movie" + }, + "screamingSnakeCase": { + "unsafeName": "MOVIE", + "safeName": "MOVIE" + }, + "pascalCase": { + "unsafeName": "Movie", + "safeName": "Movie" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "typeId": "type_imdb:Movie" + } + } + ] + } + }, + "jsonExample": { + "id": "string", + "title": "string", + "rating": 1.1 + } + } + } + }, + "id": "bddb18e0df4a21ec0a1d0f2de6f81effa3fe4a52", + "docs": null + } + }, + { + "example": { + "url": "/movies/string", + "rootPathParameters": [], + "servicePathParameters": [], + "endpointPathParameters": [ + { + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + }, + "value": { + "shape": { + "type": "named", + "typeName": { + "name": { + "originalName": "MovieId", + "camelCase": { + "unsafeName": "movieID", + "safeName": "movieID" + }, + "snakeCase": { + "unsafeName": "movie_id", + "safeName": "movie_id" + }, + "screamingSnakeCase": { + "unsafeName": "MOVIE_ID", + "safeName": "MOVIE_ID" + }, + "pascalCase": { + "unsafeName": "MovieID", + "safeName": "MovieID" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "typeId": "type_imdb:MovieId" + }, + "shape": { + "type": "alias", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "string" + } + } + }, + "jsonExample": "string" + } + } + }, + "jsonExample": "string" + } + } + ], + "serviceHeaders": [], + "endpointHeaders": [], + "queryParameters": [], + "request": null, + "name": null, + "response": { + "type": "error", + "error": { + "name": { + "originalName": "MovieDoesNotExistError", + "camelCase": { + "unsafeName": "movieDoesNotExistError", + "safeName": "movieDoesNotExistError" + }, + "snakeCase": { + "unsafeName": "movie_does_not_exist_error", + "safeName": "movie_does_not_exist_error" + }, + "screamingSnakeCase": { + "unsafeName": "MOVIE_DOES_NOT_EXIST_ERROR", + "safeName": "MOVIE_DOES_NOT_EXIST_ERROR" + }, + "pascalCase": { + "unsafeName": "MovieDoesNotExistError", + "safeName": "MovieDoesNotExistError" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "errorId": "error_imdb:MovieDoesNotExistError" + }, + "body": null + }, + "id": "caa8c82f67c291fe8638d9e341d06b605b1958c0", + "docs": null + } + } + ], + "pagination": null, + "availability": null, + "docs": "Retrieve a movie from the database based on the ID" + } + ] + } + }, + "constants": { + "errorInstanceIdKey": { + "name": { + "originalName": "errorInstanceId", + "camelCase": { + "unsafeName": "errorInstanceID", + "safeName": "errorInstanceID" + }, + "snakeCase": { + "unsafeName": "error_instance_id", + "safeName": "error_instance_id" + }, + "screamingSnakeCase": { + "unsafeName": "ERROR_INSTANCE_ID", + "safeName": "ERROR_INSTANCE_ID" + }, + "pascalCase": { + "unsafeName": "ErrorInstanceID", + "safeName": "ErrorInstanceID" + } + }, + "wireValue": "errorInstanceId" + } + }, + "environments": { + "defaultEnvironment": null, + "environments": { + "type": "singleBaseUrl", + "environments": [ + { + "id": "Production", + "name": { + "originalName": "Production", + "camelCase": { + "unsafeName": "production", + "safeName": "production" + }, + "snakeCase": { + "unsafeName": "production", + "safeName": "production" + }, + "screamingSnakeCase": { + "unsafeName": "PRODUCTION", + "safeName": "PRODUCTION" + }, + "pascalCase": { + "unsafeName": "Production", + "safeName": "Production" + } + }, + "url": "https://www.yoursite.com", + "docs": null + }, + { + "id": "Staging", + "name": { + "originalName": "Staging", + "camelCase": { + "unsafeName": "staging", + "safeName": "staging" + }, + "snakeCase": { + "unsafeName": "staging", + "safeName": "staging" + }, + "screamingSnakeCase": { + "unsafeName": "STAGING", + "safeName": "STAGING" + }, + "pascalCase": { + "unsafeName": "Staging", + "safeName": "Staging" + } + }, + "url": "https://www.staging.yoursite.com", + "docs": null + } + ] + } + }, + "errorDiscriminationStrategy": { + "type": "statusCode" + }, + "basePath": null, + "pathParameters": [], + "variables": [], + "serviceTypeReferenceInfo": { + "typesReferencedOnlyByService": { + "service_imdb": [ + "type_imdb:MovieId", + "type_imdb:Movie", + "type_imdb:CreateMovieRequest" + ] + }, + "sharedTypes": [ + "type_webhooks:User" + ] + }, + "webhookGroups": { + "webhooks_webhooks": [ + { + "id": "webhooks_webhooks.userAdded", + "displayName": "User Added", + "method": "POST", + "name": { + "originalName": "userAdded", + "camelCase": { + "unsafeName": "userAdded", + "safeName": "userAdded" + }, + "snakeCase": { + "unsafeName": "user_added", + "safeName": "user_added" + }, + "screamingSnakeCase": { + "unsafeName": "USER_ADDED", + "safeName": "USER_ADDED" + }, + "pascalCase": { + "unsafeName": "UserAdded", + "safeName": "UserAdded" + } + }, + "headers": [], + "payload": { + "type": "reference", + "payloadType": { + "_type": "named", + "name": { + "originalName": "User", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "webhooks", + "camelCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "snakeCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "screamingSnakeCase": { + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" + }, + "pascalCase": { + "unsafeName": "Webhooks", + "safeName": "Webhooks" + } + } + ], + "packagePath": [], + "file": { + "originalName": "webhooks", + "camelCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "snakeCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "screamingSnakeCase": { + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" + }, + "pascalCase": { + "unsafeName": "Webhooks", + "safeName": "Webhooks" + } + } + }, + "typeId": "type_webhooks:User", + "default": null, + "inline": null + }, + "docs": null + }, + "examples": null, + "availability": null, + "docs": null + }, + { + "id": "webhooks_webhooks.userDeleted", + "displayName": "User Added", + "method": "POST", + "name": { + "originalName": "userDeleted", + "camelCase": { + "unsafeName": "userDeleted", + "safeName": "userDeleted" + }, + "snakeCase": { + "unsafeName": "user_deleted", + "safeName": "user_deleted" + }, + "screamingSnakeCase": { + "unsafeName": "USER_DELETED", + "safeName": "USER_DELETED" + }, + "pascalCase": { + "unsafeName": "UserDeleted", + "safeName": "UserDeleted" + } + }, + "headers": [], + "payload": { + "type": "reference", + "payloadType": { + "_type": "named", + "name": { + "originalName": "User", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "webhooks", + "camelCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "snakeCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "screamingSnakeCase": { + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" + }, + "pascalCase": { + "unsafeName": "Webhooks", + "safeName": "Webhooks" + } + } + ], + "packagePath": [], + "file": { + "originalName": "webhooks", + "camelCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "snakeCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "screamingSnakeCase": { + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" + }, + "pascalCase": { + "unsafeName": "Webhooks", + "safeName": "Webhooks" + } + } + }, + "typeId": "type_webhooks:User", + "default": null, + "inline": null + }, + "docs": null + }, + "examples": null, + "availability": null, + "docs": null + } + ] + }, + "websocketChannels": {}, + "readmeConfig": null, + "sourceConfig": null, + "subpackages": { + "subpackage_imdb": { + "name": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "service": "service_imdb", + "types": [ + "type_imdb:MovieId", + "type_imdb:Movie", + "type_imdb:CreateMovieRequest" + ], + "errors": [ + "error_imdb:MovieDoesNotExistError" + ], + "subpackages": [], + "navigationConfig": null, + "webhooks": null, + "websocket": null, + "hasEndpointsInTree": true, + "docs": null + }, + "subpackage_webhooks": { + "name": { + "originalName": "webhooks", + "camelCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "snakeCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "screamingSnakeCase": { + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" + }, + "pascalCase": { + "unsafeName": "Webhooks", + "safeName": "Webhooks" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "webhooks", + "camelCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "snakeCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "screamingSnakeCase": { + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" + }, + "pascalCase": { + "unsafeName": "Webhooks", + "safeName": "Webhooks" + } + } + ], + "packagePath": [], + "file": { + "originalName": "webhooks", + "camelCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "snakeCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "screamingSnakeCase": { + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" + }, + "pascalCase": { + "unsafeName": "Webhooks", + "safeName": "Webhooks" + } + } + }, + "service": null, + "types": [ + "type_webhooks:User" + ], + "errors": [], + "subpackages": [], + "navigationConfig": null, + "webhooks": "webhooks_webhooks", + "websocket": null, + "hasEndpointsInTree": false, + "docs": null + } + }, + "rootPackage": { + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "websocket": null, + "service": null, + "types": [], + "errors": [], + "subpackages": [ + "subpackage_imdb", + "subpackage_webhooks" + ], + "webhooks": null, + "navigationConfig": null, + "hasEndpointsInTree": true, + "docs": null + }, + "sdkConfig": { + "isAuthMandatory": false, + "hasStreamingEndpoints": false, + "hasPaginatedEndpoints": false, + "hasFileDownloadEndpoints": false, + "platformHeaders": { + "language": "X-Fern-Language", + "sdkName": "X-Fern-SDK-Name", + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null + } + } +} \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/irs/environmentAudiencesSelectHack.json b/packages/cli/generation/ir-generator/src/__test__/irs/environmentAudiencesSelectHack.json new file mode 100644 index 00000000000..8d82aebc03f --- /dev/null +++ b/packages/cli/generation/ir-generator/src/__test__/irs/environmentAudiencesSelectHack.json @@ -0,0 +1,1539 @@ +{ + "fdrApiDefinitionId": null, + "apiVersion": null, + "apiName": { + "originalName": "api", + "camelCase": { + "unsafeName": "api", + "safeName": "api" + }, + "snakeCase": { + "unsafeName": "api", + "safeName": "api" + }, + "screamingSnakeCase": { + "unsafeName": "API", + "safeName": "API" + }, + "pascalCase": { + "unsafeName": "API", + "safeName": "API" + } + }, + "apiDisplayName": null, + "apiDocs": null, + "auth": { + "requirement": "ALL", + "schemes": [], + "docs": null + }, + "headers": [], + "idempotencyHeaders": [], + "types": { + "type_imdb:MovieId": { + "name": { + "name": { + "originalName": "MovieId", + "camelCase": { + "unsafeName": "movieID", + "safeName": "movieID" + }, + "snakeCase": { + "unsafeName": "movie_id", + "safeName": "movie_id" + }, + "screamingSnakeCase": { + "unsafeName": "MOVIE_ID", + "safeName": "MOVIE_ID" + }, + "pascalCase": { + "unsafeName": "MovieID", + "safeName": "MovieID" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "typeId": "type_imdb:MovieId" + }, + "shape": { + "_type": "alias", + "aliasOf": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "resolvedType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + }, + "referencedTypes": [], + "encoding": null, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "availability": null, + "docs": "The unique identifier for a Movie in the database" + }, + "type_imdb:CreateMovieRequest": { + "name": { + "name": { + "originalName": "CreateMovieRequest", + "camelCase": { + "unsafeName": "createMovieRequest", + "safeName": "createMovieRequest" + }, + "snakeCase": { + "unsafeName": "create_movie_request", + "safeName": "create_movie_request" + }, + "screamingSnakeCase": { + "unsafeName": "CREATE_MOVIE_REQUEST", + "safeName": "CREATE_MOVIE_REQUEST" + }, + "pascalCase": { + "unsafeName": "CreateMovieRequest", + "safeName": "CreateMovieRequest" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "typeId": "type_imdb:CreateMovieRequest" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": { + "name": { + "originalName": "title", + "camelCase": { + "unsafeName": "title", + "safeName": "title" + }, + "snakeCase": { + "unsafeName": "title", + "safeName": "title" + }, + "screamingSnakeCase": { + "unsafeName": "TITLE", + "safeName": "TITLE" + }, + "pascalCase": { + "unsafeName": "Title", + "safeName": "Title" + } + }, + "wireValue": "title" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "rating", + "camelCase": { + "unsafeName": "rating", + "safeName": "rating" + }, + "snakeCase": { + "unsafeName": "rating", + "safeName": "rating" + }, + "screamingSnakeCase": { + "unsafeName": "RATING", + "safeName": "RATING" + }, + "pascalCase": { + "unsafeName": "Rating", + "safeName": "Rating" + } + }, + "wireValue": "rating" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "DOUBLE", + "v2": { + "type": "double", + "default": null, + "validation": null + } + } + }, + "availability": null, + "docs": null + } + ], + "extra-properties": false, + "extendedProperties": [] + }, + "referencedTypes": [], + "encoding": null, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "availability": null, + "docs": null + }, + "type_webhooks:User": { + "name": { + "name": { + "originalName": "User", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "webhooks", + "camelCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "snakeCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "screamingSnakeCase": { + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" + }, + "pascalCase": { + "unsafeName": "Webhooks", + "safeName": "Webhooks" + } + } + ], + "packagePath": [], + "file": { + "originalName": "webhooks", + "camelCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "snakeCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "screamingSnakeCase": { + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" + }, + "pascalCase": { + "unsafeName": "Webhooks", + "safeName": "Webhooks" + } + } + }, + "typeId": "type_webhooks:User" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": { + "name": { + "originalName": "name", + "camelCase": { + "unsafeName": "name", + "safeName": "name" + }, + "snakeCase": { + "unsafeName": "name", + "safeName": "name" + }, + "screamingSnakeCase": { + "unsafeName": "NAME", + "safeName": "NAME" + }, + "pascalCase": { + "unsafeName": "Name", + "safeName": "Name" + } + }, + "wireValue": "name" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "availability": null, + "docs": null + } + ], + "extra-properties": false, + "extendedProperties": [] + }, + "referencedTypes": [], + "encoding": null, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "availability": null, + "docs": null + } + }, + "errors": {}, + "services": { + "service_imdb": { + "availability": null, + "name": { + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + } + }, + "displayName": null, + "basePath": { + "head": "/movies", + "parts": [] + }, + "headers": [], + "pathParameters": [], + "encoding": null, + "transport": null, + "endpoints": [ + { + "id": "endpoint_imdb.createMovie", + "name": { + "originalName": "createMovie", + "camelCase": { + "unsafeName": "createMovie", + "safeName": "createMovie" + }, + "snakeCase": { + "unsafeName": "create_movie", + "safeName": "create_movie" + }, + "screamingSnakeCase": { + "unsafeName": "CREATE_MOVIE", + "safeName": "CREATE_MOVIE" + }, + "pascalCase": { + "unsafeName": "CreateMovie", + "safeName": "CreateMovie" + } + }, + "displayName": null, + "auth": false, + "idempotent": false, + "baseUrl": null, + "method": "POST", + "path": { + "head": "/create-movie", + "parts": [] + }, + "fullPath": { + "head": "/movies/create-movie", + "parts": [] + }, + "pathParameters": [], + "allPathParameters": [], + "queryParameters": [], + "headers": [], + "requestBody": { + "type": "reference", + "requestBodyType": { + "_type": "named", + "name": { + "originalName": "CreateMovieRequest", + "camelCase": { + "unsafeName": "createMovieRequest", + "safeName": "createMovieRequest" + }, + "snakeCase": { + "unsafeName": "create_movie_request", + "safeName": "create_movie_request" + }, + "screamingSnakeCase": { + "unsafeName": "CREATE_MOVIE_REQUEST", + "safeName": "CREATE_MOVIE_REQUEST" + }, + "pascalCase": { + "unsafeName": "CreateMovieRequest", + "safeName": "CreateMovieRequest" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "typeId": "type_imdb:CreateMovieRequest", + "default": null, + "inline": null + }, + "contentType": null, + "docs": null + }, + "sdkRequest": { + "shape": { + "type": "justRequestBody", + "value": { + "type": "typeReference", + "requestBodyType": { + "_type": "named", + "name": { + "originalName": "CreateMovieRequest", + "camelCase": { + "unsafeName": "createMovieRequest", + "safeName": "createMovieRequest" + }, + "snakeCase": { + "unsafeName": "create_movie_request", + "safeName": "create_movie_request" + }, + "screamingSnakeCase": { + "unsafeName": "CREATE_MOVIE_REQUEST", + "safeName": "CREATE_MOVIE_REQUEST" + }, + "pascalCase": { + "unsafeName": "CreateMovieRequest", + "safeName": "CreateMovieRequest" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "typeId": "type_imdb:CreateMovieRequest", + "default": null, + "inline": null + }, + "contentType": null, + "docs": null + } + }, + "requestParameterName": { + "originalName": "request", + "camelCase": { + "unsafeName": "request", + "safeName": "request" + }, + "snakeCase": { + "unsafeName": "request", + "safeName": "request" + }, + "screamingSnakeCase": { + "unsafeName": "REQUEST", + "safeName": "REQUEST" + }, + "pascalCase": { + "unsafeName": "Request", + "safeName": "Request" + } + }, + "streamParameter": null + }, + "response": { + "body": { + "type": "json", + "value": { + "type": "response", + "responseBodyType": { + "_type": "named", + "name": { + "originalName": "MovieId", + "camelCase": { + "unsafeName": "movieID", + "safeName": "movieID" + }, + "snakeCase": { + "unsafeName": "movie_id", + "safeName": "movie_id" + }, + "screamingSnakeCase": { + "unsafeName": "MOVIE_ID", + "safeName": "MOVIE_ID" + }, + "pascalCase": { + "unsafeName": "MovieID", + "safeName": "MovieID" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "typeId": "type_imdb:MovieId", + "default": null, + "inline": null + }, + "docs": null + } + }, + "status-code": null + }, + "errors": [], + "userSpecifiedExamples": [], + "autogeneratedExamples": [ + { + "example": { + "url": "/movies/create-movie", + "rootPathParameters": [], + "servicePathParameters": [], + "endpointPathParameters": [], + "serviceHeaders": [], + "endpointHeaders": [], + "queryParameters": [], + "request": { + "type": "reference", + "shape": { + "type": "named", + "typeName": { + "name": { + "originalName": "CreateMovieRequest", + "camelCase": { + "unsafeName": "createMovieRequest", + "safeName": "createMovieRequest" + }, + "snakeCase": { + "unsafeName": "create_movie_request", + "safeName": "create_movie_request" + }, + "screamingSnakeCase": { + "unsafeName": "CREATE_MOVIE_REQUEST", + "safeName": "CREATE_MOVIE_REQUEST" + }, + "pascalCase": { + "unsafeName": "CreateMovieRequest", + "safeName": "CreateMovieRequest" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "typeId": "type_imdb:CreateMovieRequest" + }, + "shape": { + "type": "object", + "properties": [ + { + "name": { + "name": { + "originalName": "title", + "camelCase": { + "unsafeName": "title", + "safeName": "title" + }, + "snakeCase": { + "unsafeName": "title", + "safeName": "title" + }, + "screamingSnakeCase": { + "unsafeName": "TITLE", + "safeName": "TITLE" + }, + "pascalCase": { + "unsafeName": "Title", + "safeName": "Title" + } + }, + "wireValue": "title" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "string" + } + } + }, + "jsonExample": "string" + }, + "originalTypeDeclaration": { + "name": { + "originalName": "CreateMovieRequest", + "camelCase": { + "unsafeName": "createMovieRequest", + "safeName": "createMovieRequest" + }, + "snakeCase": { + "unsafeName": "create_movie_request", + "safeName": "create_movie_request" + }, + "screamingSnakeCase": { + "unsafeName": "CREATE_MOVIE_REQUEST", + "safeName": "CREATE_MOVIE_REQUEST" + }, + "pascalCase": { + "unsafeName": "CreateMovieRequest", + "safeName": "CreateMovieRequest" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "typeId": "type_imdb:CreateMovieRequest" + } + }, + { + "name": { + "name": { + "originalName": "rating", + "camelCase": { + "unsafeName": "rating", + "safeName": "rating" + }, + "snakeCase": { + "unsafeName": "rating", + "safeName": "rating" + }, + "screamingSnakeCase": { + "unsafeName": "RATING", + "safeName": "RATING" + }, + "pascalCase": { + "unsafeName": "Rating", + "safeName": "Rating" + } + }, + "wireValue": "rating" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "double", + "double": 1.1 + } + }, + "jsonExample": 1.1 + }, + "originalTypeDeclaration": { + "name": { + "originalName": "CreateMovieRequest", + "camelCase": { + "unsafeName": "createMovieRequest", + "safeName": "createMovieRequest" + }, + "snakeCase": { + "unsafeName": "create_movie_request", + "safeName": "create_movie_request" + }, + "screamingSnakeCase": { + "unsafeName": "CREATE_MOVIE_REQUEST", + "safeName": "CREATE_MOVIE_REQUEST" + }, + "pascalCase": { + "unsafeName": "CreateMovieRequest", + "safeName": "CreateMovieRequest" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "typeId": "type_imdb:CreateMovieRequest" + } + } + ] + } + }, + "jsonExample": { + "title": "string", + "rating": 1.1 + } + }, + "name": null, + "response": { + "type": "ok", + "value": { + "type": "body", + "value": { + "shape": { + "type": "named", + "typeName": { + "name": { + "originalName": "MovieId", + "camelCase": { + "unsafeName": "movieID", + "safeName": "movieID" + }, + "snakeCase": { + "unsafeName": "movie_id", + "safeName": "movie_id" + }, + "screamingSnakeCase": { + "unsafeName": "MOVIE_ID", + "safeName": "MOVIE_ID" + }, + "pascalCase": { + "unsafeName": "MovieID", + "safeName": "MovieID" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "typeId": "type_imdb:MovieId" + }, + "shape": { + "type": "alias", + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "string" + } + } + }, + "jsonExample": "string" + } + } + }, + "jsonExample": "string" + } + } + }, + "id": "0fd01d62bca0126c84c6dae0eb32bd903b886a36", + "docs": null + } + } + ], + "pagination": null, + "availability": null, + "docs": "Add a movie to the database" + } + ] + } + }, + "constants": { + "errorInstanceIdKey": { + "name": { + "originalName": "errorInstanceId", + "camelCase": { + "unsafeName": "errorInstanceID", + "safeName": "errorInstanceID" + }, + "snakeCase": { + "unsafeName": "error_instance_id", + "safeName": "error_instance_id" + }, + "screamingSnakeCase": { + "unsafeName": "ERROR_INSTANCE_ID", + "safeName": "ERROR_INSTANCE_ID" + }, + "pascalCase": { + "unsafeName": "ErrorInstanceID", + "safeName": "ErrorInstanceID" + } + }, + "wireValue": "errorInstanceId" + } + }, + "environments": { + "defaultEnvironment": null, + "environments": { + "type": "singleBaseUrl", + "environments": [ + { + "id": "Production", + "name": { + "originalName": "Production", + "camelCase": { + "unsafeName": "production", + "safeName": "production" + }, + "snakeCase": { + "unsafeName": "production", + "safeName": "production" + }, + "screamingSnakeCase": { + "unsafeName": "PRODUCTION", + "safeName": "PRODUCTION" + }, + "pascalCase": { + "unsafeName": "Production", + "safeName": "Production" + } + }, + "url": "https://www.yoursite.com", + "docs": null + }, + { + "id": "Staging", + "name": { + "originalName": "Staging", + "camelCase": { + "unsafeName": "staging", + "safeName": "staging" + }, + "snakeCase": { + "unsafeName": "staging", + "safeName": "staging" + }, + "screamingSnakeCase": { + "unsafeName": "STAGING", + "safeName": "STAGING" + }, + "pascalCase": { + "unsafeName": "Staging", + "safeName": "Staging" + } + }, + "url": "https://www.staging.yoursite.com", + "docs": null + } + ] + } + }, + "errorDiscriminationStrategy": { + "type": "statusCode" + }, + "basePath": null, + "pathParameters": [], + "variables": [], + "serviceTypeReferenceInfo": { + "sharedTypes": [ + "type_webhooks:User" + ], + "typesReferencedOnlyByService": { + "service_imdb": [ + "type_imdb:MovieId", + "type_imdb:CreateMovieRequest" + ] + } + }, + "webhookGroups": { + "webhooks_webhooks": [ + { + "id": "webhooks_webhooks.userAdded", + "displayName": "User Added", + "method": "POST", + "name": { + "originalName": "userAdded", + "camelCase": { + "unsafeName": "userAdded", + "safeName": "userAdded" + }, + "snakeCase": { + "unsafeName": "user_added", + "safeName": "user_added" + }, + "screamingSnakeCase": { + "unsafeName": "USER_ADDED", + "safeName": "USER_ADDED" + }, + "pascalCase": { + "unsafeName": "UserAdded", + "safeName": "UserAdded" + } + }, + "headers": [], + "payload": { + "type": "reference", + "payloadType": { + "_type": "named", + "name": { + "originalName": "User", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "webhooks", + "camelCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "snakeCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "screamingSnakeCase": { + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" + }, + "pascalCase": { + "unsafeName": "Webhooks", + "safeName": "Webhooks" + } + } + ], + "packagePath": [], + "file": { + "originalName": "webhooks", + "camelCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "snakeCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "screamingSnakeCase": { + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" + }, + "pascalCase": { + "unsafeName": "Webhooks", + "safeName": "Webhooks" + } + } + }, + "typeId": "type_webhooks:User", + "default": null, + "inline": null + }, + "docs": null + }, + "examples": null, + "availability": null, + "docs": null + } + ] + }, + "websocketChannels": {}, + "readmeConfig": null, + "sourceConfig": null, + "subpackages": { + "subpackage_imdb": { + "name": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + ], + "packagePath": [], + "file": { + "originalName": "imdb", + "camelCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "snakeCase": { + "unsafeName": "imdb", + "safeName": "imdb" + }, + "screamingSnakeCase": { + "unsafeName": "IMDB", + "safeName": "IMDB" + }, + "pascalCase": { + "unsafeName": "Imdb", + "safeName": "Imdb" + } + } + }, + "service": "service_imdb", + "types": [ + "type_imdb:MovieId", + "type_imdb:CreateMovieRequest" + ], + "errors": [], + "subpackages": [], + "navigationConfig": null, + "webhooks": null, + "websocket": null, + "hasEndpointsInTree": true, + "docs": null + }, + "subpackage_webhooks": { + "name": { + "originalName": "webhooks", + "camelCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "snakeCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "screamingSnakeCase": { + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" + }, + "pascalCase": { + "unsafeName": "Webhooks", + "safeName": "Webhooks" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "webhooks", + "camelCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "snakeCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "screamingSnakeCase": { + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" + }, + "pascalCase": { + "unsafeName": "Webhooks", + "safeName": "Webhooks" + } + } + ], + "packagePath": [], + "file": { + "originalName": "webhooks", + "camelCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "snakeCase": { + "unsafeName": "webhooks", + "safeName": "webhooks" + }, + "screamingSnakeCase": { + "unsafeName": "WEBHOOKS", + "safeName": "WEBHOOKS" + }, + "pascalCase": { + "unsafeName": "Webhooks", + "safeName": "Webhooks" + } + } + }, + "service": null, + "types": [ + "type_webhooks:User" + ], + "errors": [], + "subpackages": [], + "navigationConfig": null, + "webhooks": "webhooks_webhooks", + "websocket": null, + "hasEndpointsInTree": false, + "docs": null + } + }, + "rootPackage": { + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "websocket": null, + "service": null, + "types": [], + "errors": [], + "subpackages": [ + "subpackage_imdb", + "subpackage_webhooks" + ], + "webhooks": null, + "navigationConfig": null, + "hasEndpointsInTree": true, + "docs": null + }, + "sdkConfig": { + "isAuthMandatory": false, + "hasStreamingEndpoints": false, + "hasPaginatedEndpoints": false, + "hasFileDownloadEndpoints": false, + "platformHeaders": { + "language": "X-Fern-Language", + "sdkName": "X-Fern-SDK-Name", + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null + } + } +} \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/irs/fhir.json b/packages/cli/generation/ir-generator/src/__test__/irs/fhir.json index 0d17a989b1f..47e37cb0cda 100644 --- a/packages/cli/generation/ir-generator/src/__test__/irs/fhir.json +++ b/packages/cli/generation/ir-generator/src/__test__/irs/fhir.json @@ -6341,6 +6341,8 @@ "type_:AccountCoverage", "type_:AccountGuarantor" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -7861,6 +7863,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -7920,6 +7924,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -7971,6 +7977,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -8030,6 +8038,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -8089,6 +8099,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -8148,6 +8160,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -8207,6 +8221,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -8266,6 +8282,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -8325,6 +8343,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -8384,6 +8404,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -8443,6 +8465,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -8502,6 +8526,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -8561,6 +8587,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -8620,6 +8648,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -8679,6 +8709,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -8738,6 +8770,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -8797,6 +8831,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -8856,6 +8892,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -8915,6 +8953,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -8974,6 +9014,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -9017,6 +9059,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -10134,6 +10178,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -14048,6 +14094,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -15283,6 +15331,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -15427,6 +15477,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -16773,6 +16825,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -18367,6 +18421,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -19837,6 +19893,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -20007,6 +20065,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -21235,6 +21295,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -22601,6 +22663,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -24009,6 +24073,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -24153,6 +24219,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -25561,6 +25629,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -25705,6 +25775,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -27113,6 +27185,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -27257,6 +27331,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -28665,6 +28741,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -28809,6 +28887,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -30050,6 +30130,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -31458,6 +31540,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -31602,6 +31686,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -32843,6 +32929,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -34084,6 +34172,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -35325,6 +35415,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -36714,6 +36806,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -38240,6 +38334,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -39785,6 +39881,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -41259,6 +41357,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -41481,6 +41581,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -43091,6 +43193,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -43261,6 +43365,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -43379,6 +43485,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -44787,6 +44895,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -45009,6 +45119,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -45179,6 +45291,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -46556,6 +46670,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -48813,6 +48929,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -49035,6 +49153,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -49257,6 +49377,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -49973,6 +50095,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -51734,6 +51858,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -52962,6 +53088,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -54252,6 +54380,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -54396,6 +54526,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -56082,6 +56214,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -57483,6 +57617,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -58921,6 +59057,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -60211,6 +60349,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -60303,6 +60443,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -61797,6 +61939,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -63310,6 +63454,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -63558,6 +63704,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -65120,6 +65268,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -65368,6 +65518,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -66789,6 +66941,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -68774,6 +68928,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -70331,6 +70487,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -71826,6 +71984,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -73253,6 +73413,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -75081,6 +75243,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -76558,6 +76722,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -78077,6 +78243,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -79386,6 +79554,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -80775,6 +80945,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -80893,6 +81065,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -92678,6 +92852,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -92848,6 +93024,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -94245,6 +94423,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -94363,6 +94543,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -95653,6 +95835,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -95823,6 +96007,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -97156,6 +97342,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -98669,6 +98857,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -98787,6 +98977,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -98905,6 +99097,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -102868,6 +103062,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -104411,6 +104607,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -104503,6 +104701,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -105855,6 +106055,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -105999,6 +106201,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -107394,6 +107598,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -109647,6 +109853,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -109817,6 +110025,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -111120,6 +111330,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -112462,6 +112674,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -117216,6 +117430,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -117360,6 +117576,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -118669,6 +118887,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -119953,6 +120173,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -122814,6 +123036,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -122906,6 +123130,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -124215,6 +124441,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -125629,6 +125857,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -128434,6 +128664,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -128526,6 +128758,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -128670,6 +128904,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -128788,6 +129024,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -130394,6 +130632,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -130512,6 +130752,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -133464,6 +133706,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -133764,6 +134008,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -135265,6 +135511,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -135383,6 +135631,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -135527,6 +135777,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -137595,6 +137847,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -139849,6 +140103,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -140019,6 +140275,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -140163,6 +140421,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -141987,6 +142247,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -143277,6 +143539,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -143447,6 +143711,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -144799,6 +145065,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -146516,6 +146784,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -147830,6 +148100,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -149702,6 +149974,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -151178,6 +151452,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -153456,6 +153732,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -153626,6 +153904,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -153718,6 +153998,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -155132,6 +155414,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -156589,6 +156873,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -157922,6 +158208,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -159336,6 +159624,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -159454,6 +159744,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -161505,6 +161797,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -163241,6 +163535,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -163515,6 +163811,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -164805,6 +165103,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -166368,6 +166668,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -167677,6 +167979,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -167795,6 +168099,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -169295,6 +169601,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -169491,6 +169799,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -170948,6 +171258,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -174112,6 +174424,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -174256,6 +174570,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -174374,6 +174690,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -174986,6 +175304,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -176319,6 +176639,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -177671,6 +177993,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -179382,6 +179706,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -179474,6 +179800,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -180828,6 +181156,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -182979,6 +183309,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -183097,6 +183429,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -183241,6 +183575,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -183359,6 +183695,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -183529,6 +183867,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -184838,6 +185178,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -185112,6 +185454,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -186526,6 +186870,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -186800,6 +187146,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -188146,6 +188494,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -189455,6 +189805,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -189599,6 +189951,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -191044,6 +191398,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -192347,6 +192703,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -193650,6 +194008,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -193742,6 +194102,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -195107,6 +195469,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -195199,6 +195563,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -198250,6 +198616,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -199763,6 +200131,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -202163,6 +202533,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -202437,6 +202809,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -204857,6 +205231,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -205027,6 +205403,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -206466,6 +206844,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -208864,6 +209244,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -209008,6 +209390,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -210311,6 +210695,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -210403,6 +210789,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -213802,6 +214190,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -214024,6 +214414,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -215327,6 +215719,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -218272,6 +218666,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -218416,6 +218812,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -219730,6 +220128,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -221051,6 +221451,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -222484,6 +222886,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -225761,6 +226165,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -225879,6 +226285,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -227250,6 +227658,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -228553,6 +228963,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -230019,6 +230431,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -231819,6 +232233,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -233382,6 +233798,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -234951,6 +235369,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -236509,6 +236929,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -237919,6 +238341,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -240627,6 +241051,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -242574,6 +243000,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -244453,6 +244881,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -247710,6 +248140,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -249155,6 +249587,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -250582,6 +251016,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -252027,6 +252463,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -253410,6 +253848,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -255926,6 +256366,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -257681,6 +258123,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -259368,6 +259812,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -260665,6 +261111,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -262187,6 +262635,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -263601,6 +264051,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -263719,6 +264171,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -265166,6 +265620,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -266593,6 +267049,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -269471,6 +269929,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -270780,6 +271240,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -272132,6 +272594,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -275203,6 +275667,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -275347,6 +275813,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -275491,6 +275959,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -275661,6 +276131,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -277062,6 +277534,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -278476,6 +278950,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -278698,6 +279174,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -280235,6 +280713,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -281587,6 +282067,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -283150,6 +283632,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -286245,6 +286729,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -287597,6 +288083,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -290632,6 +291120,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -291984,6 +292474,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -294352,6 +294844,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -294496,6 +294990,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -294666,6 +295162,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -296005,6 +296503,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -298525,6 +299025,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -298669,6 +299171,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -300040,6 +300544,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -300184,6 +300690,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -301555,6 +302063,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -302938,6 +303448,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -304742,6 +305254,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -307416,6 +307930,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -307560,6 +308076,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -309079,6 +309597,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -310437,6 +310957,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -311968,6 +312490,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -312268,6 +312792,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -313663,6 +314189,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -315077,6 +315605,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -315195,6 +315725,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -318284,6 +318816,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -319661,6 +320195,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -320982,6 +321518,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -323459,6 +323997,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -323655,6 +324195,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -324964,6 +325506,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -326312,6 +326856,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -328227,6 +328773,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -328319,6 +328867,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -329616,6 +330166,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -330919,6 +331471,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -331063,6 +331617,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -334807,6 +335363,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -336358,6 +336916,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -338348,6 +338908,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -339793,6 +340355,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -341596,6 +342160,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -342899,6 +343465,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -344672,6 +345240,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -346796,6 +347366,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -348154,6 +348726,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -350243,6 +350817,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -352758,6 +353334,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -354067,6 +354645,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -355426,6 +356006,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -356735,6 +357317,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -358044,6 +358628,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -359353,6 +359939,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -361930,6 +362518,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -363257,6 +363847,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -364696,6 +365288,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -365999,6 +366593,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -368513,6 +369109,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -368657,6 +369255,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -369999,6 +370599,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -371322,6 +371924,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -373151,6 +373755,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -374460,6 +375066,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -376986,6 +377594,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -377130,6 +377740,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -377274,6 +377886,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -378684,6 +379298,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -380671,6 +381287,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -382208,6 +382826,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -383449,6 +384069,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -385826,6 +386448,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -385944,6 +386568,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -387265,6 +387891,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -388630,6 +389258,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -391773,6 +392403,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -391917,6 +392549,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -393436,6 +394070,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -393632,6 +394268,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -394922,6 +395560,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -395118,6 +395758,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -396402,6 +397044,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -397754,6 +398398,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -399131,6 +399777,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -402231,6 +402879,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -403583,6 +404233,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -404873,6 +405525,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -405069,6 +405723,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -406340,6 +406996,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -407649,6 +408307,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -409026,6 +409686,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -410345,6 +411007,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -411654,6 +412318,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -413865,6 +414531,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -414009,6 +414677,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -414257,6 +414927,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -414401,6 +415073,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -415772,6 +416446,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -415916,6 +416592,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -416060,6 +416738,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -419366,6 +420046,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -420838,6 +421520,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -423358,6 +424042,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -423554,6 +424240,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -426339,6 +427027,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -426639,6 +427329,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -427923,6 +428615,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -430257,6 +430951,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -430375,6 +431071,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -431684,6 +432382,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -434272,6 +434972,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -434390,6 +435092,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -435693,6 +436397,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -435837,6 +436543,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -437140,6 +437848,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -438777,6 +439487,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -442346,6 +443058,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -442490,6 +443204,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -443804,6 +444520,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -445212,6 +445930,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -445304,6 +446024,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -446848,6 +447570,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -448281,6 +449005,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -449670,6 +450396,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -451053,6 +451781,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -454136,6 +454866,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -454410,6 +455142,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -455713,6 +456447,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -455987,6 +456723,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -457284,6 +458022,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -458661,6 +459401,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -460026,6 +460768,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -461787,6 +462531,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -463214,6 +463960,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -463358,6 +464106,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -465605,6 +466355,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -465801,6 +466553,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -467826,6 +468580,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -469894,6 +470650,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -470038,6 +470796,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -472403,6 +473163,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -472625,6 +473387,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -473928,6 +474692,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -474150,6 +474916,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -475515,6 +476283,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -478775,6 +479545,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -478919,6 +479691,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -482152,6 +482926,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -482296,6 +483072,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -485461,6 +486239,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -485605,6 +486385,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -485723,6 +486505,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -487764,6 +488548,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -487960,6 +488746,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -490567,6 +491355,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -490711,6 +491501,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -492106,6 +492898,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -492198,6 +492992,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -493729,6 +494525,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -495019,6 +495817,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -496290,6 +497090,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -497772,6 +498574,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -499194,6 +499998,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -500858,6 +501664,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -502216,6 +503024,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -506489,6 +507299,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -506633,6 +507445,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -508004,6 +508818,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -509313,6 +510129,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -510779,6 +511597,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -512579,6 +513399,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -514142,6 +514964,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -515711,6 +516535,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -517040,6 +517866,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -518450,6 +519278,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -521294,6 +522124,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -522721,6 +523553,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -524804,6 +525638,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -526819,6 +527655,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -529341,6 +530179,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -531102,6 +531942,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -532795,6 +533637,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -534092,6 +534936,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -535626,6 +536472,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -537040,6 +537888,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -537158,6 +538008,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -538778,6 +539630,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -540272,6 +541126,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -543339,6 +544195,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -543483,6 +544341,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -545122,6 +545982,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -547203,6 +548065,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -547321,6 +548185,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -549929,6 +550795,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -550203,6 +551071,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -551928,6 +552798,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -554387,6 +555259,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -554531,6 +555405,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -555999,6 +556875,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -557487,6 +558365,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -558944,6 +559824,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -559036,6 +559918,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -559180,6 +560064,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -561338,6 +562224,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -561534,6 +562422,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -563163,6 +564053,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -564505,6 +565397,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -567142,6 +568036,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -567338,6 +568234,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -570404,6 +571302,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -571713,6 +572613,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -573129,6 +574031,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -573351,6 +574255,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -574641,6 +575547,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -577501,6 +578409,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -577671,6 +578581,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -579599,6 +580511,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -580902,6 +581816,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -582310,6 +583226,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -585615,6 +586533,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -586918,6 +587838,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -588332,6 +589254,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -589680,6 +590604,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -591210,6 +592136,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -593599,6 +594527,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -595494,6 +596424,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -597463,6 +598395,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -598766,6 +599700,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -601528,6 +602464,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -601672,6 +602610,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -610708,6 +611648,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -611320,6 +612262,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -612666,6 +613610,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -613969,6 +614915,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -615482,6 +616430,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -616753,6 +617703,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -618292,6 +619244,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -618904,6 +619858,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -620367,6 +621323,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -620511,6 +621469,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -621801,6 +622761,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -622101,6 +623063,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -623434,6 +624398,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -624909,6 +625875,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -626294,6 +627262,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -627614,6 +628584,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -630083,6 +631055,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -630227,6 +631201,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -631666,6 +632642,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -633037,6 +634015,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -634389,6 +635369,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -635698,6 +636680,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -637285,6 +638269,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -638699,6 +639685,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -640008,6 +640996,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -641317,6 +642307,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -642750,6 +643742,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -645338,6 +646332,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -645508,6 +646504,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -646811,6 +647809,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -648250,6 +649250,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -649683,6 +650685,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -649879,6 +650883,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -653337,6 +654343,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -653481,6 +654489,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -655229,6 +656239,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -656532,6 +657544,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -656650,6 +657664,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -659040,6 +660056,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -659158,6 +660176,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -659276,6 +660296,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -660680,6 +661702,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -663279,6 +664303,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -663397,6 +664423,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -663489,6 +664517,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -664860,6 +665890,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -666276,6 +667308,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -670334,6 +671368,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -670478,6 +671514,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -671904,6 +672942,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -673250,6 +674290,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -674670,6 +675712,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -676016,6 +677060,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -677430,6 +678476,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -679703,6 +680751,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -679821,6 +680871,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -679965,6 +681017,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -681410,6 +682464,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -682762,6 +683818,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -684083,6 +685141,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -685528,6 +686588,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -686825,6 +687887,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -688177,6 +689241,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -691174,6 +692240,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -693267,6 +694335,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -694677,6 +695747,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -695967,6 +697039,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -698895,6 +699969,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -700198,6 +701274,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -701798,6 +702876,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -705055,6 +706135,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -706358,6 +707440,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -707780,6 +708864,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -710800,6 +711886,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -712103,6 +713191,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -713412,6 +714502,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -714822,6 +715914,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -716162,6 +717256,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -717452,6 +718548,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -718897,6 +719995,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -720200,6 +721300,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -721558,6 +722660,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -722867,6 +723971,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -724176,6 +725282,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -725633,6 +726741,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -727072,6 +728182,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -728352,6 +729464,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -729593,6 +730707,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -730896,6 +732012,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -732279,6 +733397,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -735998,6 +737118,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -737617,6 +738739,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -738926,6 +740050,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -740274,6 +741400,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -743066,6 +744194,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -745950,6 +747080,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -747314,6 +748446,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -748598,6 +749732,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -749957,6 +751093,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -751520,6 +752658,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -753207,6 +754347,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -755808,6 +756950,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -757315,6 +758459,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -758791,6 +759937,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -760840,6 +761988,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -762205,6 +763355,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -764378,6 +765530,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -765743,6 +766897,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -767683,6 +768839,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -769110,6 +770268,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -770716,6 +771876,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -772192,6 +773354,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -773501,6 +774665,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -775513,6 +776679,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -776822,6 +777990,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -778847,6 +780017,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -781001,6 +782173,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -782304,6 +783478,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -784275,6 +785451,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -786312,6 +787490,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -787615,6 +788795,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -789234,6 +790416,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -790543,6 +791727,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -791883,6 +793069,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -793790,6 +794978,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -796861,6 +798051,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -797005,6 +798197,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -797123,6 +798317,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -797267,6 +798463,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -798681,6 +799879,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -799984,6 +801184,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -802300,6 +803502,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -803714,6 +804918,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -805152,6 +806358,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -806523,6 +807731,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -806641,6 +807851,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -809197,6 +810409,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -809315,6 +810529,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -810982,6 +812198,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -811074,6 +812292,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -811166,6 +812386,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -812628,6 +813850,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -814705,6 +815929,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -814823,6 +816049,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -816408,6 +817636,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -817889,6 +819119,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -818059,6 +819291,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -819512,6 +820746,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -820783,6 +822019,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -822054,6 +823292,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -824387,6 +825627,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -824531,6 +825773,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -824649,6 +825893,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -826083,6 +827329,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -826227,6 +827475,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -828914,6 +830164,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -830482,6 +831734,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -831791,6 +833045,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -833100,6 +834356,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -834563,6 +835821,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -836255,6 +837515,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -837688,6 +838950,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -841496,6 +842760,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -841744,6 +843010,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -843288,6 +844556,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -845310,6 +846580,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -847739,6 +849011,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -848065,6 +849339,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -849479,6 +850755,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -851147,6 +852425,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -851265,6 +852545,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -851409,6 +852691,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -854451,6 +855735,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -854595,6 +855881,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -854687,6 +855975,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -856515,6 +857805,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -856607,6 +857899,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -856881,6 +858175,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -858184,6 +859480,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -858328,6 +859626,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -859599,6 +860899,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -860876,6 +862178,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -862628,6 +863932,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -864140,6 +865446,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -864284,6 +865592,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -865130,6 +866440,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -867316,6 +868628,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -868755,6 +870069,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -871109,6 +872425,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -872467,6 +873785,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -876560,6 +877880,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -879185,6 +880507,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -879329,6 +880653,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -880960,6 +882286,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -881104,6 +882432,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -882384,6 +883714,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -883687,6 +885019,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -883831,6 +885165,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -886125,6 +887461,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -888610,6 +889948,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -888754,6 +890094,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -890530,6 +891872,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -891820,6 +893164,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -891938,6 +893284,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -894127,6 +895475,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -894271,6 +895621,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -895574,6 +896926,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -895718,6 +897072,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -899182,6 +900538,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -899326,6 +900684,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -900957,6 +902317,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -902452,6 +903814,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -905684,6 +907048,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -905802,6 +907168,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -905998,6 +907366,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -906116,6 +907486,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -906208,6 +907580,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -906300,6 +907674,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -907609,6 +908985,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -907727,6 +909105,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -909160,6 +910540,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -909434,6 +910816,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -910743,6 +912127,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -910887,6 +912273,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -912177,6 +913565,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -914378,6 +915768,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -914522,6 +915914,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -915955,6 +917349,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -918420,6 +919816,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -919836,6 +921234,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -921126,6 +922526,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -924753,6 +926155,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -926118,6 +927522,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -927421,6 +928827,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -929693,6 +931101,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -931126,6 +932536,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -932497,6 +933909,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -932667,6 +934081,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -935519,6 +936935,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -935663,6 +937081,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -937725,6 +939145,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -938181,6 +939603,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -938273,6 +939697,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -940046,6 +941472,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -940268,6 +941696,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -941788,6 +943218,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -943561,6 +944993,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -945846,6 +947280,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -946016,6 +947452,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -947485,6 +948923,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -949326,6 +950766,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -951645,6 +953087,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -951789,6 +953233,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -953069,6 +954515,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -955824,6 +957272,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -958438,6 +959888,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -959747,6 +961199,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -961180,6 +962634,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -964780,6 +966236,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -964924,6 +966382,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -968468,6 +969928,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -968612,6 +970074,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -968730,6 +970194,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -968848,6 +970314,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -971223,6 +972691,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -971419,6 +972889,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -971615,6 +973087,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -974759,6 +976233,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -975085,6 +976561,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -976418,6 +977896,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -977708,6 +979188,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -979745,6 +981227,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -980123,6 +981607,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -982816,6 +984302,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -984459,6 +985947,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -987904,6 +989394,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -988048,6 +989540,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -989362,6 +990856,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -990930,6 +992426,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -992363,6 +993861,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -993752,6 +995252,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -995135,6 +996637,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -997198,6 +998702,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1000266,6 +1001772,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1000410,6 +1001918,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1000684,6 +1002194,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1000854,6 +1002366,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1001128,6 +1002642,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1001480,6 +1002996,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1002764,6 +1004282,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1006723,6 +1008243,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1008966,6 +1010488,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1009136,6 +1010660,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1011569,6 +1013095,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1011713,6 +1013241,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1013437,6 +1014967,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1014900,6 +1016432,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1016506,6 +1018040,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1018462,6 +1019998,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1020113,6 +1021651,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1020205,6 +1021745,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1021897,6 +1023439,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1023206,6 +1024750,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1024620,6 +1026166,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1027786,6 +1029334,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1027930,6 +1029480,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1028542,6 +1030094,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1028686,6 +1030240,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1028778,6 +1030334,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1030173,6 +1031731,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1031463,6 +1033023,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1031581,6 +1033143,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1032828,6 +1034392,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1034075,6 +1035641,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1036713,6 +1038281,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1036857,6 +1038427,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1038246,6 +1039818,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1038390,6 +1039964,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1039928,6 +1041504,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1040046,6 +1041624,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1041441,6 +1043021,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1041533,6 +1043115,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1043089,6 +1044673,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1047496,6 +1049082,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1047666,6 +1049254,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1049340,6 +1050930,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1049432,6 +1051024,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1049576,6 +1051170,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1050058,6 +1051654,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1051454,6 +1053052,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1052750,6 +1054350,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1054712,6 +1056314,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1054856,6 +1056460,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1056276,6 +1057882,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1056446,6 +1058054,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1058464,6 +1060074,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1058582,6 +1060194,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1059953,6 +1061567,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1061324,6 +1062940,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1063187,6 +1064805,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1064823,6 +1066443,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1066199,6 +1067821,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1067532,6 +1069156,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1069494,6 +1071120,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1070809,6 +1072437,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1072219,6 +1073849,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1073620,6 +1075252,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1075108,6 +1076742,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1076417,6 +1078053,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1077769,6 +1079407,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1079576,6 +1081216,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1081162,6 +1082804,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1083062,6 +1084706,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1084439,6 +1086085,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1085816,6 +1087464,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1087261,6 +1088911,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1089053,6 +1090705,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1091423,6 +1093077,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1092713,6 +1094369,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1094381,6 +1096039,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1095671,6 +1097331,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1097090,6 +1098752,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1098523,6 +1100187,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1099832,6 +1101498,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1102599,6 +1104267,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1104223,6 +1105893,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1105804,6 +1107476,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1107465,6 +1109139,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1108960,6 +1110636,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1110331,6 +1112009,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1111683,6 +1113363,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1113165,6 +1114847,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1115032,6 +1116716,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1116403,6 +1118089,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1118234,6 +1119922,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1120630,6 +1122320,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1120774,6 +1122466,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1122145,6 +1123839,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1124851,6 +1126547,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1125073,6 +1126771,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1126545,6 +1128245,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1130087,6 +1131789,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1130439,6 +1132143,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1130713,6 +1132419,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1132090,6 +1133798,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1136066,6 +1137776,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1140042,6 +1141754,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1143017,6 +1144731,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1143161,6 +1144877,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1143253,6 +1144971,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1144524,6 +1146244,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1145814,6 +1147536,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1147168,6 +1148892,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1148678,6 +1150404,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1149993,6 +1151721,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1151425,6 +1153155,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1152715,6 +1154447,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1153939,6 +1155673,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1155163,6 +1156899,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1156387,6 +1158125,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1158684,6 +1160424,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1158854,6 +1160596,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1158972,6 +1160716,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1160324,6 +1162070,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1160442,6 +1162190,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1161689,6 +1163439,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1162998,6 +1164750,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1164369,6 +1166123,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1164539,6 +1166295,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1165891,6 +1167649,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1166061,6 +1167821,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1167394,6 +1169156,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1168703,6 +1170467,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1169950,6 +1171716,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1171191,6 +1172959,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1174219,6 +1175989,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1174363,6 +1176135,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1175647,6 +1177421,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1176931,6 +1178707,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1178246,6 +1180024,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1179536,6 +1181316,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1181058,6 +1182840,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1182383,6 +1184167,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1183931,6 +1185717,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1185178,6 +1186966,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1186487,6 +1188277,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1188595,6 +1190387,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1188817,6 +1190611,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1190088,6 +1191884,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1192363,6 +1194161,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1192455,6 +1194255,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1192781,6 +1194583,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1193003,6 +1194807,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1193355,6 +1195161,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1194688,6 +1196496,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1195997,6 +1197807,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1197244,6 +1199056,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1198485,6 +1200299,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1201088,6 +1202904,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1201232,6 +1203050,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1202625,6 +1204445,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1204119,6 +1205941,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1205477,6 +1207301,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1206829,6 +1208655,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1208181,6 +1210009,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1208455,6 +1210285,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1209986,6 +1211818,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1211511,6 +1213345,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1213120,6 +1214956,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1215561,6 +1217399,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1217198,6 +1219038,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1218818,6 +1220660,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1220164,6 +1222008,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1222239,6 +1224085,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1224241,6 +1226089,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1224333,6 +1226183,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1225642,6 +1227494,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1225786,6 +1227640,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1227607,6 +1229463,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1227777,6 +1229635,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1228045,6 +1229905,8 @@ "referencedTypes": [ "type_:decimal" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1228339,6 +1230201,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1230133,6 +1231997,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1231839,6 +1233705,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1233554,6 +1235422,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1235923,6 +1237793,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1236119,6 +1237991,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1236211,6 +1238085,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1237757,6 +1239633,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1237849,6 +1239727,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1239835,6 +1241715,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1241912,6 +1243794,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1242004,6 +1243888,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1242148,6 +1244034,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1242240,6 +1244128,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1243771,6 +1245661,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1245087,6 +1246979,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1246624,6 +1248518,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1246774,6 +1248670,8 @@ "type_:UserConfigurationMenuLink", "type_:url" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1246911,6 +1248809,8 @@ "referencedTypes": [ "type_:url" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1247027,6 +1248927,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1247378,6 +1249280,8 @@ "type_:code", "type_:decimal" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1247644,6 +1249548,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1248774,6 +1250680,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1249941,6 +1251849,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1251704,6 +1253614,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1251848,6 +1253760,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1252005,6 +1253919,8 @@ "type_:code", "type_:uri" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1252162,6 +1254078,8 @@ "type_:code", "type_:uri" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1252319,6 +1254237,8 @@ "type_:code", "type_:uri" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1253733,6 +1255653,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1255141,6 +1257063,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1255321,6 +1257245,8 @@ "referencedTypes": [ "type_:AccessPolicyIpAccessRuleAction" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1255413,6 +1257339,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1256933,6 +1258861,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1257077,6 +1259007,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1258720,6 +1260652,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1258838,6 +1260772,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1260024,6 +1261960,8 @@ "type_:AgentSetting", "type_:AgentChannel" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1260292,6 +1262230,8 @@ "referencedTypes": [ "type_:decimal" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1260420,6 +1262360,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1261438,6 +1263380,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": {}, "rootPackage": { "fernFilepath": { @@ -1262435,7 +1264378,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions-openapi/code-samples-open-api.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions-openapi/code-samples-open-api.json index 03be8a0c468..b79ed1793af 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions-openapi/code-samples-open-api.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions-openapi/code-samples-open-api.json @@ -238,6 +238,8 @@ "referencedTypes": [ "type_:TelemetryData" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -397,6 +399,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -421,6 +425,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_.telemetry", @@ -1923,6 +1929,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": {}, "rootPackage": { "fernFilepath": { @@ -1951,7 +1958,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions-openapi/enum-casing.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions-openapi/enum-casing.json index 13a1a7d1c66..f23d3c81b7a 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions-openapi/enum-casing.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions-openapi/enum-casing.json @@ -117,6 +117,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -326,6 +328,8 @@ "referencedTypes": [ "type_:ExampleResponseStatus" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -418,6 +422,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -442,6 +448,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_.getExample", @@ -1475,6 +1483,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": {}, "rootPackage": { "fernFilepath": { @@ -1504,7 +1513,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions-openapi/inline-schema-reference.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions-openapi/inline-schema-reference.json index e93e94dff89..732b3eb01ad 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions-openapi/inline-schema-reference.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions-openapi/inline-schema-reference.json @@ -110,6 +110,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -252,6 +254,8 @@ "type_:Schema1", "type_:Schema2" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -380,6 +384,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -508,6 +514,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -532,6 +540,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_.getExample", @@ -1038,6 +1048,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": {}, "rootPackage": { "fernFilepath": { @@ -1068,7 +1079,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions-openapi/names.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions-openapi/names.json index 0b8262593bf..a7bbe7adebb 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions-openapi/names.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions-openapi/names.json @@ -353,6 +353,8 @@ "referencedTypes": [ "type_infra/telemetry:TelemetryData" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -589,6 +591,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -651,6 +655,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_telemetry.getTelemetryData", @@ -3243,6 +3249,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_telemetry": { "name": { @@ -3538,7 +3545,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/alias-extends.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/alias-extends.json index 8e1dbda42fa..2a979cd3be2 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/alias-extends.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/alias-extends.json @@ -125,6 +125,8 @@ "referencedTypes": [ "type_:Parent" ], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -336,6 +338,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -577,6 +581,8 @@ "referencedTypes": [ "type_:Parent" ], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -759,6 +765,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_.extendedInlineRequestBody", @@ -1181,6 +1189,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": {}, "rootPackage": { "fernFilepath": { @@ -1210,7 +1219,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/alias.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/alias.json index d424a6fefd3..6bf19e8999d 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/alias.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/alias.json @@ -84,6 +84,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -260,6 +262,8 @@ "referencedTypes": [ "type_:TypeId" ], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -555,6 +559,8 @@ "type_:Type", "type_:TypeId" ], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -812,6 +818,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_.get", @@ -1125,6 +1133,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": {}, "rootPackage": { "fernFilepath": { @@ -1154,7 +1163,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/api-wide-base-path.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/api-wide-base-path.json index facef34f1bd..21ef83d2cc3 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/api-wide-base-path.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/api-wide-base-path.json @@ -127,6 +127,8 @@ "docs": null } ], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_service.post", @@ -658,6 +660,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_service": { "name": { @@ -759,7 +762,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/audiences.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/audiences.json index 394a6b2535a..455c77ab6a9 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/audiences.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/audiences.json @@ -122,6 +122,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -383,6 +385,8 @@ "type_folder-b/common:Foo", "type_folder-c/common:Foo" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -643,6 +647,8 @@ "referencedTypes": [ "type_folder-c/common:Foo" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -795,6 +801,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -971,6 +979,8 @@ "referencedTypes": [ "type_commons:Imported" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1080,6 +1090,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1240,6 +1252,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1341,6 +1355,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_folder-a/service.getDirectThread", @@ -2059,6 +2075,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_foo.find", @@ -3034,6 +3052,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_commons": { "name": { @@ -3791,7 +3810,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/auth-environment-variables.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/auth-environment-variables.json index 43d1cb8c2d3..f0edba3d648 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/auth-environment-variables.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/auth-environment-variables.json @@ -165,6 +165,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_service.getWithApiKey", @@ -549,6 +551,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_service": { "name": { @@ -650,7 +653,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/basic-auth-environment-variables.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/basic-auth-environment-variables.json index 75eec5d7671..13cb5a8b76e 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/basic-auth-environment-variables.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/basic-auth-environment-variables.json @@ -187,6 +187,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -503,6 +505,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_basic-auth.getWithBasicAuth", @@ -1269,6 +1273,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_basic-auth": { "name": { @@ -1449,7 +1454,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/basic-auth.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/basic-auth.json index 18b4e3b5f15..e07d58742f7 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/basic-auth.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/basic-auth.json @@ -187,6 +187,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -503,6 +505,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_basic-auth.getWithBasicAuth", @@ -1269,6 +1273,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_basic-auth": { "name": { @@ -1449,7 +1454,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/bearer-token-environment-variable.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/bearer-token-environment-variable.json index c3d982797bb..5c1d639f0e5 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/bearer-token-environment-variable.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/bearer-token-environment-variable.json @@ -111,6 +111,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_service.getWithBearerToken", @@ -255,6 +257,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_service": { "name": { @@ -356,7 +359,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/bytes.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/bytes.json index 69373b47842..85a733f3164 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/bytes.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/bytes.json @@ -86,6 +86,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_service.upload", @@ -235,6 +237,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_service": { "name": { @@ -336,7 +339,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/circular-references-advanced.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/circular-references-advanced.json index d8ac59cfac0..3bac33b9378 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/circular-references-advanced.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/circular-references-advanced.json @@ -170,6 +170,8 @@ "type_a:A", "type_:RootType" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -249,6 +251,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -396,6 +400,8 @@ "referencedTypes": [ "type_:RootType" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -750,6 +756,8 @@ "type_ast:ObjectValue", "type_ast:ContainerValue" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -880,6 +888,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -959,6 +969,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1056,6 +1068,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1489,6 +1503,8 @@ "type_ast:ContainerValue", "type_ast:FieldValue" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1763,6 +1779,8 @@ "type_ast:ObjectValue", "type_ast:ContainerValue" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1819,6 +1837,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_a": { "name": { @@ -2006,7 +2025,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/circular-references.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/circular-references.json index b956ebd25e6..6bf2aaad10b 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/circular-references.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/circular-references.json @@ -170,6 +170,8 @@ "type_a:A", "type_:RootType" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -249,6 +251,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -396,6 +400,8 @@ "referencedTypes": [ "type_:RootType" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -829,6 +835,8 @@ "type_ast:ContainerValue", "type_ast:FieldValue" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1183,6 +1191,8 @@ "type_ast:ObjectValue", "type_ast:ContainerValue" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1313,6 +1323,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1392,6 +1404,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1446,6 +1460,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_a": { "name": { @@ -1631,7 +1646,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/code-samples.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/code-samples.json index e82f48a8f3b..cde9b8f3b59 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/code-samples.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/code-samples.json @@ -185,6 +185,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -247,6 +249,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_service.hello", @@ -1325,6 +1329,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_service": { "name": { @@ -1428,7 +1433,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/custom-auth.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/custom-auth.json index 126ac4e2896..9d7cd06bd62 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/custom-auth.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/custom-auth.json @@ -182,6 +182,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -498,6 +500,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_custom-auth.getWithCustomAuth", @@ -1264,6 +1268,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_custom-auth": { "name": { @@ -1444,7 +1449,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/enum.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/enum.json index 0acc153f626..2d14b5ce49a 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/enum.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/enum.json @@ -143,6 +143,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -332,6 +334,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -489,6 +493,8 @@ "type_:Color", "type_:Operand" ], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -637,6 +643,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_inlined-request.send", @@ -1535,6 +1543,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_path-param.send", @@ -3120,6 +3130,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_query-param.send", @@ -5108,6 +5120,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_inlined-request": { "name": { @@ -5361,7 +5374,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/error-property.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/error-property.json index a9250c41576..7ad60171cd9 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/error-property.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/error-property.json @@ -142,6 +142,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -365,6 +367,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_property-based-error.ThrowError", @@ -708,6 +712,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_errors": { "name": { @@ -887,7 +892,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/examples.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/examples.json index b56b1a80828..3e40f47deae 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/examples.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/examples.json @@ -158,6 +158,8 @@ "type_:BasicType", "type_:ComplexType" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -334,6 +336,8 @@ "type_:BasicType", "type_:ComplexType" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -426,6 +430,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -544,6 +550,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -680,6 +688,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -978,6 +988,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -2000,6 +2012,8 @@ "type_commons/types:Metadata", "type_commons/types:Tag" ], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -2874,6 +2888,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -3099,6 +3115,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -3235,6 +3253,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -3920,6 +3940,8 @@ "type_types:MovieId", "type_commons/types:Tag" ], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -5511,6 +5533,8 @@ "type_types:Actress", "type_types:StuntDouble" ], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -6288,6 +6312,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -6442,6 +6468,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -6830,6 +6858,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -7587,6 +7617,8 @@ "type_types:MovieId", "type_commons/types:Tag" ], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": null, @@ -8946,6 +8978,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": null, @@ -9412,6 +9446,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -10211,6 +10247,8 @@ "type_types:File", "type_types:Directory" ], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -12232,6 +12270,8 @@ "type_types:Node", "type_types:Tree" ], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -14439,6 +14479,8 @@ "type_types:Node", "type_types:Tree" ], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -15421,6 +15463,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -15740,6 +15784,8 @@ "referencedTypes": [ "type_types:ExceptionInfo" ], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -16320,6 +16366,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -16812,6 +16860,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -17122,6 +17172,8 @@ "referencedTypes": [ "type_types:MigrationStatus" ], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": null, @@ -17531,6 +17583,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -17834,6 +17888,8 @@ "type_:BasicType", "type_:ComplexType" ], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -18882,6 +18938,8 @@ "type_:BasicType", "type_:ComplexType" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -19097,6 +19155,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -19447,6 +19507,8 @@ "type_:BasicType", "type_:ComplexType" ], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -19892,6 +19954,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_.echo", @@ -20283,6 +20347,8 @@ "docs": null } ], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_file/notification/service.getException", @@ -21569,6 +21635,8 @@ } ], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_file/service.getFile", @@ -22700,6 +22768,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_health/service.check", @@ -23301,6 +23371,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_service.getMovie", @@ -32911,6 +32983,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_commons": { "name": { @@ -33959,7 +34032,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/exhaustive.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/exhaustive.json index d9f499532f8..7cd71cd9cf5 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/exhaustive.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/exhaustive.json @@ -167,6 +167,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -388,6 +390,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1067,6 +1071,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1223,6 +1229,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1413,6 +1421,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1716,6 +1726,8 @@ "referencedTypes": [ "type_types/object:ObjectWithOptionalField" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -2007,6 +2019,8 @@ "referencedTypes": [ "type_types/object:ObjectWithOptionalField" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -2267,6 +2281,8 @@ "referencedTypes": [ "type_types/object:OptionalAlias" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -2415,6 +2431,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -2818,6 +2836,8 @@ "type_types/union:Dog", "type_types/union:Cat" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -3007,6 +3027,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -3196,6 +3218,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -4886,6 +4910,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_endpoints/container.getAndReturnListOfPrimitives", @@ -10681,6 +10707,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_endpoints/enum.getAndReturnEnum", @@ -11480,6 +11508,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_endpoints/http-methods.testGet", @@ -23263,6 +23293,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_endpoints/object.getAndReturnWithOptionalField", @@ -48670,6 +48702,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_endpoints/params.getWithPath", @@ -50165,6 +50199,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_endpoints/primitive.getAndReturnString", @@ -51682,6 +51718,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_endpoints/union.getAndReturnUnion", @@ -53070,6 +53108,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_inlined-requests.postWithObjectBodyandResponse", @@ -60920,6 +60960,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_no-auth.postWithNoAuth", @@ -61287,6 +61329,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_no-req-body.getWithNoRequestBody", @@ -63974,6 +64018,8 @@ } ], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_req-with-headers.getWithCustomHeader", @@ -64305,6 +64351,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_endpoints": { "name": { @@ -66013,7 +66060,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/extends.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/extends.json index 0d65d490c24..cb214b5f803 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/extends.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/extends.json @@ -172,6 +172,8 @@ "referencedTypes": [ "type_:Docs" ], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -515,6 +517,8 @@ "type_:JSON", "type_:Docs" ], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -816,6 +820,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -1057,6 +1063,8 @@ "referencedTypes": [ "type_:Docs" ], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -1239,6 +1247,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_.extendedInlineRequestBody", @@ -1763,6 +1773,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": {}, "rootPackage": { "fernFilepath": { @@ -1793,7 +1804,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/extra-properties.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/extra-properties.json index d94c4ae5776..2d7543e9f24 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/extra-properties.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/extra-properties.json @@ -103,6 +103,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -220,6 +222,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -282,6 +286,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_user.createUser", @@ -977,6 +983,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_user": { "name": { @@ -1082,7 +1089,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/file-download.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/file-download.json index 12afdeb102a..823b5692545 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/file-download.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/file-download.json @@ -86,6 +86,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_service.downloadFile", @@ -216,6 +218,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_service": { "name": { @@ -317,7 +320,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/file-upload.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/file-upload.json index d16b30c537c..76238d2afb9 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/file-upload.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/file-upload.json @@ -142,6 +142,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -204,6 +206,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_service.post", @@ -1619,6 +1623,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_service": { "name": { @@ -1722,7 +1727,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/folders.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/folders.json index aebc0bb3a76..c0cdefb1be2 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/folders.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/folders.json @@ -199,6 +199,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -366,6 +368,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_.foo", @@ -537,6 +541,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_a/b.foo", @@ -708,6 +714,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_a/c.foo", @@ -841,6 +849,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_folder.foo", @@ -1011,6 +1021,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_folder/service.endpoint", @@ -1495,6 +1507,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_a": { "name": { @@ -2287,7 +2300,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/grpc-proto.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/grpc-proto.json new file mode 100644 index 00000000000..b94b460d6dc --- /dev/null +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/grpc-proto.json @@ -0,0 +1,2338 @@ +{ + "fdrApiDefinitionId": null, + "apiVersion": null, + "apiName": { + "originalName": "api", + "camelCase": { + "unsafeName": "api", + "safeName": "api" + }, + "snakeCase": { + "unsafeName": "api", + "safeName": "api" + }, + "screamingSnakeCase": { + "unsafeName": "API", + "safeName": "API" + }, + "pascalCase": { + "unsafeName": "API", + "safeName": "API" + } + }, + "apiDisplayName": "\"\"", + "apiDocs": null, + "auth": { + "requirement": "ALL", + "schemes": [], + "docs": null + }, + "headers": [], + "idempotencyHeaders": [], + "types": { + "type_:CreateResponse": { + "name": { + "name": { + "originalName": "CreateResponse", + "camelCase": { + "unsafeName": "createResponse", + "safeName": "createResponse" + }, + "snakeCase": { + "unsafeName": "create_response", + "safeName": "create_response" + }, + "screamingSnakeCase": { + "unsafeName": "CREATE_RESPONSE", + "safeName": "CREATE_RESPONSE" + }, + "pascalCase": { + "unsafeName": "CreateResponse", + "safeName": "CreateResponse" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "typeId": "type_:CreateResponse" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": { + "name": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "wireValue": "user" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "named", + "name": { + "originalName": "UserModel", + "camelCase": { + "unsafeName": "userModel", + "safeName": "userModel" + }, + "snakeCase": { + "unsafeName": "user_model", + "safeName": "user_model" + }, + "screamingSnakeCase": { + "unsafeName": "USER_MODEL", + "safeName": "USER_MODEL" + }, + "pascalCase": { + "unsafeName": "UserModel", + "safeName": "UserModel" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "typeId": "type_:UserModel", + "default": null, + "inline": null + } + } + }, + "availability": null, + "docs": null + } + ], + "extra-properties": false, + "extendedProperties": [] + }, + "referencedTypes": [ + "type_:UserModel" + ], + "encoding": null, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "availability": null, + "docs": null + }, + "type_:UserModel": { + "name": { + "name": { + "originalName": "UserModel", + "camelCase": { + "unsafeName": "userModel", + "safeName": "userModel" + }, + "snakeCase": { + "unsafeName": "user_model", + "safeName": "user_model" + }, + "screamingSnakeCase": { + "unsafeName": "USER_MODEL", + "safeName": "USER_MODEL" + }, + "pascalCase": { + "unsafeName": "UserModel", + "safeName": "UserModel" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "typeId": "type_:UserModel" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": { + "name": { + "originalName": "username", + "camelCase": { + "unsafeName": "username", + "safeName": "username" + }, + "snakeCase": { + "unsafeName": "username", + "safeName": "username" + }, + "screamingSnakeCase": { + "unsafeName": "USERNAME", + "safeName": "USERNAME" + }, + "pascalCase": { + "unsafeName": "Username", + "safeName": "Username" + } + }, + "wireValue": "username" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "email", + "camelCase": { + "unsafeName": "email", + "safeName": "email" + }, + "snakeCase": { + "unsafeName": "email", + "safeName": "email" + }, + "screamingSnakeCase": { + "unsafeName": "EMAIL", + "safeName": "EMAIL" + }, + "pascalCase": { + "unsafeName": "Email", + "safeName": "Email" + } + }, + "wireValue": "email" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "age", + "camelCase": { + "unsafeName": "age", + "safeName": "age" + }, + "snakeCase": { + "unsafeName": "age", + "safeName": "age" + }, + "screamingSnakeCase": { + "unsafeName": "AGE", + "safeName": "AGE" + }, + "pascalCase": { + "unsafeName": "Age", + "safeName": "Age" + } + }, + "wireValue": "age" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "UINT", + "v2": null + } + } + } + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "weight", + "camelCase": { + "unsafeName": "weight", + "safeName": "weight" + }, + "snakeCase": { + "unsafeName": "weight", + "safeName": "weight" + }, + "screamingSnakeCase": { + "unsafeName": "WEIGHT", + "safeName": "WEIGHT" + }, + "pascalCase": { + "unsafeName": "Weight", + "safeName": "Weight" + } + }, + "wireValue": "weight" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "FLOAT", + "v2": null + } + } + } + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "metadata", + "camelCase": { + "unsafeName": "metadata", + "safeName": "metadata" + }, + "snakeCase": { + "unsafeName": "metadata", + "safeName": "metadata" + }, + "screamingSnakeCase": { + "unsafeName": "METADATA", + "safeName": "METADATA" + }, + "pascalCase": { + "unsafeName": "Metadata", + "safeName": "Metadata" + } + }, + "wireValue": "metadata" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "container", + "container": { + "_type": "map", + "keyType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "valueType": { + "_type": "unknown" + } + } + } + } + }, + "availability": null, + "docs": null + } + ], + "extra-properties": false, + "extendedProperties": [] + }, + "referencedTypes": [], + "encoding": null, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "availability": null, + "docs": null + } + }, + "errors": {}, + "services": { + "service_user": { + "availability": null, + "name": { + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + } + }, + "displayName": "User", + "basePath": { + "head": "", + "parts": [] + }, + "headers": [], + "pathParameters": [], + "encoding": null, + "transport": null, + "endpoints": [ + { + "id": "endpoint_user.create", + "name": { + "originalName": "create", + "camelCase": { + "unsafeName": "create", + "safeName": "create" + }, + "snakeCase": { + "unsafeName": "create", + "safeName": "create" + }, + "screamingSnakeCase": { + "unsafeName": "CREATE", + "safeName": "CREATE" + }, + "pascalCase": { + "unsafeName": "Create", + "safeName": "Create" + } + }, + "displayName": null, + "auth": false, + "idempotent": false, + "baseUrl": null, + "method": "POST", + "path": { + "head": "/users", + "parts": [] + }, + "fullPath": { + "head": "users", + "parts": [] + }, + "pathParameters": [], + "allPathParameters": [], + "queryParameters": [], + "headers": [], + "requestBody": { + "type": "inlinedRequestBody", + "name": { + "originalName": "CreateRequest", + "camelCase": { + "unsafeName": "createRequest", + "safeName": "createRequest" + }, + "snakeCase": { + "unsafeName": "create_request", + "safeName": "create_request" + }, + "screamingSnakeCase": { + "unsafeName": "CREATE_REQUEST", + "safeName": "CREATE_REQUEST" + }, + "pascalCase": { + "unsafeName": "CreateRequest", + "safeName": "CreateRequest" + } + }, + "extends": [], + "contentType": null, + "properties": [ + { + "name": { + "name": { + "originalName": "username", + "camelCase": { + "unsafeName": "username", + "safeName": "username" + }, + "snakeCase": { + "unsafeName": "username", + "safeName": "username" + }, + "screamingSnakeCase": { + "unsafeName": "USERNAME", + "safeName": "USERNAME" + }, + "pascalCase": { + "unsafeName": "Username", + "safeName": "Username" + } + }, + "wireValue": "username" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "docs": null + }, + { + "name": { + "name": { + "originalName": "email", + "camelCase": { + "unsafeName": "email", + "safeName": "email" + }, + "snakeCase": { + "unsafeName": "email", + "safeName": "email" + }, + "screamingSnakeCase": { + "unsafeName": "EMAIL", + "safeName": "EMAIL" + }, + "pascalCase": { + "unsafeName": "Email", + "safeName": "Email" + } + }, + "wireValue": "email" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "docs": null + }, + { + "name": { + "name": { + "originalName": "age", + "camelCase": { + "unsafeName": "age", + "safeName": "age" + }, + "snakeCase": { + "unsafeName": "age", + "safeName": "age" + }, + "screamingSnakeCase": { + "unsafeName": "AGE", + "safeName": "AGE" + }, + "pascalCase": { + "unsafeName": "Age", + "safeName": "Age" + } + }, + "wireValue": "age" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "UINT", + "v2": null + } + } + } + }, + "docs": null + }, + { + "name": { + "name": { + "originalName": "weight", + "camelCase": { + "unsafeName": "weight", + "safeName": "weight" + }, + "snakeCase": { + "unsafeName": "weight", + "safeName": "weight" + }, + "screamingSnakeCase": { + "unsafeName": "WEIGHT", + "safeName": "WEIGHT" + }, + "pascalCase": { + "unsafeName": "Weight", + "safeName": "Weight" + } + }, + "wireValue": "weight" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "FLOAT", + "v2": null + } + } + } + }, + "docs": null + }, + { + "name": { + "name": { + "originalName": "metadata", + "camelCase": { + "unsafeName": "metadata", + "safeName": "metadata" + }, + "snakeCase": { + "unsafeName": "metadata", + "safeName": "metadata" + }, + "screamingSnakeCase": { + "unsafeName": "METADATA", + "safeName": "METADATA" + }, + "pascalCase": { + "unsafeName": "Metadata", + "safeName": "Metadata" + } + }, + "wireValue": "metadata" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "container", + "container": { + "_type": "map", + "keyType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "valueType": { + "_type": "unknown" + } + } + } + } + }, + "docs": null + } + ], + "extra-properties": false, + "extendedProperties": [] + }, + "sdkRequest": { + "shape": { + "type": "wrapper", + "wrapperName": { + "originalName": "CreateRequest", + "camelCase": { + "unsafeName": "createRequest", + "safeName": "createRequest" + }, + "snakeCase": { + "unsafeName": "create_request", + "safeName": "create_request" + }, + "screamingSnakeCase": { + "unsafeName": "CREATE_REQUEST", + "safeName": "CREATE_REQUEST" + }, + "pascalCase": { + "unsafeName": "CreateRequest", + "safeName": "CreateRequest" + } + }, + "bodyKey": { + "originalName": "body", + "camelCase": { + "unsafeName": "body", + "safeName": "body" + }, + "snakeCase": { + "unsafeName": "body", + "safeName": "body" + }, + "screamingSnakeCase": { + "unsafeName": "BODY", + "safeName": "BODY" + }, + "pascalCase": { + "unsafeName": "Body", + "safeName": "Body" + } + } + }, + "requestParameterName": { + "originalName": "request", + "camelCase": { + "unsafeName": "request", + "safeName": "request" + }, + "snakeCase": { + "unsafeName": "request", + "safeName": "request" + }, + "screamingSnakeCase": { + "unsafeName": "REQUEST", + "safeName": "REQUEST" + }, + "pascalCase": { + "unsafeName": "Request", + "safeName": "Request" + } + }, + "streamParameter": null + }, + "response": { + "body": { + "type": "json", + "value": { + "type": "response", + "responseBodyType": { + "_type": "named", + "name": { + "originalName": "CreateResponse", + "camelCase": { + "unsafeName": "createResponse", + "safeName": "createResponse" + }, + "snakeCase": { + "unsafeName": "create_response", + "safeName": "create_response" + }, + "screamingSnakeCase": { + "unsafeName": "CREATE_RESPONSE", + "safeName": "CREATE_RESPONSE" + }, + "pascalCase": { + "unsafeName": "CreateResponse", + "safeName": "CreateResponse" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "typeId": "type_:CreateResponse", + "default": null, + "inline": null + }, + "docs": "OK" + } + }, + "status-code": null + }, + "errors": [], + "userSpecifiedExamples": [ + { + "example": { + "id": null, + "name": null, + "url": "/users", + "rootPathParameters": [], + "endpointPathParameters": [], + "servicePathParameters": [], + "endpointHeaders": [], + "serviceHeaders": [], + "queryParameters": [], + "request": { + "type": "inlinedRequestBody", + "properties": [], + "jsonExample": {} + }, + "response": { + "type": "ok", + "value": { + "type": "body", + "value": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:CreateResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": { + "originalName": "CreateResponse", + "camelCase": { + "unsafeName": "createResponse", + "safeName": "createResponse" + }, + "snakeCase": { + "unsafeName": "create_response", + "safeName": "create_response" + }, + "screamingSnakeCase": { + "unsafeName": "CREATE_RESPONSE", + "safeName": "CREATE_RESPONSE" + }, + "pascalCase": { + "unsafeName": "CreateResponse", + "safeName": "CreateResponse" + } + } + }, + "shape": { + "type": "object", + "properties": [ + { + "name": { + "name": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "wireValue": "user" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:UserModel", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": { + "originalName": "UserModel", + "camelCase": { + "unsafeName": "userModel", + "safeName": "userModel" + }, + "snakeCase": { + "unsafeName": "user_model", + "safeName": "user_model" + }, + "screamingSnakeCase": { + "unsafeName": "USER_MODEL", + "safeName": "USER_MODEL" + }, + "pascalCase": { + "unsafeName": "UserModel", + "safeName": "UserModel" + } + } + }, + "shape": { + "type": "object", + "properties": [ + { + "name": { + "name": { + "originalName": "username", + "camelCase": { + "unsafeName": "username", + "safeName": "username" + }, + "snakeCase": { + "unsafeName": "username", + "safeName": "username" + }, + "screamingSnakeCase": { + "unsafeName": "USERNAME", + "safeName": "USERNAME" + }, + "pascalCase": { + "unsafeName": "Username", + "safeName": "Username" + } + }, + "wireValue": "username" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "username" + } + } + }, + "jsonExample": "username" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "username" + }, + "originalTypeDeclaration": { + "typeId": "type_:UserModel", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": { + "originalName": "UserModel", + "camelCase": { + "unsafeName": "userModel", + "safeName": "userModel" + }, + "snakeCase": { + "unsafeName": "user_model", + "safeName": "user_model" + }, + "screamingSnakeCase": { + "unsafeName": "USER_MODEL", + "safeName": "USER_MODEL" + }, + "pascalCase": { + "unsafeName": "UserModel", + "safeName": "UserModel" + } + } + } + }, + { + "name": { + "name": { + "originalName": "email", + "camelCase": { + "unsafeName": "email", + "safeName": "email" + }, + "snakeCase": { + "unsafeName": "email", + "safeName": "email" + }, + "screamingSnakeCase": { + "unsafeName": "EMAIL", + "safeName": "EMAIL" + }, + "pascalCase": { + "unsafeName": "Email", + "safeName": "Email" + } + }, + "wireValue": "email" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "email" + } + } + }, + "jsonExample": "email" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "email" + }, + "originalTypeDeclaration": { + "typeId": "type_:UserModel", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": { + "originalName": "UserModel", + "camelCase": { + "unsafeName": "userModel", + "safeName": "userModel" + }, + "snakeCase": { + "unsafeName": "user_model", + "safeName": "user_model" + }, + "screamingSnakeCase": { + "unsafeName": "USER_MODEL", + "safeName": "USER_MODEL" + }, + "pascalCase": { + "unsafeName": "UserModel", + "safeName": "UserModel" + } + } + } + }, + { + "name": { + "name": { + "originalName": "age", + "camelCase": { + "unsafeName": "age", + "safeName": "age" + }, + "snakeCase": { + "unsafeName": "age", + "safeName": "age" + }, + "screamingSnakeCase": { + "unsafeName": "AGE", + "safeName": "AGE" + }, + "pascalCase": { + "unsafeName": "Age", + "safeName": "Age" + } + }, + "wireValue": "age" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "uint", + "uint": 1 + } + }, + "jsonExample": 1 + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "UINT", + "v2": null + } + } + } + }, + "jsonExample": 1 + }, + "originalTypeDeclaration": { + "typeId": "type_:UserModel", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": { + "originalName": "UserModel", + "camelCase": { + "unsafeName": "userModel", + "safeName": "userModel" + }, + "snakeCase": { + "unsafeName": "user_model", + "safeName": "user_model" + }, + "screamingSnakeCase": { + "unsafeName": "USER_MODEL", + "safeName": "USER_MODEL" + }, + "pascalCase": { + "unsafeName": "UserModel", + "safeName": "UserModel" + } + } + } + }, + { + "name": { + "name": { + "originalName": "weight", + "camelCase": { + "unsafeName": "weight", + "safeName": "weight" + }, + "snakeCase": { + "unsafeName": "weight", + "safeName": "weight" + }, + "screamingSnakeCase": { + "unsafeName": "WEIGHT", + "safeName": "WEIGHT" + }, + "pascalCase": { + "unsafeName": "Weight", + "safeName": "Weight" + } + }, + "wireValue": "weight" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "float", + "float": 1.1 + } + }, + "jsonExample": 1.1 + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "FLOAT", + "v2": null + } + } + } + }, + "jsonExample": 1.1 + }, + "originalTypeDeclaration": { + "typeId": "type_:UserModel", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": { + "originalName": "UserModel", + "camelCase": { + "unsafeName": "userModel", + "safeName": "userModel" + }, + "snakeCase": { + "unsafeName": "user_model", + "safeName": "user_model" + }, + "screamingSnakeCase": { + "unsafeName": "USER_MODEL", + "safeName": "USER_MODEL" + }, + "pascalCase": { + "unsafeName": "UserModel", + "safeName": "UserModel" + } + } + } + }, + { + "name": { + "name": { + "originalName": "metadata", + "camelCase": { + "unsafeName": "metadata", + "safeName": "metadata" + }, + "snakeCase": { + "unsafeName": "metadata", + "safeName": "metadata" + }, + "screamingSnakeCase": { + "unsafeName": "METADATA", + "safeName": "METADATA" + }, + "pascalCase": { + "unsafeName": "Metadata", + "safeName": "Metadata" + } + }, + "wireValue": "metadata" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "container", + "container": { + "type": "map", + "map": [ + { + "key": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "key" + } + } + }, + "jsonExample": "key" + }, + "value": { + "shape": { + "type": "unknown", + "unknown": "value" + }, + "jsonExample": "value" + } + } + ], + "keyType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "valueType": { + "_type": "unknown" + } + } + }, + "jsonExample": { + "key": "value" + } + }, + "valueType": { + "_type": "container", + "container": { + "_type": "map", + "keyType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "valueType": { + "_type": "unknown" + } + } + } + } + }, + "jsonExample": { + "key": "value" + } + }, + "originalTypeDeclaration": { + "typeId": "type_:UserModel", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": { + "originalName": "UserModel", + "camelCase": { + "unsafeName": "userModel", + "safeName": "userModel" + }, + "snakeCase": { + "unsafeName": "user_model", + "safeName": "user_model" + }, + "screamingSnakeCase": { + "unsafeName": "USER_MODEL", + "safeName": "USER_MODEL" + }, + "pascalCase": { + "unsafeName": "UserModel", + "safeName": "UserModel" + } + } + } + } + ] + } + }, + "jsonExample": { + "username": "username", + "email": "email", + "age": 1, + "weight": 1.1, + "metadata": { + "key": "value" + } + } + }, + "valueType": { + "_type": "named", + "name": { + "originalName": "UserModel", + "camelCase": { + "unsafeName": "userModel", + "safeName": "userModel" + }, + "snakeCase": { + "unsafeName": "user_model", + "safeName": "user_model" + }, + "screamingSnakeCase": { + "unsafeName": "USER_MODEL", + "safeName": "USER_MODEL" + }, + "pascalCase": { + "unsafeName": "UserModel", + "safeName": "UserModel" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "typeId": "type_:UserModel", + "default": null, + "inline": null + } + } + }, + "jsonExample": { + "username": "username", + "email": "email", + "age": 1, + "weight": 1.1, + "metadata": { + "key": "value" + } + } + }, + "originalTypeDeclaration": { + "typeId": "type_:CreateResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": { + "originalName": "CreateResponse", + "camelCase": { + "unsafeName": "createResponse", + "safeName": "createResponse" + }, + "snakeCase": { + "unsafeName": "create_response", + "safeName": "create_response" + }, + "screamingSnakeCase": { + "unsafeName": "CREATE_RESPONSE", + "safeName": "CREATE_RESPONSE" + }, + "pascalCase": { + "unsafeName": "CreateResponse", + "safeName": "CreateResponse" + } + } + } + } + ] + } + }, + "jsonExample": { + "user": { + "username": "username", + "email": "email", + "age": 1, + "weight": 1.1, + "metadata": { + "key": "value" + } + } + } + } + } + }, + "docs": null + }, + "codeSamples": null + } + ], + "autogeneratedExamples": [ + { + "example": { + "url": "/users", + "rootPathParameters": [], + "servicePathParameters": [], + "endpointPathParameters": [], + "serviceHeaders": [], + "endpointHeaders": [], + "queryParameters": [], + "request": { + "type": "inlinedRequestBody", + "properties": [], + "jsonExample": {} + }, + "name": null, + "response": { + "type": "ok", + "value": { + "type": "body", + "value": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:CreateResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": { + "originalName": "CreateResponse", + "camelCase": { + "unsafeName": "createResponse", + "safeName": "createResponse" + }, + "snakeCase": { + "unsafeName": "create_response", + "safeName": "create_response" + }, + "screamingSnakeCase": { + "unsafeName": "CREATE_RESPONSE", + "safeName": "CREATE_RESPONSE" + }, + "pascalCase": { + "unsafeName": "CreateResponse", + "safeName": "CreateResponse" + } + } + }, + "shape": { + "type": "object", + "properties": [ + { + "name": { + "name": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "wireValue": "user" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "named", + "typeName": { + "typeId": "type_:UserModel", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": { + "originalName": "UserModel", + "camelCase": { + "unsafeName": "userModel", + "safeName": "userModel" + }, + "snakeCase": { + "unsafeName": "user_model", + "safeName": "user_model" + }, + "screamingSnakeCase": { + "unsafeName": "USER_MODEL", + "safeName": "USER_MODEL" + }, + "pascalCase": { + "unsafeName": "UserModel", + "safeName": "UserModel" + } + } + }, + "shape": { + "type": "object", + "properties": [ + { + "name": { + "name": { + "originalName": "username", + "camelCase": { + "unsafeName": "username", + "safeName": "username" + }, + "snakeCase": { + "unsafeName": "username", + "safeName": "username" + }, + "screamingSnakeCase": { + "unsafeName": "USERNAME", + "safeName": "USERNAME" + }, + "pascalCase": { + "unsafeName": "Username", + "safeName": "Username" + } + }, + "wireValue": "username" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "username" + } + } + }, + "jsonExample": "username" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "username" + }, + "originalTypeDeclaration": { + "typeId": "type_:UserModel", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": { + "originalName": "UserModel", + "camelCase": { + "unsafeName": "userModel", + "safeName": "userModel" + }, + "snakeCase": { + "unsafeName": "user_model", + "safeName": "user_model" + }, + "screamingSnakeCase": { + "unsafeName": "USER_MODEL", + "safeName": "USER_MODEL" + }, + "pascalCase": { + "unsafeName": "UserModel", + "safeName": "UserModel" + } + } + } + }, + { + "name": { + "name": { + "originalName": "email", + "camelCase": { + "unsafeName": "email", + "safeName": "email" + }, + "snakeCase": { + "unsafeName": "email", + "safeName": "email" + }, + "screamingSnakeCase": { + "unsafeName": "EMAIL", + "safeName": "EMAIL" + }, + "pascalCase": { + "unsafeName": "Email", + "safeName": "Email" + } + }, + "wireValue": "email" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "email" + } + } + }, + "jsonExample": "email" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "email" + }, + "originalTypeDeclaration": { + "typeId": "type_:UserModel", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": { + "originalName": "UserModel", + "camelCase": { + "unsafeName": "userModel", + "safeName": "userModel" + }, + "snakeCase": { + "unsafeName": "user_model", + "safeName": "user_model" + }, + "screamingSnakeCase": { + "unsafeName": "USER_MODEL", + "safeName": "USER_MODEL" + }, + "pascalCase": { + "unsafeName": "UserModel", + "safeName": "UserModel" + } + } + } + }, + { + "name": { + "name": { + "originalName": "age", + "camelCase": { + "unsafeName": "age", + "safeName": "age" + }, + "snakeCase": { + "unsafeName": "age", + "safeName": "age" + }, + "screamingSnakeCase": { + "unsafeName": "AGE", + "safeName": "AGE" + }, + "pascalCase": { + "unsafeName": "Age", + "safeName": "Age" + } + }, + "wireValue": "age" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "uint", + "uint": 1 + } + }, + "jsonExample": 1 + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "UINT", + "v2": null + } + } + } + }, + "jsonExample": 1 + }, + "originalTypeDeclaration": { + "typeId": "type_:UserModel", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": { + "originalName": "UserModel", + "camelCase": { + "unsafeName": "userModel", + "safeName": "userModel" + }, + "snakeCase": { + "unsafeName": "user_model", + "safeName": "user_model" + }, + "screamingSnakeCase": { + "unsafeName": "USER_MODEL", + "safeName": "USER_MODEL" + }, + "pascalCase": { + "unsafeName": "UserModel", + "safeName": "UserModel" + } + } + } + }, + { + "name": { + "name": { + "originalName": "weight", + "camelCase": { + "unsafeName": "weight", + "safeName": "weight" + }, + "snakeCase": { + "unsafeName": "weight", + "safeName": "weight" + }, + "screamingSnakeCase": { + "unsafeName": "WEIGHT", + "safeName": "WEIGHT" + }, + "pascalCase": { + "unsafeName": "Weight", + "safeName": "Weight" + } + }, + "wireValue": "weight" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "float", + "float": 1.1 + } + }, + "jsonExample": 1.1 + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "FLOAT", + "v2": null + } + } + } + }, + "jsonExample": 1.1 + }, + "originalTypeDeclaration": { + "typeId": "type_:UserModel", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": { + "originalName": "UserModel", + "camelCase": { + "unsafeName": "userModel", + "safeName": "userModel" + }, + "snakeCase": { + "unsafeName": "user_model", + "safeName": "user_model" + }, + "screamingSnakeCase": { + "unsafeName": "USER_MODEL", + "safeName": "USER_MODEL" + }, + "pascalCase": { + "unsafeName": "UserModel", + "safeName": "UserModel" + } + } + } + }, + { + "name": { + "name": { + "originalName": "metadata", + "camelCase": { + "unsafeName": "metadata", + "safeName": "metadata" + }, + "snakeCase": { + "unsafeName": "metadata", + "safeName": "metadata" + }, + "screamingSnakeCase": { + "unsafeName": "METADATA", + "safeName": "METADATA" + }, + "pascalCase": { + "unsafeName": "Metadata", + "safeName": "Metadata" + } + }, + "wireValue": "metadata" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "container", + "container": { + "type": "map", + "map": [ + { + "key": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "key" + } + } + }, + "jsonExample": "key" + }, + "value": { + "shape": { + "type": "unknown", + "unknown": "value" + }, + "jsonExample": "value" + } + } + ], + "keyType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "valueType": { + "_type": "unknown" + } + } + }, + "jsonExample": { + "key": "value" + } + }, + "valueType": { + "_type": "container", + "container": { + "_type": "map", + "keyType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "valueType": { + "_type": "unknown" + } + } + } + } + }, + "jsonExample": { + "key": "value" + } + }, + "originalTypeDeclaration": { + "typeId": "type_:UserModel", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": { + "originalName": "UserModel", + "camelCase": { + "unsafeName": "userModel", + "safeName": "userModel" + }, + "snakeCase": { + "unsafeName": "user_model", + "safeName": "user_model" + }, + "screamingSnakeCase": { + "unsafeName": "USER_MODEL", + "safeName": "USER_MODEL" + }, + "pascalCase": { + "unsafeName": "UserModel", + "safeName": "UserModel" + } + } + } + } + ] + } + }, + "jsonExample": { + "username": "username", + "email": "email", + "age": 1, + "weight": 1.1, + "metadata": { + "key": "value" + } + } + }, + "valueType": { + "_type": "named", + "name": { + "originalName": "UserModel", + "camelCase": { + "unsafeName": "userModel", + "safeName": "userModel" + }, + "snakeCase": { + "unsafeName": "user_model", + "safeName": "user_model" + }, + "screamingSnakeCase": { + "unsafeName": "USER_MODEL", + "safeName": "USER_MODEL" + }, + "pascalCase": { + "unsafeName": "UserModel", + "safeName": "UserModel" + } + }, + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "typeId": "type_:UserModel", + "default": null, + "inline": null + } + } + }, + "jsonExample": { + "username": "username", + "email": "email", + "age": 1, + "weight": 1.1, + "metadata": { + "key": "value" + } + } + }, + "originalTypeDeclaration": { + "typeId": "type_:CreateResponse", + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "name": { + "originalName": "CreateResponse", + "camelCase": { + "unsafeName": "createResponse", + "safeName": "createResponse" + }, + "snakeCase": { + "unsafeName": "create_response", + "safeName": "create_response" + }, + "screamingSnakeCase": { + "unsafeName": "CREATE_RESPONSE", + "safeName": "CREATE_RESPONSE" + }, + "pascalCase": { + "unsafeName": "CreateResponse", + "safeName": "CreateResponse" + } + } + } + } + ] + } + }, + "jsonExample": { + "user": { + "username": "username", + "email": "email", + "age": 1, + "weight": 1.1, + "metadata": { + "key": "value" + } + } + } + } + } + }, + "id": "4e9e6a8deac43e17c75fea5e338cf78006465d8e", + "docs": null + } + } + ], + "pagination": null, + "availability": null, + "docs": null + } + ] + } + }, + "constants": { + "errorInstanceIdKey": { + "name": { + "originalName": "errorInstanceId", + "camelCase": { + "unsafeName": "errorInstanceID", + "safeName": "errorInstanceID" + }, + "snakeCase": { + "unsafeName": "error_instance_id", + "safeName": "error_instance_id" + }, + "screamingSnakeCase": { + "unsafeName": "ERROR_INSTANCE_ID", + "safeName": "ERROR_INSTANCE_ID" + }, + "pascalCase": { + "unsafeName": "ErrorInstanceID", + "safeName": "ErrorInstanceID" + } + }, + "wireValue": "errorInstanceId" + } + }, + "environments": null, + "errorDiscriminationStrategy": { + "type": "statusCode" + }, + "basePath": null, + "pathParameters": [], + "variables": [], + "serviceTypeReferenceInfo": { + "typesReferencedOnlyByService": {}, + "sharedTypes": [ + "type_:CreateResponse", + "type_:UserModel" + ] + }, + "webhookGroups": {}, + "websocketChannels": {}, + "readmeConfig": null, + "sourceConfig": null, + "subpackages": { + "subpackage_user": { + "name": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "service": "service_user", + "types": [], + "errors": [], + "subpackages": [], + "navigationConfig": null, + "webhooks": null, + "websocket": null, + "hasEndpointsInTree": true, + "docs": null + } + }, + "rootPackage": { + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "websocket": null, + "service": null, + "types": [ + "type_:CreateResponse", + "type_:UserModel" + ], + "errors": [], + "subpackages": [ + "subpackage_user" + ], + "webhooks": null, + "navigationConfig": null, + "hasEndpointsInTree": true, + "docs": null + }, + "sdkConfig": { + "isAuthMandatory": false, + "hasStreamingEndpoints": false, + "hasPaginatedEndpoints": false, + "hasFileDownloadEndpoints": false, + "platformHeaders": { + "language": "X-Fern-Language", + "sdkName": "X-Fern-SDK-Name", + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null + } + } +} \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/grpc.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/grpc.json new file mode 100644 index 00000000000..84e278bc0ee --- /dev/null +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/grpc.json @@ -0,0 +1,4719 @@ +{ + "fdrApiDefinitionId": null, + "apiVersion": null, + "apiName": { + "originalName": "api", + "camelCase": { + "unsafeName": "api", + "safeName": "api" + }, + "snakeCase": { + "unsafeName": "api", + "safeName": "api" + }, + "screamingSnakeCase": { + "unsafeName": "API", + "safeName": "API" + }, + "pascalCase": { + "unsafeName": "API", + "safeName": "API" + } + }, + "apiDisplayName": null, + "apiDocs": null, + "auth": { + "requirement": "ALL", + "schemes": [], + "docs": null + }, + "headers": [], + "idempotencyHeaders": [], + "types": { + "type_user:Metadata": { + "name": { + "name": { + "originalName": "Metadata", + "camelCase": { + "unsafeName": "metadata", + "safeName": "metadata" + }, + "snakeCase": { + "unsafeName": "metadata", + "safeName": "metadata" + }, + "screamingSnakeCase": { + "unsafeName": "METADATA", + "safeName": "METADATA" + }, + "pascalCase": { + "unsafeName": "Metadata", + "safeName": "Metadata" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:Metadata" + }, + "shape": { + "_type": "alias", + "aliasOf": { + "_type": "container", + "container": { + "_type": "map", + "keyType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "named", + "name": { + "originalName": "MetadataValue", + "camelCase": { + "unsafeName": "metadataValue", + "safeName": "metadataValue" + }, + "snakeCase": { + "unsafeName": "metadata_value", + "safeName": "metadata_value" + }, + "screamingSnakeCase": { + "unsafeName": "METADATA_VALUE", + "safeName": "METADATA_VALUE" + }, + "pascalCase": { + "unsafeName": "MetadataValue", + "safeName": "MetadataValue" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:MetadataValue", + "default": null, + "inline": null + } + } + } + } + }, + "resolvedType": { + "_type": "container", + "container": { + "_type": "map", + "keyType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "named", + "name": { + "originalName": "MetadataValue", + "camelCase": { + "unsafeName": "metadataValue", + "safeName": "metadataValue" + }, + "snakeCase": { + "unsafeName": "metadata_value", + "safeName": "metadata_value" + }, + "screamingSnakeCase": { + "unsafeName": "METADATA_VALUE", + "safeName": "METADATA_VALUE" + }, + "pascalCase": { + "unsafeName": "MetadataValue", + "safeName": "MetadataValue" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:MetadataValue", + "default": null, + "inline": null + } + } + } + } + } + }, + "referencedTypes": [ + "type_user:MetadataValue" + ], + "encoding": null, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "availability": null, + "docs": null + }, + "type_user:MetadataValue": { + "name": { + "name": { + "originalName": "MetadataValue", + "camelCase": { + "unsafeName": "metadataValue", + "safeName": "metadataValue" + }, + "snakeCase": { + "unsafeName": "metadata_value", + "safeName": "metadata_value" + }, + "screamingSnakeCase": { + "unsafeName": "METADATA_VALUE", + "safeName": "METADATA_VALUE" + }, + "pascalCase": { + "unsafeName": "MetadataValue", + "safeName": "MetadataValue" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:MetadataValue" + }, + "shape": { + "_type": "undiscriminatedUnion", + "members": [ + { + "type": { + "_type": "primitive", + "primitive": { + "v1": "DOUBLE", + "v2": { + "type": "double", + "default": null, + "validation": null + } + } + }, + "docs": null + }, + { + "type": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "docs": null + }, + { + "type": { + "_type": "primitive", + "primitive": { + "v1": "BOOLEAN", + "v2": null + } + }, + "docs": null + }, + { + "type": { + "_type": "container", + "container": { + "_type": "list", + "list": { + "_type": "named", + "name": { + "originalName": "MetadataValue", + "camelCase": { + "unsafeName": "metadataValue", + "safeName": "metadataValue" + }, + "snakeCase": { + "unsafeName": "metadata_value", + "safeName": "metadata_value" + }, + "screamingSnakeCase": { + "unsafeName": "METADATA_VALUE", + "safeName": "METADATA_VALUE" + }, + "pascalCase": { + "unsafeName": "MetadataValue", + "safeName": "MetadataValue" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:MetadataValue", + "default": null, + "inline": null + } + } + }, + "docs": null + } + ] + }, + "referencedTypes": [ + "type_user:MetadataValue" + ], + "encoding": null, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "availability": null, + "docs": null + }, + "type_user:User": { + "name": { + "name": { + "originalName": "User", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:User" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": { + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + }, + "wireValue": "id" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "username", + "camelCase": { + "unsafeName": "username", + "safeName": "username" + }, + "snakeCase": { + "unsafeName": "username", + "safeName": "username" + }, + "screamingSnakeCase": { + "unsafeName": "USERNAME", + "safeName": "USERNAME" + }, + "pascalCase": { + "unsafeName": "Username", + "safeName": "Username" + } + }, + "wireValue": "username" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "email", + "camelCase": { + "unsafeName": "email", + "safeName": "email" + }, + "snakeCase": { + "unsafeName": "email", + "safeName": "email" + }, + "screamingSnakeCase": { + "unsafeName": "EMAIL", + "safeName": "EMAIL" + }, + "pascalCase": { + "unsafeName": "Email", + "safeName": "Email" + } + }, + "wireValue": "email" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "age", + "camelCase": { + "unsafeName": "age", + "safeName": "age" + }, + "snakeCase": { + "unsafeName": "age", + "safeName": "age" + }, + "screamingSnakeCase": { + "unsafeName": "AGE", + "safeName": "AGE" + }, + "pascalCase": { + "unsafeName": "Age", + "safeName": "Age" + } + }, + "wireValue": "age" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "UINT", + "v2": null + } + } + } + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "weight", + "camelCase": { + "unsafeName": "weight", + "safeName": "weight" + }, + "snakeCase": { + "unsafeName": "weight", + "safeName": "weight" + }, + "screamingSnakeCase": { + "unsafeName": "WEIGHT", + "safeName": "WEIGHT" + }, + "pascalCase": { + "unsafeName": "Weight", + "safeName": "Weight" + } + }, + "wireValue": "weight" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "FLOAT", + "v2": null + } + } + } + }, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "metadata", + "camelCase": { + "unsafeName": "metadata", + "safeName": "metadata" + }, + "snakeCase": { + "unsafeName": "metadata", + "safeName": "metadata" + }, + "screamingSnakeCase": { + "unsafeName": "METADATA", + "safeName": "METADATA" + }, + "pascalCase": { + "unsafeName": "Metadata", + "safeName": "Metadata" + } + }, + "wireValue": "metadata" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "named", + "name": { + "originalName": "Metadata", + "camelCase": { + "unsafeName": "metadata", + "safeName": "metadata" + }, + "snakeCase": { + "unsafeName": "metadata", + "safeName": "metadata" + }, + "screamingSnakeCase": { + "unsafeName": "METADATA", + "safeName": "METADATA" + }, + "pascalCase": { + "unsafeName": "Metadata", + "safeName": "Metadata" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:Metadata", + "default": null, + "inline": null + } + } + }, + "availability": null, + "docs": null + } + ], + "extra-properties": false, + "extendedProperties": [] + }, + "referencedTypes": [ + "type_user:Metadata", + "type_user:MetadataValue" + ], + "encoding": null, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "availability": null, + "docs": null + }, + "type_user:CreateUserResponse": { + "name": { + "name": { + "originalName": "CreateUserResponse", + "camelCase": { + "unsafeName": "createUserResponse", + "safeName": "createUserResponse" + }, + "snakeCase": { + "unsafeName": "create_user_response", + "safeName": "create_user_response" + }, + "screamingSnakeCase": { + "unsafeName": "CREATE_USER_RESPONSE", + "safeName": "CREATE_USER_RESPONSE" + }, + "pascalCase": { + "unsafeName": "CreateUserResponse", + "safeName": "CreateUserResponse" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:CreateUserResponse" + }, + "shape": { + "_type": "object", + "extends": [], + "properties": [ + { + "name": { + "name": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "wireValue": "user" + }, + "valueType": { + "_type": "named", + "name": { + "originalName": "User", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:User", + "default": null, + "inline": null + }, + "availability": null, + "docs": null + } + ], + "extra-properties": false, + "extendedProperties": [] + }, + "referencedTypes": [ + "type_user:User", + "type_user:Metadata", + "type_user:MetadataValue" + ], + "encoding": null, + "source": null, + "userProvidedExamples": [], + "autogeneratedExamples": [], + "availability": null, + "docs": null + } + }, + "errors": {}, + "services": { + "service_user": { + "availability": null, + "name": { + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + } + }, + "displayName": null, + "basePath": { + "head": "/", + "parts": [] + }, + "headers": [], + "pathParameters": [], + "encoding": null, + "transport": null, + "endpoints": [ + { + "id": "endpoint_user.createUser", + "name": { + "originalName": "createUser", + "camelCase": { + "unsafeName": "createUser", + "safeName": "createUser" + }, + "snakeCase": { + "unsafeName": "create_user", + "safeName": "create_user" + }, + "screamingSnakeCase": { + "unsafeName": "CREATE_USER", + "safeName": "CREATE_USER" + }, + "pascalCase": { + "unsafeName": "CreateUser", + "safeName": "CreateUser" + } + }, + "displayName": null, + "auth": false, + "idempotent": false, + "baseUrl": null, + "method": "POST", + "path": { + "head": "/users", + "parts": [] + }, + "fullPath": { + "head": "/users", + "parts": [] + }, + "pathParameters": [], + "allPathParameters": [], + "queryParameters": [], + "headers": [], + "requestBody": { + "type": "inlinedRequestBody", + "name": { + "originalName": "CreateUserRequest", + "camelCase": { + "unsafeName": "createUserRequest", + "safeName": "createUserRequest" + }, + "snakeCase": { + "unsafeName": "create_user_request", + "safeName": "create_user_request" + }, + "screamingSnakeCase": { + "unsafeName": "CREATE_USER_REQUEST", + "safeName": "CREATE_USER_REQUEST" + }, + "pascalCase": { + "unsafeName": "CreateUserRequest", + "safeName": "CreateUserRequest" + } + }, + "extends": [], + "contentType": null, + "properties": [ + { + "name": { + "name": { + "originalName": "username", + "camelCase": { + "unsafeName": "username", + "safeName": "username" + }, + "snakeCase": { + "unsafeName": "username", + "safeName": "username" + }, + "screamingSnakeCase": { + "unsafeName": "USERNAME", + "safeName": "USERNAME" + }, + "pascalCase": { + "unsafeName": "Username", + "safeName": "Username" + } + }, + "wireValue": "username" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "docs": null + }, + { + "name": { + "name": { + "originalName": "email", + "camelCase": { + "unsafeName": "email", + "safeName": "email" + }, + "snakeCase": { + "unsafeName": "email", + "safeName": "email" + }, + "screamingSnakeCase": { + "unsafeName": "EMAIL", + "safeName": "EMAIL" + }, + "pascalCase": { + "unsafeName": "Email", + "safeName": "Email" + } + }, + "wireValue": "email" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "docs": null + }, + { + "name": { + "name": { + "originalName": "age", + "camelCase": { + "unsafeName": "age", + "safeName": "age" + }, + "snakeCase": { + "unsafeName": "age", + "safeName": "age" + }, + "screamingSnakeCase": { + "unsafeName": "AGE", + "safeName": "AGE" + }, + "pascalCase": { + "unsafeName": "Age", + "safeName": "Age" + } + }, + "wireValue": "age" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "UINT", + "v2": null + } + } + } + }, + "docs": null + }, + { + "name": { + "name": { + "originalName": "weight", + "camelCase": { + "unsafeName": "weight", + "safeName": "weight" + }, + "snakeCase": { + "unsafeName": "weight", + "safeName": "weight" + }, + "screamingSnakeCase": { + "unsafeName": "WEIGHT", + "safeName": "WEIGHT" + }, + "pascalCase": { + "unsafeName": "Weight", + "safeName": "Weight" + } + }, + "wireValue": "weight" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "FLOAT", + "v2": null + } + } + } + }, + "docs": null + } + ], + "extra-properties": false, + "extendedProperties": [] + }, + "sdkRequest": { + "shape": { + "type": "wrapper", + "wrapperName": { + "originalName": "CreateUserRequest", + "camelCase": { + "unsafeName": "createUserRequest", + "safeName": "createUserRequest" + }, + "snakeCase": { + "unsafeName": "create_user_request", + "safeName": "create_user_request" + }, + "screamingSnakeCase": { + "unsafeName": "CREATE_USER_REQUEST", + "safeName": "CREATE_USER_REQUEST" + }, + "pascalCase": { + "unsafeName": "CreateUserRequest", + "safeName": "CreateUserRequest" + } + }, + "bodyKey": { + "originalName": "body", + "camelCase": { + "unsafeName": "body", + "safeName": "body" + }, + "snakeCase": { + "unsafeName": "body", + "safeName": "body" + }, + "screamingSnakeCase": { + "unsafeName": "BODY", + "safeName": "BODY" + }, + "pascalCase": { + "unsafeName": "Body", + "safeName": "Body" + } + } + }, + "requestParameterName": { + "originalName": "request", + "camelCase": { + "unsafeName": "request", + "safeName": "request" + }, + "snakeCase": { + "unsafeName": "request", + "safeName": "request" + }, + "screamingSnakeCase": { + "unsafeName": "REQUEST", + "safeName": "REQUEST" + }, + "pascalCase": { + "unsafeName": "Request", + "safeName": "Request" + } + }, + "streamParameter": null + }, + "response": { + "body": { + "type": "json", + "value": { + "type": "response", + "responseBodyType": { + "_type": "named", + "name": { + "originalName": "CreateUserResponse", + "camelCase": { + "unsafeName": "createUserResponse", + "safeName": "createUserResponse" + }, + "snakeCase": { + "unsafeName": "create_user_response", + "safeName": "create_user_response" + }, + "screamingSnakeCase": { + "unsafeName": "CREATE_USER_RESPONSE", + "safeName": "CREATE_USER_RESPONSE" + }, + "pascalCase": { + "unsafeName": "CreateUserResponse", + "safeName": "CreateUserResponse" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:CreateUserResponse", + "default": null, + "inline": null + }, + "docs": null + } + }, + "status-code": null + }, + "errors": [], + "userSpecifiedExamples": [], + "autogeneratedExamples": [ + { + "example": { + "url": "/users", + "rootPathParameters": [], + "servicePathParameters": [], + "endpointPathParameters": [], + "serviceHeaders": [], + "endpointHeaders": [], + "queryParameters": [], + "request": { + "type": "inlinedRequestBody", + "properties": [ + { + "name": { + "name": { + "originalName": "username", + "camelCase": { + "unsafeName": "username", + "safeName": "username" + }, + "snakeCase": { + "unsafeName": "username", + "safeName": "username" + }, + "screamingSnakeCase": { + "unsafeName": "USERNAME", + "safeName": "USERNAME" + }, + "pascalCase": { + "unsafeName": "Username", + "safeName": "Username" + } + }, + "wireValue": "username" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "string" + } + } + }, + "jsonExample": "string" + }, + "originalTypeDeclaration": null + }, + { + "name": { + "name": { + "originalName": "email", + "camelCase": { + "unsafeName": "email", + "safeName": "email" + }, + "snakeCase": { + "unsafeName": "email", + "safeName": "email" + }, + "screamingSnakeCase": { + "unsafeName": "EMAIL", + "safeName": "EMAIL" + }, + "pascalCase": { + "unsafeName": "Email", + "safeName": "Email" + } + }, + "wireValue": "email" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "string" + } + } + }, + "jsonExample": "string" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "string" + }, + "originalTypeDeclaration": null + }, + { + "name": { + "name": { + "originalName": "age", + "camelCase": { + "unsafeName": "age", + "safeName": "age" + }, + "snakeCase": { + "unsafeName": "age", + "safeName": "age" + }, + "screamingSnakeCase": { + "unsafeName": "AGE", + "safeName": "AGE" + }, + "pascalCase": { + "unsafeName": "Age", + "safeName": "Age" + } + }, + "wireValue": "age" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "uint", + "uint": 1 + } + }, + "jsonExample": 1 + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "UINT", + "v2": null + } + } + } + }, + "jsonExample": 1 + }, + "originalTypeDeclaration": null + }, + { + "name": { + "name": { + "originalName": "weight", + "camelCase": { + "unsafeName": "weight", + "safeName": "weight" + }, + "snakeCase": { + "unsafeName": "weight", + "safeName": "weight" + }, + "screamingSnakeCase": { + "unsafeName": "WEIGHT", + "safeName": "WEIGHT" + }, + "pascalCase": { + "unsafeName": "Weight", + "safeName": "Weight" + } + }, + "wireValue": "weight" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "float", + "float": 1.1 + } + }, + "jsonExample": 1.1 + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "FLOAT", + "v2": null + } + } + } + }, + "jsonExample": 1.1 + }, + "originalTypeDeclaration": null + } + ], + "jsonExample": { + "username": "string", + "email": "string", + "age": 1, + "weight": 1.1 + } + }, + "name": null, + "response": { + "type": "ok", + "value": { + "type": "body", + "value": { + "shape": { + "type": "named", + "typeName": { + "name": { + "originalName": "CreateUserResponse", + "camelCase": { + "unsafeName": "createUserResponse", + "safeName": "createUserResponse" + }, + "snakeCase": { + "unsafeName": "create_user_response", + "safeName": "create_user_response" + }, + "screamingSnakeCase": { + "unsafeName": "CREATE_USER_RESPONSE", + "safeName": "CREATE_USER_RESPONSE" + }, + "pascalCase": { + "unsafeName": "CreateUserResponse", + "safeName": "CreateUserResponse" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:CreateUserResponse" + }, + "shape": { + "type": "object", + "properties": [ + { + "name": { + "name": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "wireValue": "user" + }, + "value": { + "shape": { + "type": "named", + "typeName": { + "name": { + "originalName": "User", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:User" + }, + "shape": { + "type": "object", + "properties": [ + { + "name": { + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + }, + "wireValue": "id" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "string" + } + } + }, + "jsonExample": "string" + }, + "originalTypeDeclaration": { + "name": { + "originalName": "User", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:User" + } + }, + { + "name": { + "name": { + "originalName": "username", + "camelCase": { + "unsafeName": "username", + "safeName": "username" + }, + "snakeCase": { + "unsafeName": "username", + "safeName": "username" + }, + "screamingSnakeCase": { + "unsafeName": "USERNAME", + "safeName": "USERNAME" + }, + "pascalCase": { + "unsafeName": "Username", + "safeName": "Username" + } + }, + "wireValue": "username" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "string" + } + } + }, + "jsonExample": "string" + }, + "originalTypeDeclaration": { + "name": { + "originalName": "User", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:User" + } + }, + { + "name": { + "name": { + "originalName": "email", + "camelCase": { + "unsafeName": "email", + "safeName": "email" + }, + "snakeCase": { + "unsafeName": "email", + "safeName": "email" + }, + "screamingSnakeCase": { + "unsafeName": "EMAIL", + "safeName": "EMAIL" + }, + "pascalCase": { + "unsafeName": "Email", + "safeName": "Email" + } + }, + "wireValue": "email" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "string" + } + } + }, + "jsonExample": "string" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "string" + }, + "originalTypeDeclaration": { + "name": { + "originalName": "User", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:User" + } + }, + { + "name": { + "name": { + "originalName": "age", + "camelCase": { + "unsafeName": "age", + "safeName": "age" + }, + "snakeCase": { + "unsafeName": "age", + "safeName": "age" + }, + "screamingSnakeCase": { + "unsafeName": "AGE", + "safeName": "AGE" + }, + "pascalCase": { + "unsafeName": "Age", + "safeName": "Age" + } + }, + "wireValue": "age" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "uint", + "uint": 1 + } + }, + "jsonExample": 1 + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "UINT", + "v2": null + } + } + } + }, + "jsonExample": 1 + }, + "originalTypeDeclaration": { + "name": { + "originalName": "User", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:User" + } + }, + { + "name": { + "name": { + "originalName": "weight", + "camelCase": { + "unsafeName": "weight", + "safeName": "weight" + }, + "snakeCase": { + "unsafeName": "weight", + "safeName": "weight" + }, + "screamingSnakeCase": { + "unsafeName": "WEIGHT", + "safeName": "WEIGHT" + }, + "pascalCase": { + "unsafeName": "Weight", + "safeName": "Weight" + } + }, + "wireValue": "weight" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "float", + "float": 1.1 + } + }, + "jsonExample": 1.1 + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "FLOAT", + "v2": null + } + } + } + }, + "jsonExample": 1.1 + }, + "originalTypeDeclaration": { + "name": { + "originalName": "User", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:User" + } + }, + { + "name": { + "name": { + "originalName": "metadata", + "camelCase": { + "unsafeName": "metadata", + "safeName": "metadata" + }, + "snakeCase": { + "unsafeName": "metadata", + "safeName": "metadata" + }, + "screamingSnakeCase": { + "unsafeName": "METADATA", + "safeName": "METADATA" + }, + "pascalCase": { + "unsafeName": "Metadata", + "safeName": "Metadata" + } + }, + "wireValue": "metadata" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "named", + "typeName": { + "name": { + "originalName": "Metadata", + "camelCase": { + "unsafeName": "metadata", + "safeName": "metadata" + }, + "snakeCase": { + "unsafeName": "metadata", + "safeName": "metadata" + }, + "screamingSnakeCase": { + "unsafeName": "METADATA", + "safeName": "METADATA" + }, + "pascalCase": { + "unsafeName": "Metadata", + "safeName": "Metadata" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:Metadata" + }, + "shape": { + "type": "alias", + "value": { + "shape": { + "type": "container", + "container": { + "type": "map", + "map": [ + { + "key": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "string" + } + } + }, + "jsonExample": "string" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "unknown", + "unknown": { + "key": "value" + } + }, + "jsonExample": { + "key": "value" + } + }, + "valueType": { + "_type": "named", + "name": { + "originalName": "MetadataValue", + "camelCase": { + "unsafeName": "metadataValue", + "safeName": "metadataValue" + }, + "snakeCase": { + "unsafeName": "metadata_value", + "safeName": "metadata_value" + }, + "screamingSnakeCase": { + "unsafeName": "METADATA_VALUE", + "safeName": "METADATA_VALUE" + }, + "pascalCase": { + "unsafeName": "MetadataValue", + "safeName": "MetadataValue" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:MetadataValue", + "default": null, + "inline": null + } + } + }, + "jsonExample": { + "key": "value" + } + } + } + ], + "keyType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "named", + "name": { + "originalName": "MetadataValue", + "camelCase": { + "unsafeName": "metadataValue", + "safeName": "metadataValue" + }, + "snakeCase": { + "unsafeName": "metadata_value", + "safeName": "metadata_value" + }, + "screamingSnakeCase": { + "unsafeName": "METADATA_VALUE", + "safeName": "METADATA_VALUE" + }, + "pascalCase": { + "unsafeName": "MetadataValue", + "safeName": "MetadataValue" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:MetadataValue", + "default": null, + "inline": null + } + } + } + } + }, + "jsonExample": { + "string": { + "key": "value" + } + } + } + } + }, + "jsonExample": { + "string": { + "key": "value" + } + } + }, + "valueType": { + "_type": "named", + "name": { + "originalName": "Metadata", + "camelCase": { + "unsafeName": "metadata", + "safeName": "metadata" + }, + "snakeCase": { + "unsafeName": "metadata", + "safeName": "metadata" + }, + "screamingSnakeCase": { + "unsafeName": "METADATA", + "safeName": "METADATA" + }, + "pascalCase": { + "unsafeName": "Metadata", + "safeName": "Metadata" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:Metadata", + "default": null, + "inline": null + } + } + }, + "jsonExample": { + "string": { + "key": "value" + } + } + }, + "originalTypeDeclaration": { + "name": { + "originalName": "User", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:User" + } + } + ] + } + }, + "jsonExample": { + "id": "string", + "username": "string", + "email": "string", + "age": 1, + "weight": 1.1, + "metadata": { + "string": { + "key": "value" + } + } + } + }, + "originalTypeDeclaration": { + "name": { + "originalName": "CreateUserResponse", + "camelCase": { + "unsafeName": "createUserResponse", + "safeName": "createUserResponse" + }, + "snakeCase": { + "unsafeName": "create_user_response", + "safeName": "create_user_response" + }, + "screamingSnakeCase": { + "unsafeName": "CREATE_USER_RESPONSE", + "safeName": "CREATE_USER_RESPONSE" + }, + "pascalCase": { + "unsafeName": "CreateUserResponse", + "safeName": "CreateUserResponse" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:CreateUserResponse" + } + } + ] + } + }, + "jsonExample": { + "user": { + "id": "string", + "username": "string", + "email": "string", + "age": 1, + "weight": 1.1, + "metadata": { + "string": { + "key": "value" + } + } + } + } + } + } + }, + "id": "9126375cc2a3c189c681d151bafcc356d3695f23", + "docs": null + } + } + ], + "pagination": null, + "availability": null, + "docs": null + }, + { + "id": "endpoint_user.getUser", + "name": { + "originalName": "getUser", + "camelCase": { + "unsafeName": "getUser", + "safeName": "getUser" + }, + "snakeCase": { + "unsafeName": "get_user", + "safeName": "get_user" + }, + "screamingSnakeCase": { + "unsafeName": "GET_USER", + "safeName": "GET_USER" + }, + "pascalCase": { + "unsafeName": "GetUser", + "safeName": "GetUser" + } + }, + "displayName": null, + "auth": false, + "idempotent": false, + "baseUrl": null, + "method": "GET", + "path": { + "head": "/users", + "parts": [] + }, + "fullPath": { + "head": "/users", + "parts": [] + }, + "pathParameters": [], + "allPathParameters": [], + "queryParameters": [ + { + "name": { + "name": { + "originalName": "username", + "camelCase": { + "unsafeName": "username", + "safeName": "username" + }, + "snakeCase": { + "unsafeName": "username", + "safeName": "username" + }, + "screamingSnakeCase": { + "unsafeName": "USERNAME", + "safeName": "USERNAME" + }, + "pascalCase": { + "unsafeName": "Username", + "safeName": "Username" + } + }, + "wireValue": "username" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "allowMultiple": false, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "age", + "camelCase": { + "unsafeName": "age", + "safeName": "age" + }, + "snakeCase": { + "unsafeName": "age", + "safeName": "age" + }, + "screamingSnakeCase": { + "unsafeName": "AGE", + "safeName": "AGE" + }, + "pascalCase": { + "unsafeName": "Age", + "safeName": "Age" + } + }, + "wireValue": "age" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "UINT", + "v2": null + } + } + } + }, + "allowMultiple": false, + "availability": null, + "docs": null + }, + { + "name": { + "name": { + "originalName": "weight", + "camelCase": { + "unsafeName": "weight", + "safeName": "weight" + }, + "snakeCase": { + "unsafeName": "weight", + "safeName": "weight" + }, + "screamingSnakeCase": { + "unsafeName": "WEIGHT", + "safeName": "WEIGHT" + }, + "pascalCase": { + "unsafeName": "Weight", + "safeName": "Weight" + } + }, + "wireValue": "weight" + }, + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "primitive", + "primitive": { + "v1": "FLOAT", + "v2": null + } + } + } + }, + "allowMultiple": false, + "availability": null, + "docs": null + } + ], + "headers": [], + "requestBody": null, + "sdkRequest": { + "shape": { + "type": "wrapper", + "wrapperName": { + "originalName": "GetUserRequest", + "camelCase": { + "unsafeName": "getUserRequest", + "safeName": "getUserRequest" + }, + "snakeCase": { + "unsafeName": "get_user_request", + "safeName": "get_user_request" + }, + "screamingSnakeCase": { + "unsafeName": "GET_USER_REQUEST", + "safeName": "GET_USER_REQUEST" + }, + "pascalCase": { + "unsafeName": "GetUserRequest", + "safeName": "GetUserRequest" + } + }, + "bodyKey": { + "originalName": "body", + "camelCase": { + "unsafeName": "body", + "safeName": "body" + }, + "snakeCase": { + "unsafeName": "body", + "safeName": "body" + }, + "screamingSnakeCase": { + "unsafeName": "BODY", + "safeName": "BODY" + }, + "pascalCase": { + "unsafeName": "Body", + "safeName": "Body" + } + } + }, + "requestParameterName": { + "originalName": "request", + "camelCase": { + "unsafeName": "request", + "safeName": "request" + }, + "snakeCase": { + "unsafeName": "request", + "safeName": "request" + }, + "screamingSnakeCase": { + "unsafeName": "REQUEST", + "safeName": "REQUEST" + }, + "pascalCase": { + "unsafeName": "Request", + "safeName": "Request" + } + }, + "streamParameter": null + }, + "response": { + "body": { + "type": "json", + "value": { + "type": "response", + "responseBodyType": { + "_type": "named", + "name": { + "originalName": "User", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:User", + "default": null, + "inline": null + }, + "docs": null + } + }, + "status-code": null + }, + "errors": [], + "userSpecifiedExamples": [], + "autogeneratedExamples": [ + { + "example": { + "url": "/users", + "rootPathParameters": [], + "servicePathParameters": [], + "endpointPathParameters": [], + "serviceHeaders": [], + "endpointHeaders": [], + "queryParameters": [ + { + "name": { + "name": { + "originalName": "username", + "camelCase": { + "unsafeName": "username", + "safeName": "username" + }, + "snakeCase": { + "unsafeName": "username", + "safeName": "username" + }, + "screamingSnakeCase": { + "unsafeName": "USERNAME", + "safeName": "USERNAME" + }, + "pascalCase": { + "unsafeName": "Username", + "safeName": "Username" + } + }, + "wireValue": "username" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "string" + } + } + }, + "jsonExample": "string" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "string" + } + }, + { + "name": { + "name": { + "originalName": "age", + "camelCase": { + "unsafeName": "age", + "safeName": "age" + }, + "snakeCase": { + "unsafeName": "age", + "safeName": "age" + }, + "screamingSnakeCase": { + "unsafeName": "AGE", + "safeName": "AGE" + }, + "pascalCase": { + "unsafeName": "Age", + "safeName": "Age" + } + }, + "wireValue": "age" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "uint", + "uint": 1 + } + }, + "jsonExample": 1 + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "UINT", + "v2": null + } + } + } + }, + "jsonExample": 1 + } + }, + { + "name": { + "name": { + "originalName": "weight", + "camelCase": { + "unsafeName": "weight", + "safeName": "weight" + }, + "snakeCase": { + "unsafeName": "weight", + "safeName": "weight" + }, + "screamingSnakeCase": { + "unsafeName": "WEIGHT", + "safeName": "WEIGHT" + }, + "pascalCase": { + "unsafeName": "Weight", + "safeName": "Weight" + } + }, + "wireValue": "weight" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "float", + "float": 1.1 + } + }, + "jsonExample": 1.1 + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "FLOAT", + "v2": null + } + } + } + }, + "jsonExample": 1.1 + } + } + ], + "request": null, + "name": null, + "response": { + "type": "ok", + "value": { + "type": "body", + "value": { + "shape": { + "type": "named", + "typeName": { + "name": { + "originalName": "User", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:User" + }, + "shape": { + "type": "object", + "properties": [ + { + "name": { + "name": { + "originalName": "id", + "camelCase": { + "unsafeName": "id", + "safeName": "id" + }, + "snakeCase": { + "unsafeName": "id", + "safeName": "id" + }, + "screamingSnakeCase": { + "unsafeName": "ID", + "safeName": "ID" + }, + "pascalCase": { + "unsafeName": "ID", + "safeName": "ID" + } + }, + "wireValue": "id" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "string" + } + } + }, + "jsonExample": "string" + }, + "originalTypeDeclaration": { + "name": { + "originalName": "User", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:User" + } + }, + { + "name": { + "name": { + "originalName": "username", + "camelCase": { + "unsafeName": "username", + "safeName": "username" + }, + "snakeCase": { + "unsafeName": "username", + "safeName": "username" + }, + "screamingSnakeCase": { + "unsafeName": "USERNAME", + "safeName": "USERNAME" + }, + "pascalCase": { + "unsafeName": "Username", + "safeName": "Username" + } + }, + "wireValue": "username" + }, + "value": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "string" + } + } + }, + "jsonExample": "string" + }, + "originalTypeDeclaration": { + "name": { + "originalName": "User", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:User" + } + }, + { + "name": { + "name": { + "originalName": "email", + "camelCase": { + "unsafeName": "email", + "safeName": "email" + }, + "snakeCase": { + "unsafeName": "email", + "safeName": "email" + }, + "screamingSnakeCase": { + "unsafeName": "EMAIL", + "safeName": "EMAIL" + }, + "pascalCase": { + "unsafeName": "Email", + "safeName": "Email" + } + }, + "wireValue": "email" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "string" + } + } + }, + "jsonExample": "string" + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + } + } + }, + "jsonExample": "string" + }, + "originalTypeDeclaration": { + "name": { + "originalName": "User", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:User" + } + }, + { + "name": { + "name": { + "originalName": "age", + "camelCase": { + "unsafeName": "age", + "safeName": "age" + }, + "snakeCase": { + "unsafeName": "age", + "safeName": "age" + }, + "screamingSnakeCase": { + "unsafeName": "AGE", + "safeName": "AGE" + }, + "pascalCase": { + "unsafeName": "Age", + "safeName": "Age" + } + }, + "wireValue": "age" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "uint", + "uint": 1 + } + }, + "jsonExample": 1 + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "UINT", + "v2": null + } + } + } + }, + "jsonExample": 1 + }, + "originalTypeDeclaration": { + "name": { + "originalName": "User", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:User" + } + }, + { + "name": { + "name": { + "originalName": "weight", + "camelCase": { + "unsafeName": "weight", + "safeName": "weight" + }, + "snakeCase": { + "unsafeName": "weight", + "safeName": "weight" + }, + "screamingSnakeCase": { + "unsafeName": "WEIGHT", + "safeName": "WEIGHT" + }, + "pascalCase": { + "unsafeName": "Weight", + "safeName": "Weight" + } + }, + "wireValue": "weight" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "primitive", + "primitive": { + "type": "float", + "float": 1.1 + } + }, + "jsonExample": 1.1 + }, + "valueType": { + "_type": "primitive", + "primitive": { + "v1": "FLOAT", + "v2": null + } + } + } + }, + "jsonExample": 1.1 + }, + "originalTypeDeclaration": { + "name": { + "originalName": "User", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:User" + } + }, + { + "name": { + "name": { + "originalName": "metadata", + "camelCase": { + "unsafeName": "metadata", + "safeName": "metadata" + }, + "snakeCase": { + "unsafeName": "metadata", + "safeName": "metadata" + }, + "screamingSnakeCase": { + "unsafeName": "METADATA", + "safeName": "METADATA" + }, + "pascalCase": { + "unsafeName": "Metadata", + "safeName": "Metadata" + } + }, + "wireValue": "metadata" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "named", + "typeName": { + "name": { + "originalName": "Metadata", + "camelCase": { + "unsafeName": "metadata", + "safeName": "metadata" + }, + "snakeCase": { + "unsafeName": "metadata", + "safeName": "metadata" + }, + "screamingSnakeCase": { + "unsafeName": "METADATA", + "safeName": "METADATA" + }, + "pascalCase": { + "unsafeName": "Metadata", + "safeName": "Metadata" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:Metadata" + }, + "shape": { + "type": "alias", + "value": { + "shape": { + "type": "container", + "container": { + "type": "map", + "map": [ + { + "key": { + "shape": { + "type": "primitive", + "primitive": { + "type": "string", + "string": { + "original": "string" + } + } + }, + "jsonExample": "string" + }, + "value": { + "shape": { + "type": "container", + "container": { + "type": "optional", + "optional": { + "shape": { + "type": "unknown", + "unknown": { + "key": "value" + } + }, + "jsonExample": { + "key": "value" + } + }, + "valueType": { + "_type": "named", + "name": { + "originalName": "MetadataValue", + "camelCase": { + "unsafeName": "metadataValue", + "safeName": "metadataValue" + }, + "snakeCase": { + "unsafeName": "metadata_value", + "safeName": "metadata_value" + }, + "screamingSnakeCase": { + "unsafeName": "METADATA_VALUE", + "safeName": "METADATA_VALUE" + }, + "pascalCase": { + "unsafeName": "MetadataValue", + "safeName": "MetadataValue" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:MetadataValue", + "default": null, + "inline": null + } + } + }, + "jsonExample": { + "key": "value" + } + } + } + ], + "keyType": { + "_type": "primitive", + "primitive": { + "v1": "STRING", + "v2": { + "type": "string", + "default": null, + "validation": null + } + } + }, + "valueType": { + "_type": "container", + "container": { + "_type": "optional", + "optional": { + "_type": "named", + "name": { + "originalName": "MetadataValue", + "camelCase": { + "unsafeName": "metadataValue", + "safeName": "metadataValue" + }, + "snakeCase": { + "unsafeName": "metadata_value", + "safeName": "metadata_value" + }, + "screamingSnakeCase": { + "unsafeName": "METADATA_VALUE", + "safeName": "METADATA_VALUE" + }, + "pascalCase": { + "unsafeName": "MetadataValue", + "safeName": "MetadataValue" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:MetadataValue", + "default": null, + "inline": null + } + } + } + } + }, + "jsonExample": { + "string": { + "key": "value" + } + } + } + } + }, + "jsonExample": { + "string": { + "key": "value" + } + } + }, + "valueType": { + "_type": "named", + "name": { + "originalName": "Metadata", + "camelCase": { + "unsafeName": "metadata", + "safeName": "metadata" + }, + "snakeCase": { + "unsafeName": "metadata", + "safeName": "metadata" + }, + "screamingSnakeCase": { + "unsafeName": "METADATA", + "safeName": "METADATA" + }, + "pascalCase": { + "unsafeName": "Metadata", + "safeName": "Metadata" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:Metadata", + "default": null, + "inline": null + } + } + }, + "jsonExample": { + "string": { + "key": "value" + } + } + }, + "originalTypeDeclaration": { + "name": { + "originalName": "User", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "typeId": "type_user:User" + } + } + ] + } + }, + "jsonExample": { + "id": "string", + "username": "string", + "email": "string", + "age": 1, + "weight": 1.1, + "metadata": { + "string": { + "key": "value" + } + } + } + } + } + }, + "id": "e1ebad3f3c40049093aacc8613a1fe41a68173cf", + "docs": null + } + } + ], + "pagination": null, + "availability": null, + "docs": null + } + ] + } + }, + "constants": { + "errorInstanceIdKey": { + "name": { + "originalName": "errorInstanceId", + "camelCase": { + "unsafeName": "errorInstanceID", + "safeName": "errorInstanceID" + }, + "snakeCase": { + "unsafeName": "error_instance_id", + "safeName": "error_instance_id" + }, + "screamingSnakeCase": { + "unsafeName": "ERROR_INSTANCE_ID", + "safeName": "ERROR_INSTANCE_ID" + }, + "pascalCase": { + "unsafeName": "ErrorInstanceID", + "safeName": "ErrorInstanceID" + } + }, + "wireValue": "errorInstanceId" + } + }, + "environments": null, + "errorDiscriminationStrategy": { + "type": "statusCode" + }, + "basePath": null, + "pathParameters": [], + "variables": [], + "serviceTypeReferenceInfo": { + "typesReferencedOnlyByService": { + "service_user": [ + "type_user:User", + "type_user:CreateUserResponse" + ] + }, + "sharedTypes": [ + "type_user:Metadata", + "type_user:MetadataValue" + ] + }, + "webhookGroups": {}, + "websocketChannels": {}, + "readmeConfig": null, + "sourceConfig": null, + "subpackages": { + "subpackage_user": { + "name": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + }, + "fernFilepath": { + "allParts": [ + { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + ], + "packagePath": [], + "file": { + "originalName": "user", + "camelCase": { + "unsafeName": "user", + "safeName": "user" + }, + "snakeCase": { + "unsafeName": "user", + "safeName": "user" + }, + "screamingSnakeCase": { + "unsafeName": "USER", + "safeName": "USER" + }, + "pascalCase": { + "unsafeName": "User", + "safeName": "User" + } + } + }, + "service": "service_user", + "types": [ + "type_user:Metadata", + "type_user:MetadataValue", + "type_user:User", + "type_user:CreateUserResponse" + ], + "errors": [], + "subpackages": [], + "navigationConfig": null, + "webhooks": null, + "websocket": null, + "hasEndpointsInTree": true, + "docs": null + } + }, + "rootPackage": { + "fernFilepath": { + "allParts": [], + "packagePath": [], + "file": null + }, + "websocket": null, + "service": null, + "types": [], + "errors": [], + "subpackages": [ + "subpackage_user" + ], + "webhooks": null, + "navigationConfig": null, + "hasEndpointsInTree": true, + "docs": null + }, + "sdkConfig": { + "isAuthMandatory": false, + "hasStreamingEndpoints": false, + "hasPaginatedEndpoints": false, + "hasFileDownloadEndpoints": false, + "platformHeaders": { + "language": "X-Fern-Language", + "sdkName": "X-Fern-SDK-Name", + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null + } + } +} \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/idempotency-headers.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/idempotency-headers.json index 5c367bac539..c213ebf7c15 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/idempotency-headers.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/idempotency-headers.json @@ -257,6 +257,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -319,6 +321,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_payment.create", @@ -1033,6 +1037,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_payment": { "name": { @@ -1136,7 +1141,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/imdb.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/imdb.json index 385086ab15c..185bcf4044e 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/imdb.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/imdb.json @@ -147,6 +147,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -397,6 +399,8 @@ "referencedTypes": [ "type_imdb:MovieId" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -551,6 +555,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -774,6 +780,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_imdb.createMovie", @@ -2670,6 +2678,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_imdb": { "name": { @@ -2777,7 +2786,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/literal.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/literal.json index 3950a818cb0..44fcb9ac3b3 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/literal.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/literal.json @@ -252,6 +252,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -347,6 +349,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -732,6 +736,8 @@ "referencedTypes": [ "type_reference:SomeLiteral" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -827,6 +833,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -889,6 +897,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_headers.send", @@ -1980,6 +1990,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_inlined.send", @@ -4193,6 +4205,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_path.send", @@ -4996,6 +5010,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_query.send", @@ -6046,6 +6062,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_reference.send", @@ -7991,6 +8009,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_headers": { "name": { @@ -8395,7 +8414,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/mixed-case.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/mixed-case.json index da95a5d4594..41ead2a2760 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/mixed-case.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/mixed-case.json @@ -142,6 +142,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -488,6 +490,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -1156,6 +1160,8 @@ "referencedTypes": [ "type_service:User" ], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -2020,6 +2026,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -2398,9 +2406,12 @@ ] }, "referencedTypes": [ + "type_service:ResourceStatus", "type_service:User", "type_service:Organization" ], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": null, @@ -2953,6 +2964,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_service.getResource", @@ -6195,6 +6208,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_service": { "name": { @@ -6302,7 +6316,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/multi-line-docs.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/multi-line-docs.json index 8ba4fe800fa..e4726b63216 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/multi-line-docs.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/multi-line-docs.json @@ -143,6 +143,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -437,6 +439,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -499,6 +503,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_user.getUser", @@ -1552,6 +1558,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_user": { "name": { @@ -1657,7 +1664,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/multi-url-environment-no-default.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/multi-url-environment-no-default.json index f88c150a3f2..f28062fe782 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/multi-url-environment-no-default.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/multi-url-environment-no-default.json @@ -111,6 +111,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_ec2.bootInstance", @@ -411,6 +413,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_s3.getPresignedUrl", @@ -831,6 +835,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_ec2": { "name": { @@ -1006,7 +1011,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/multi-url-environment.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/multi-url-environment.json index 5ad985de69d..66c8b4aa4a6 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/multi-url-environment.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/multi-url-environment.json @@ -111,6 +111,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_ec2.bootInstance", @@ -411,6 +413,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_s3.getPresignedUrl", @@ -831,6 +835,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_ec2": { "name": { @@ -1006,7 +1011,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/no-environment.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/no-environment.json index cff979bf319..6f770018190 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/no-environment.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/no-environment.json @@ -111,6 +111,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_dummy.getDummy", @@ -255,6 +257,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_dummy": { "name": { @@ -356,7 +359,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/oauth-client-credentials-default.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/oauth-client-credentials-default.json index 2bfca4ccda0..fb8b83159ec 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/oauth-client-credentials-default.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/oauth-client-credentials-default.json @@ -328,6 +328,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -390,6 +392,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_auth.getToken", @@ -1181,6 +1185,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_auth": { "name": { @@ -1284,7 +1289,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/oauth-client-credentials-environment-variables.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/oauth-client-credentials-environment-variables.json index a54955f8e8a..0dcb0935636 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/oauth-client-credentials-environment-variables.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/oauth-client-credentials-environment-variables.json @@ -410,6 +410,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -472,6 +474,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_auth.getTokenWithClientCredentials", @@ -2678,6 +2682,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_auth": { "name": { @@ -2781,7 +2786,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/oauth-client-credentials-nested-root.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/oauth-client-credentials-nested-root.json index c1bc2891471..e71f9784fe3 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/oauth-client-credentials-nested-root.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/oauth-client-credentials-nested-root.json @@ -412,6 +412,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -476,6 +478,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_auth.getToken", @@ -1575,6 +1579,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_auth": { "name": { @@ -1680,7 +1685,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/oauth-client-credentials.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/oauth-client-credentials.json index 0a6db20143f..65bfd3bcda5 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/oauth-client-credentials.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/oauth-client-credentials.json @@ -410,6 +410,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -472,6 +474,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_auth.getTokenWithClientCredentials", @@ -2678,6 +2682,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_auth": { "name": { @@ -2781,7 +2786,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/object.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/object.json index 2aff5c14f85..66867640b91 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/object.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/object.json @@ -960,6 +960,8 @@ "referencedTypes": [ "type_:Name" ], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -3174,6 +3176,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -3381,6 +3385,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": {}, "rootPackage": { "fernFilepath": { @@ -3409,7 +3414,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/objects-with-imports.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/objects-with-imports.json index a65b4641ac5..d6429122146 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/objects-with-imports.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/objects-with-imports.json @@ -288,6 +288,8 @@ "referencedTypes": [ "type_commons/metadata:Metadata" ], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -2150,6 +2152,8 @@ "type_:Node", "type_commons/metadata:Metadata" ], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -4371,6 +4375,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -5052,6 +5058,8 @@ "referencedTypes": [ "type_file:FileInfo" ], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -6021,6 +6029,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -6529,6 +6539,8 @@ "type_file:FileInfo", "type_file/directory:Directory" ], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -8964,6 +8976,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_commons": { "name": { @@ -9379,7 +9392,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/optional.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/optional.json index 5b514b2c95d..bd80d1d7f87 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/optional.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/optional.json @@ -86,6 +86,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_optional.sendOptionalBody", @@ -402,6 +404,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_optional": { "name": { @@ -503,7 +506,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/package-yml.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/package-yml.json index 1750a0acd01..cffe8e8e299 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/package-yml.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/package-yml.json @@ -141,6 +141,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -165,6 +167,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_.echo", @@ -891,6 +895,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_service.nop", @@ -1336,6 +1342,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_service": { "name": { @@ -1439,7 +1446,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/pagination.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/pagination.json index 94eb38ea68b..7c6dff9ecb6 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/pagination.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/pagination.json @@ -150,6 +150,8 @@ "referencedTypes": [ "type_:UsernamePage" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -278,6 +280,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -408,6 +412,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -531,6 +537,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -654,6 +662,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -836,6 +846,8 @@ "referencedTypes": [ "type_users:User" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1052,6 +1064,8 @@ "type_users:UserListContainer", "type_users:User" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1175,6 +1189,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1496,6 +1512,8 @@ "type_users:UserListContainer", "type_users:User" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1817,6 +1835,8 @@ "type_users:NextPage", "type_users:User" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -2110,6 +2130,8 @@ "referencedTypes": [ "type_users:NextPage" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -2264,6 +2286,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -2418,6 +2442,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -2480,6 +2506,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_users.listWithCursorPagination", @@ -12910,6 +12938,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_users": { "name": { @@ -13026,7 +13055,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/plain-text.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/plain-text.json index 93b0ced89a2..7a1be93cac6 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/plain-text.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/plain-text.json @@ -86,6 +86,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_service.getText", @@ -216,6 +218,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_service": { "name": { @@ -317,7 +320,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/query-parameters.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/query-parameters.json index 72eb3e18684..7a3241a4aac 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/query-parameters.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/query-parameters.json @@ -185,6 +185,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -398,6 +400,8 @@ "referencedTypes": [ "type_user:User" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -460,6 +464,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_user.getUsername", @@ -4359,6 +4365,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_user": { "name": { @@ -4463,7 +4470,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/reserved-keywords.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/reserved-keywords.json index 0d925e5e41c..9715348f34d 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/reserved-keywords.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/reserved-keywords.json @@ -142,6 +142,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -313,6 +315,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -375,6 +379,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_package.test", @@ -676,6 +682,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_package": { "name": { @@ -780,7 +787,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/response-property.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/response-property.json index 0ef698df84c..debb074f7cb 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/response-property.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/response-property.json @@ -104,6 +104,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -215,6 +217,8 @@ "referencedTypes": [ "type_:StringResponse" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -311,6 +315,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -428,6 +434,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -653,6 +661,8 @@ "referencedTypes": [ "type_service:WithDocs" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -807,6 +817,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1170,6 +1182,8 @@ "type_service:WithDocs", "type_service:Movie" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1232,6 +1246,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_service.getMovie", @@ -3774,6 +3790,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_service": { "name": { @@ -3884,7 +3901,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/server-sent-event-examples.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/server-sent-event-examples.json index 0afa467226d..c8b8c1a5989 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/server-sent-event-examples.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/server-sent-event-examples.json @@ -185,6 +185,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -247,6 +249,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_completions.stream", @@ -1907,6 +1911,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_completions": { "name": { @@ -2010,7 +2015,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/server-sent-events.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/server-sent-events.json index 67615bd4ef0..eacd813fd70 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/server-sent-events.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/server-sent-events.json @@ -185,6 +185,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -247,6 +249,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_completions.stream", @@ -1216,6 +1220,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_completions": { "name": { @@ -1319,7 +1324,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/single-url-environment-default.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/single-url-environment-default.json index f32f7e3df8d..a381c0f431d 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/single-url-environment-default.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/single-url-environment-default.json @@ -111,6 +111,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_dummy.getDummy", @@ -310,6 +312,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_dummy": { "name": { @@ -411,7 +414,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/single-url-environment-no-default.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/single-url-environment-no-default.json index fc99964d685..d1e607cde32 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/single-url-environment-no-default.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/single-url-environment-no-default.json @@ -111,6 +111,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_dummy.getDummy", @@ -310,6 +312,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_dummy": { "name": { @@ -411,7 +414,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/streaming-parameter.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/streaming-parameter.json index 2eadd918492..4b9583655da 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/streaming-parameter.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/streaming-parameter.json @@ -185,6 +185,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -345,6 +347,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -407,6 +411,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_dummy.generate", @@ -1669,6 +1675,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_dummy": { "name": { @@ -1773,7 +1780,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/streaming.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/streaming.json index 2dce2301fec..ac813e29ec8 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/streaming.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/streaming.json @@ -185,6 +185,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -247,6 +249,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_dummy.generate-stream", @@ -2398,6 +2402,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_dummy": { "name": { @@ -2501,7 +2506,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/trace.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/trace.json index 3ad432fc700..1bc4710d479 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/trace.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/trace.json @@ -310,6 +310,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -407,6 +409,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -504,6 +508,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -601,6 +607,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1112,6 +1120,8 @@ "type_commons:VariableType", "type_commons:MapType" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1329,6 +1339,8 @@ "type_commons:ListType", "type_commons:MapType" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1601,6 +1613,8 @@ "type_commons:ListType", "type_commons:MapType" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -2530,6 +2544,8 @@ "type_commons:DoublyLinkedListValue", "type_commons:DoublyLinkedListNodeValue" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -3582,6 +3598,8 @@ "type_commons:DoublyLinkedListNodeValue", "type_commons:GenericValue" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -3742,6 +3760,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -3933,6 +3953,8 @@ "type_commons:DoublyLinkedListValue", "type_commons:DoublyLinkedListNodeValue" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -4212,6 +4234,8 @@ "type_commons:DoublyLinkedListValue", "type_commons:DoublyLinkedListNodeValue" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -4563,6 +4587,8 @@ "type_commons:NodeId", "type_commons:BinaryTreeNodeValue" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -4976,6 +5002,8 @@ "referencedTypes": [ "type_commons:NodeId" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -5248,6 +5276,8 @@ "type_commons:BinaryTreeValue", "type_commons:BinaryTreeNodeValue" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -5599,6 +5629,8 @@ "type_commons:NodeId", "type_commons:SinglyLinkedListNodeValue" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -5912,6 +5944,8 @@ "referencedTypes": [ "type_commons:NodeId" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -6184,6 +6218,8 @@ "type_commons:SinglyLinkedListValue", "type_commons:SinglyLinkedListNodeValue" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -6535,6 +6571,8 @@ "type_commons:NodeId", "type_commons:DoublyLinkedListNodeValue" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -6948,6 +6986,8 @@ "referencedTypes": [ "type_commons:NodeId" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -7220,6 +7260,8 @@ "type_commons:DoublyLinkedListValue", "type_commons:DoublyLinkedListNodeValue" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -7415,6 +7457,8 @@ "type_commons:DoublyLinkedListNodeValue", "type_commons:GenericValue" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -7698,6 +7742,8 @@ "type_commons:DoublyLinkedListNodeValue", "type_commons:GenericValue" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -7926,6 +7972,8 @@ "type_commons:DoublyLinkedListValue", "type_commons:DoublyLinkedListNodeValue" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -8206,6 +8254,8 @@ "type_commons:DoublyLinkedListValue", "type_commons:DoublyLinkedListNodeValue" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -8360,6 +8410,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -8516,6 +8568,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -8625,6 +8679,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -8734,6 +8790,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -8890,6 +8948,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -9103,6 +9163,8 @@ "referencedTypes": [ "type_migration:MigrationStatus" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -9200,6 +9262,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -9677,6 +9741,8 @@ "type_playlist:PlaylistId", "type_commons:UserId" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -9896,6 +9962,8 @@ "referencedTypes": [ "type_commons:ProblemId" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -10115,6 +10183,8 @@ "referencedTypes": [ "type_commons:ProblemId" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -10336,6 +10406,8 @@ "referencedTypes": [ "type_playlist:PlaylistId" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -10466,6 +10538,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -11363,6 +11437,8 @@ "type_commons:TestCaseWithExpectedResult", "type_commons:TestCase" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -11555,6 +11631,8 @@ "type_commons:DoublyLinkedListValue", "type_commons:DoublyLinkedListNodeValue" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -11907,6 +11985,8 @@ "type_commons:DoublyLinkedListValue", "type_commons:DoublyLinkedListNodeValue" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -12183,6 +12263,8 @@ "referencedTypes": [ "type_commons:FileInfo" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -12398,6 +12480,8 @@ "type_commons:ListType", "type_commons:MapType" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -13130,6 +13214,8 @@ "type_commons:TestCaseWithExpectedResult", "type_commons:TestCase" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -13471,6 +13557,8 @@ "type_problem:CreateProblemError", "type_problem:GenericCreateProblemError" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -13588,6 +13676,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -13782,6 +13872,8 @@ "referencedTypes": [ "type_problem:GenericCreateProblemError" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -13973,6 +14065,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -14225,6 +14319,8 @@ "type_problem:ProblemFiles", "type_commons:FileInfo" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -14314,6 +14410,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -14411,6 +14509,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -14913,6 +15013,8 @@ "type_submission:WorkspaceSubmitRequest", "type_submission:StopRequest" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -15132,6 +15234,8 @@ "referencedTypes": [ "type_commons:ProblemId" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -15685,6 +15789,8 @@ "type_submission:SubmissionFileInfo", "type_commons:ProblemId" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -16100,6 +16206,8 @@ "type_commons:Language", "type_submission:SubmissionFileInfo" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -16291,6 +16399,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -16395,6 +16505,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -16571,6 +16683,8 @@ "referencedTypes": [ "type_submission:SubmissionId" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -17201,6 +17315,8 @@ "type_submission:FinishedResponse", "type_submission:TerminatedResponse" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -18356,6 +18472,8 @@ "type_submission:UnexpectedLanguageError", "type_submission:FinishedResponse" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -18627,6 +18745,8 @@ "type_submission:SubmissionId", "type_submission:ExecutionSessionStatus" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -18898,6 +19018,8 @@ "type_submission:SubmissionId", "type_submission:RunningSubmissionState" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -19106,6 +19228,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -19381,6 +19505,8 @@ "type_submission:InternalError", "type_submission:ExceptionInfo" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -19760,6 +19886,8 @@ "type_submission:InternalError", "type_submission:ExceptionInfo" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -19877,6 +20005,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -19994,6 +20124,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -20170,6 +20302,8 @@ "referencedTypes": [ "type_submission:ExceptionInfo" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -20346,6 +20480,8 @@ "referencedTypes": [ "type_submission:SubmissionId" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -20619,6 +20755,8 @@ "type_submission:ExceptionV2", "type_submission:ExceptionInfo" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -20939,6 +21077,8 @@ "type_submission:ExceptionV2", "type_submission:ExceptionInfo" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -21241,6 +21381,8 @@ "type_submission:ExceptionInfo", "type_submission:ExceptionV2" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -21640,6 +21782,8 @@ "type_submission:ExceptionV2", "type_submission:ExceptionInfo" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -21938,6 +22082,8 @@ "type_submission:ExceptionV2", "type_submission:ExceptionInfo" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -22051,6 +22197,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -22414,6 +22562,8 @@ "type_submission:ExceptionV2", "type_submission:ExceptionInfo" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -22670,6 +22820,8 @@ "referencedTypes": [ "type_submission:SubmissionId" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -23122,6 +23274,8 @@ "type_submission:LightweightStackframeInformation", "type_submission:TracedFile" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -23276,6 +23430,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -23502,6 +23658,8 @@ "type_submission:ExceptionInfo", "type_submission:ExceptionV2" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -23817,6 +23975,8 @@ "type_submission:ExceptionInfo", "type_submission:ExceptionV2" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -24258,6 +24418,8 @@ "type_submission:ExceptionInfo", "type_submission:ExceptionV2" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -24480,6 +24642,8 @@ "referencedTypes": [ "type_submission:ExceptionInfo" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -24671,6 +24835,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -24953,6 +25119,8 @@ "type_submission:CustomTestCasesUnsupported", "type_submission:UnexpectedLanguageError" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -25334,6 +25502,8 @@ "type_submission:UnexpectedLanguageError", "type_commons:Language" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -25510,6 +25680,8 @@ "referencedTypes": [ "type_submission:SubmissionId" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -25686,6 +25858,8 @@ "referencedTypes": [ "type_submission:SubmissionId" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -25957,6 +26131,8 @@ "type_commons:ProblemId", "type_submission:SubmissionId" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -26227,6 +26403,8 @@ "referencedTypes": [ "type_commons:Language" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -26306,6 +26484,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -26482,6 +26662,8 @@ "referencedTypes": [ "type_submission:SubmissionId" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -26695,6 +26877,8 @@ "referencedTypes": [ "type_submission:SubmissionId" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -26908,6 +27092,8 @@ "referencedTypes": [ "type_submission:SubmissionId" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -27476,6 +27662,8 @@ "type_submission:StackFrame", "type_submission:Scope" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -28139,6 +28327,8 @@ "type_submission:StackFrame", "type_submission:Scope" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -28293,6 +28483,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -28447,6 +28639,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -28681,6 +28875,8 @@ "type_commons:DoublyLinkedListNodeValue", "type_commons:GenericValue" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -28951,6 +29147,8 @@ "type_commons:DoublyLinkedListNodeValue", "type_commons:GenericValue" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -29157,6 +29355,8 @@ "type_commons:DoublyLinkedListNodeValue", "type_commons:GenericValue" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -29508,6 +29708,8 @@ "type_commons:Language", "type_submission:ExecutionSessionStatus" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -29742,6 +29944,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -30092,6 +30296,8 @@ "type_submission:WorkspaceRunDetails", "type_submission:WorkspaceTracedUpdate" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -30597,6 +30803,8 @@ "type_v2/problem:TestCaseImplementationReference", "type_v2/problem:TestCaseExpects" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -30789,6 +30997,8 @@ "type_submission:RuntimeError", "type_submission:InternalError" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -31021,6 +31231,8 @@ "type_submission:ExceptionV2", "type_submission:RecordedTestCaseUpdate" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -31620,6 +31832,8 @@ "type_submission:ExceptionV2", "type_submission:RecordedTestCaseUpdate" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -31838,6 +32052,8 @@ "type_submission:RuntimeError", "type_submission:InternalError" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -32451,6 +32667,8 @@ "type_submission:RuntimeError", "type_submission:InternalError" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -32775,6 +32993,8 @@ "type_submission:ExceptionV2", "type_submission:ExceptionInfo" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -33027,6 +33247,8 @@ "referencedTypes": [ "type_v2/problem:TestCaseId" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -33144,6 +33366,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -33460,6 +33684,8 @@ "type_submission:WorkspaceSubmissionStatus", "type_submission:WorkspaceRunDetails" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -33644,6 +33870,8 @@ "type_submission:WorkspaceRunDetails", "type_submission:ExceptionV2" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -34200,6 +34428,8 @@ "type_submission:WorkspaceRunDetails", "type_submission:ExceptionV2" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -34697,6 +34927,8 @@ "type_submission:TestCaseNonHiddenGrade", "type_submission:TracedTestCase" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -35223,6 +35455,8 @@ "type_submission:TestCaseNonHiddenGrade", "type_submission:TracedTestCase" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -35644,6 +35878,8 @@ "type_submission:TestCaseNonHiddenGrade", "type_submission:TracedTestCase" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -35871,6 +36107,8 @@ "type_submission:ExceptionInfo", "type_submission:ExceptionV2" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -36115,6 +36353,8 @@ "type_submission:StackFrame", "type_submission:Scope" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -36360,6 +36600,8 @@ "type_submission:StackFrame", "type_submission:Scope" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -36483,6 +36725,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -36735,6 +36979,8 @@ "type_submission:WorkspaceFiles", "type_commons:FileInfo" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -37026,6 +37272,8 @@ "type_v2/problem:Files", "type_v2/problem:FileInfoV2" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -37302,6 +37550,8 @@ "referencedTypes": [ "type_commons:FileInfo" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -37729,6 +37979,8 @@ "type_commons:Language", "type_submission:ExecutionSessionStatus" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -38010,6 +38262,8 @@ "type_commons:Language", "type_submission:ExecutionSessionStatus" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -38389,6 +38643,8 @@ "type_submission:WorkspaceSubmissionStatus", "type_submission:WorkspaceRunDetails" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -38525,6 +38781,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -38661,6 +38919,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -38797,6 +39057,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -39901,6 +40163,8 @@ "type_v2/problem:TestCaseImplementationReference", "type_v2/problem:TestCaseExpects" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -40293,6 +40557,8 @@ "type_commons:ListType", "type_commons:MapType" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -41131,6 +41397,8 @@ "type_v2/problem:TestCaseImplementationReference", "type_v2/problem:TestCaseExpects" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -41897,6 +42165,8 @@ "type_commons:DoublyLinkedListNodeValue", "type_v2/problem:TestCaseExpects" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -42059,6 +42329,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -42507,6 +42779,8 @@ "type_v2/problem:VoidFunctionDefinitionThatTakesActualResult", "type_v2/problem:VoidFunctionDefinition" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -43067,6 +43341,8 @@ "type_v2/problem:TestCaseImplementationDescriptionBoard", "type_v2/problem:ParameterId" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -43510,6 +43786,8 @@ "type_v2/problem:VoidFunctionDefinitionThatTakesActualResult", "type_v2/problem:VoidFunctionDefinition" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -43914,6 +44192,8 @@ "type_v2/problem:VoidFunctionDefinitionThatTakesActualResult", "type_v2/problem:VoidFunctionDefinition" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -44330,6 +44610,8 @@ "type_v2/problem:VoidFunctionDefinitionThatTakesActualResult", "type_v2/problem:VoidFunctionDefinition" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -44729,6 +45011,8 @@ "type_v2/problem:DeepEqualityCorrectnessCheck", "type_v2/problem:VoidFunctionDefinitionThatTakesActualResult" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -45129,6 +45413,8 @@ "type_commons:Language", "type_v2/problem:FunctionImplementation" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -45517,6 +45803,8 @@ "type_commons:ListType", "type_commons:MapType" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -45912,6 +46200,8 @@ "type_commons:Language", "type_v2/problem:FunctionImplementation" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -46176,6 +46466,8 @@ "type_commons:ListType", "type_commons:MapType" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -46534,6 +46826,8 @@ "type_commons:ListType", "type_commons:MapType" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -46892,6 +47186,8 @@ "type_commons:ListType", "type_commons:MapType" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -47303,6 +47599,8 @@ "type_commons:Language", "type_v2/problem:FunctionImplementation" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -47557,6 +47855,8 @@ "referencedTypes": [ "type_v2/problem:ParameterId" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -47957,6 +48257,8 @@ "type_commons:Language", "type_v2/problem:FunctionImplementation" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -48218,6 +48520,8 @@ "type_v2/problem:TestCaseImplementationDescriptionBoard", "type_v2/problem:ParameterId" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -48578,6 +48882,8 @@ "referencedTypes": [ "type_v2/problem:ParameterId" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -48902,6 +49208,8 @@ "referencedTypes": [ "type_v2/problem:TestCaseId" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -49231,6 +49539,8 @@ "type_commons:Language", "type_v2/problem:FunctionImplementation" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -49430,6 +49740,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -50174,6 +50486,8 @@ "type_v2/problem:Files", "type_v2/problem:FileInfoV2" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -50690,6 +51004,8 @@ "type_v2/problem:TestCaseImplementationDescription", "type_v2/problem:TestCaseImplementationDescriptionBoard" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -51333,6 +51649,8 @@ "type_v2/problem:TestCaseImplementationDescription", "type_v2/problem:TestCaseImplementationDescriptionBoard" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -51593,6 +51911,8 @@ "referencedTypes": [ "type_v2/problem:FileInfoV2" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -51856,6 +52176,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -52213,6 +52535,8 @@ "type_commons:ListType", "type_commons:MapType" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -52752,6 +53076,8 @@ "type_v2/problem:NonVoidFunctionSignature", "type_v2/problem:VoidFunctionSignatureThatTakesActualResult" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -53014,6 +53340,8 @@ "type_v2/problem:NonVoidFunctionSignature", "type_v2/problem:VoidFunctionSignatureThatTakesActualResult" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -53246,6 +53574,8 @@ "referencedTypes": [ "type_commons:Language" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -53542,6 +53872,8 @@ "type_commons:ListType", "type_commons:MapType" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -53871,6 +54203,8 @@ "type_commons:Language", "type_v2/problem:FileInfoV2" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -54299,6 +54633,8 @@ "type_commons:DoublyLinkedListNodeValue", "type_v2/problem:TestCaseExpects" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -54573,6 +54909,8 @@ "type_v2/problem:VoidFunctionDefinitionThatTakesActualResult", "type_v2/problem:VoidFunctionDefinition" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -54747,6 +55085,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -54921,6 +55261,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -55095,6 +55437,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -56389,6 +56733,8 @@ "type_v2/v3/problem:TestCaseImplementationReference", "type_v2/v3/problem:TestCaseExpects" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -56819,6 +57165,8 @@ "type_commons:ListType", "type_commons:MapType" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -57809,6 +58157,8 @@ "type_v2/v3/problem:TestCaseImplementationReference", "type_v2/v3/problem:TestCaseExpects" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -58765,6 +59115,8 @@ "type_commons:DoublyLinkedListNodeValue", "type_v2/v3/problem:TestCaseExpects" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -58965,6 +59317,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -59527,6 +59881,8 @@ "type_v2/v3/problem:VoidFunctionDefinitionThatTakesActualResult", "type_v2/v3/problem:VoidFunctionDefinition" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -60239,6 +60595,8 @@ "type_v2/v3/problem:TestCaseImplementationDescriptionBoard", "type_v2/v3/problem:ParameterId" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -60796,6 +61154,8 @@ "type_v2/v3/problem:VoidFunctionDefinitionThatTakesActualResult", "type_v2/v3/problem:VoidFunctionDefinition" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -61314,6 +61674,8 @@ "type_v2/v3/problem:VoidFunctionDefinitionThatTakesActualResult", "type_v2/v3/problem:VoidFunctionDefinition" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -61844,6 +62206,8 @@ "type_v2/v3/problem:VoidFunctionDefinitionThatTakesActualResult", "type_v2/v3/problem:VoidFunctionDefinition" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -62357,6 +62721,8 @@ "type_v2/v3/problem:DeepEqualityCorrectnessCheck", "type_v2/v3/problem:VoidFunctionDefinitionThatTakesActualResult" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -62871,6 +63237,8 @@ "type_commons:Language", "type_v2/v3/problem:FunctionImplementation" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -63335,6 +63703,8 @@ "type_commons:ListType", "type_commons:MapType" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -63844,6 +64214,8 @@ "type_commons:Language", "type_v2/v3/problem:FunctionImplementation" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -64184,6 +64556,8 @@ "type_commons:ListType", "type_commons:MapType" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -64618,6 +64992,8 @@ "type_commons:ListType", "type_commons:MapType" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -65052,6 +65428,8 @@ "type_commons:ListType", "type_commons:MapType" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -65577,6 +65955,8 @@ "type_commons:Language", "type_v2/v3/problem:FunctionImplementation" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -65907,6 +66287,8 @@ "referencedTypes": [ "type_v2/v3/problem:ParameterId" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -66421,6 +66803,8 @@ "type_commons:Language", "type_v2/v3/problem:FunctionImplementation" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -66758,6 +67142,8 @@ "type_v2/v3/problem:TestCaseImplementationDescriptionBoard", "type_v2/v3/problem:ParameterId" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -67194,6 +67580,8 @@ "referencedTypes": [ "type_v2/v3/problem:ParameterId" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -67594,6 +67982,8 @@ "referencedTypes": [ "type_v2/v3/problem:TestCaseId" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -67999,6 +68389,8 @@ "type_commons:Language", "type_v2/v3/problem:FunctionImplementation" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -68236,6 +68628,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -69132,6 +69526,8 @@ "type_v2/v3/problem:Files", "type_v2/v3/problem:FileInfoV2" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -69762,6 +70158,8 @@ "type_v2/v3/problem:TestCaseImplementationDescription", "type_v2/v3/problem:TestCaseImplementationDescriptionBoard" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -70557,6 +70955,8 @@ "type_v2/v3/problem:TestCaseImplementationDescription", "type_v2/v3/problem:TestCaseImplementationDescriptionBoard" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -70893,6 +71293,8 @@ "referencedTypes": [ "type_v2/v3/problem:FileInfoV2" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -71194,6 +71596,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -71627,6 +72031,8 @@ "type_commons:ListType", "type_commons:MapType" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -72318,6 +72724,8 @@ "type_v2/v3/problem:NonVoidFunctionSignature", "type_v2/v3/problem:VoidFunctionSignatureThatTakesActualResult" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -72656,6 +73064,8 @@ "type_v2/v3/problem:NonVoidFunctionSignature", "type_v2/v3/problem:VoidFunctionSignatureThatTakesActualResult" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -72926,6 +73336,8 @@ "referencedTypes": [ "type_commons:Language" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -73298,6 +73710,8 @@ "type_commons:ListType", "type_commons:MapType" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -73703,6 +74117,8 @@ "type_commons:Language", "type_v2/v3/problem:FileInfoV2" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -74245,6 +74661,8 @@ "type_commons:DoublyLinkedListNodeValue", "type_v2/v3/problem:TestCaseExpects" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -74595,6 +75013,8 @@ "type_v2/v3/problem:VoidFunctionDefinitionThatTakesActualResult", "type_v2/v3/problem:VoidFunctionDefinition" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -74913,6 +75333,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_v2.test", @@ -75044,6 +75466,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_admin.updateTestSubmissionStatus", @@ -88178,6 +88602,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_homepage.getHomepageProblems", @@ -88977,6 +89403,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_migration.getAttemptedMigrations", @@ -89815,6 +90243,8 @@ "docs": null } ], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_playlist.createPlaylist", @@ -98464,6 +98894,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_problem.createProblem", @@ -110638,6 +111070,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_submission.createExecutionSession", @@ -114622,6 +115056,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_sysprop.setNumWarmInstances", @@ -115579,6 +116015,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_v2/problem.getLightweightProblems", @@ -127052,6 +127490,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_v2/v3/problem.getLightweightProblems", @@ -141088,6 +141528,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_v2": { "name": { @@ -142459,7 +142900,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/undiscriminated-unions.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/undiscriminated-unions.json index fa6f6a4f0d5..14454c9825c 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/undiscriminated-unions.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/undiscriminated-unions.json @@ -216,6 +216,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -464,6 +466,8 @@ "type_union:Key", "type_union:KeyType" ], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": null, @@ -1208,6 +1212,8 @@ "referencedTypes": [ "type_union:KeyType" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1338,6 +1344,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1400,6 +1408,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_union.get", @@ -3402,6 +3412,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_union": { "name": { @@ -3508,7 +3519,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/unions.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/unions.json index 144953ed197..df8591ac524 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/unions.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/unions.json @@ -365,6 +365,8 @@ "type_types:Foo", "type_types:Bar" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -705,6 +707,8 @@ "type_types:Foo", "type_types:Bar" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -928,6 +932,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1214,6 +1220,8 @@ "type_types:Foo", "type_types:Bar" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1436,6 +1444,8 @@ "referencedTypes": [ "type_types:Foo" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1634,6 +1644,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -1988,6 +2000,8 @@ "referencedTypes": [ "type_types:Foo" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -2264,6 +2278,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -2491,6 +2507,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -2685,6 +2703,8 @@ "referencedTypes": [ "type_types:Foo" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -2802,6 +2822,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -2919,6 +2941,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -3036,6 +3060,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -3360,6 +3386,8 @@ "type_union:Circle", "type_union:Square" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -3477,6 +3505,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -3594,6 +3624,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -3656,6 +3688,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_union.get", @@ -4910,6 +4944,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_types": { "name": { @@ -5103,7 +5138,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/unknown.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/unknown.json index 90cc985cc8b..cba9783dec2 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/unknown.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/unknown.json @@ -106,6 +106,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -215,6 +217,8 @@ "extendedProperties": [] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -305,6 +309,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_unknown.post", @@ -881,6 +887,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_unknown": { "name": { @@ -985,7 +992,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/validation.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/validation.json index ce45604311f..fbbd0d43e74 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/validation.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/validation.json @@ -90,6 +90,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -155,6 +157,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -220,6 +224,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -284,6 +290,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -348,6 +356,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -491,6 +501,8 @@ ] }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -719,6 +731,8 @@ "referencedTypes": [ "type_:Shape" ], + "encoding": null, + "source": null, "userProvidedExamples": [ { "name": { @@ -1071,6 +1085,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_.create", @@ -2784,6 +2800,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": {}, "rootPackage": { "fernFilepath": { @@ -2817,7 +2834,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/variables.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/variables.json index 1806782cc5a..a8f57b2b085 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/variables.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/variables.json @@ -86,6 +86,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_service.post", @@ -353,6 +355,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_service": { "name": { @@ -454,7 +457,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/version-no-default.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/version-no-default.json index 8a3a4c04072..0412dd0e627 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/version-no-default.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/version-no-default.json @@ -245,6 +245,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -458,6 +460,8 @@ "referencedTypes": [ "type_user:UserId" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -520,6 +524,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_user.getUser", @@ -1373,6 +1379,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_user": { "name": { @@ -1477,7 +1484,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/version.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/version.json index 88799fcb398..810b67fbf56 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/version.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/version.json @@ -270,6 +270,8 @@ } }, "referencedTypes": [], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -483,6 +485,8 @@ "referencedTypes": [ "type_user:UserId" ], + "encoding": null, + "source": null, "userProvidedExamples": [], "autogeneratedExamples": [], "availability": null, @@ -545,6 +549,8 @@ }, "headers": [], "pathParameters": [], + "encoding": null, + "transport": null, "endpoints": [ { "id": "endpoint_user.getUser", @@ -1398,6 +1404,7 @@ "webhookGroups": {}, "websocketChannels": {}, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_user": { "name": { @@ -1502,7 +1509,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/__test__/test-definitions/websocket.json b/packages/cli/generation/ir-generator/src/__test__/test-definitions/websocket.json index 57da11e05d7..5ef8ddd9014 100644 --- a/packages/cli/generation/ir-generator/src/__test__/test-definitions/websocket.json +++ b/packages/cli/generation/ir-generator/src/__test__/test-definitions/websocket.json @@ -489,6 +489,7 @@ } }, "readmeConfig": null, + "sourceConfig": null, "subpackages": { "subpackage_realtime": { "name": { @@ -590,7 +591,8 @@ "platformHeaders": { "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" + "sdkVersion": "X-Fern-SDK-Version", + "userAgent": null } } } \ No newline at end of file diff --git a/packages/cli/generation/ir-generator/src/converters/services/convertHttpService.ts b/packages/cli/generation/ir-generator/src/converters/services/convertHttpService.ts index 562e8ae1aee..ba48ff2422d 100644 --- a/packages/cli/generation/ir-generator/src/converters/services/convertHttpService.ts +++ b/packages/cli/generation/ir-generator/src/converters/services/convertHttpService.ts @@ -77,6 +77,8 @@ export async function convertHttpService({ ) : [], pathParameters: servicePathParameters, + encoding: undefined, + transport: undefined, endpoints: await Promise.all( Object.entries(serviceDefinition.endpoints).map(async ([endpointKey, endpoint]): Promise => { const endpointPathParameters = await convertPathParameters({ diff --git a/packages/cli/generation/ir-generator/src/converters/type-declarations/convertTypeDeclaration.ts b/packages/cli/generation/ir-generator/src/converters/type-declarations/convertTypeDeclaration.ts index 5c3e8a7eee3..647d123bc40 100644 --- a/packages/cli/generation/ir-generator/src/converters/type-declarations/convertTypeDeclaration.ts +++ b/packages/cli/generation/ir-generator/src/converters/type-declarations/convertTypeDeclaration.ts @@ -56,6 +56,8 @@ export async function convertTypeDeclaration({ name: declaredTypeName, shape: await convertType({ typeDeclaration, file, typeResolver }), referencedTypes: new Set(referencedTypes.map((referencedType) => referencedType.typeId)), + encoding: undefined, + source: undefined, userProvidedExamples: typeof typeDeclaration !== "string" && typeDeclaration.examples != null ? typeDeclaration.examples.map( diff --git a/packages/cli/generation/ir-generator/src/converters/type-declarations/getReferencedTypesFromRawDeclaration.ts b/packages/cli/generation/ir-generator/src/converters/type-declarations/getReferencedTypesFromRawDeclaration.ts index 911f1f622ca..bea9f3a3913 100644 --- a/packages/cli/generation/ir-generator/src/converters/type-declarations/getReferencedTypesFromRawDeclaration.ts +++ b/packages/cli/generation/ir-generator/src/converters/type-declarations/getReferencedTypesFromRawDeclaration.ts @@ -43,13 +43,24 @@ export function getReferencedTypesFromRawDeclaration({ return types; }, discriminatedUnion: (unionDeclaration) => { - return Object.values(unionDeclaration.union).reduce((types, singleUnionType) => { - const rawType = typeof singleUnionType === "string" ? singleUnionType : singleUnionType.type; - if (typeof rawType === "string") { - types.push(rawType); - } - return types; - }, []); + const types: string[] = []; + if (unionDeclaration["base-properties"] != null) { + types.push( + ...Object.values(unionDeclaration["base-properties"]).map((property) => + typeof property === "string" ? property : property.type + ) + ); + } + types.push( + ...Object.values(unionDeclaration.union).reduce((types, singleUnionType) => { + const rawType = typeof singleUnionType === "string" ? singleUnionType : singleUnionType.type; + if (typeof rawType === "string") { + types.push(rawType); + } + return types; + }, []) + ); + return types; }, undiscriminatedUnion: (unionDeclaration) => { return Object.values(unionDeclaration.union).reduce((types, unionMember) => { diff --git a/packages/cli/generation/ir-generator/src/filtered-ir/IrGraph.ts b/packages/cli/generation/ir-generator/src/filtered-ir/IrGraph.ts index 1041ae99758..6c9a0271ff3 100644 --- a/packages/cli/generation/ir-generator/src/filtered-ir/IrGraph.ts +++ b/packages/cli/generation/ir-generator/src/filtered-ir/IrGraph.ts @@ -107,10 +107,12 @@ export class IrGraph { public markEnvironmentForAudiences( environment: SingleBaseUrlEnvironment | MultipleBaseUrlsEnvironment, - audiences: string[] + audiences: string[], + // TODO: (rohin) this is brought in for backwards compatibility when no audiences are supplied to environments. Restore this when api/generators bumping. + ignoreAudiences?: boolean ): void { if (environment) { - if (this.hasAudience(audiences)) { + if (this.hasAudience(audiences) || ignoreAudiences) { this.environmentsNeededForAudience.add(environment.id); } } diff --git a/packages/cli/generation/ir-generator/src/generateIntermediateRepresentation.ts b/packages/cli/generation/ir-generator/src/generateIntermediateRepresentation.ts index 989dc79b6fc..fb27ca86b3a 100644 --- a/packages/cli/generation/ir-generator/src/generateIntermediateRepresentation.ts +++ b/packages/cli/generation/ir-generator/src/generateIntermediateRepresentation.ts @@ -56,7 +56,9 @@ export async function generateIntermediateRepresentation({ smartCasing, disableExamples, audiences, - readme + readme, + packageName, + version }: { fdrApiDefinitionId?: string; workspace: FernWorkspace; @@ -66,6 +68,8 @@ export async function generateIntermediateRepresentation({ disableExamples: boolean; audiences: Audiences; readme: generatorsYml.ReadmeSchema | undefined; + packageName: string | undefined; + version: string | undefined; }): Promise { const casingsGenerator = constructCasingsGenerator({ generationLanguage, keywords, smartCasing }); @@ -160,7 +164,8 @@ export async function generateIntermediateRepresentation({ }, webhookGroups: {}, websocketChannels: {}, - readmeConfig: undefined + readmeConfig: undefined, + sourceConfig: undefined }; const packageTreeGenerator = new PackageTreeGenerator(); @@ -398,14 +403,46 @@ export async function generateIntermediateRepresentation({ intermediateRepresentation ).enrichWithAutogeneratedExamples(); + const workspaceDefinitionRootApiFileContents = workspace.definition.rootApiFile.contents; const environments = convertEnvironments({ casingsGenerator, - rawApiFileSchema: workspace.definition.rootApiFile.contents + rawApiFileSchema: workspaceDefinitionRootApiFileContents }); + + // TODO: (rohin) Back compat hack before pushing generator/api upgrade + const ignoreAudiences = environments?.environments._visit({ + singleBaseUrl: (value) => { + return ( + value.environments.filter((environment) => { + return ( + getAudienceForEnvironment( + environment.id, + workspaceDefinitionRootApiFileContents.environments + ) != null + ); + }).length === 0 + ); + }, + multipleBaseUrls: (value) => { + return ( + value.environments.filter((environment) => { + return ( + getAudienceForEnvironment( + environment.id, + workspaceDefinitionRootApiFileContents.environments + ) != null + ); + }).length === 0 + ); + }, + _other: () => false + }); + environments?.environments.environments.forEach((environment) => { irGraph.markEnvironmentForAudiences( environment, - getAudienceForEnvironment(environment.id, workspace.definition.rootApiFile.contents.environments) ?? [] + getAudienceForEnvironment(environment.id, workspaceDefinitionRootApiFileContents.environments) ?? [], + ignoreAudiences ); }); @@ -451,7 +488,14 @@ export async function generateIntermediateRepresentation({ platformHeaders: { language: "X-Fern-Language", sdkName: "X-Fern-SDK-Name", - sdkVersion: "X-Fern-SDK-Version" + sdkVersion: "X-Fern-SDK-Version", + userAgent: + version != null && packageName != null + ? { + header: "User-Agent", + value: `${packageName}/${version}` + } + : undefined } }, readmeConfig diff --git a/packages/cli/generation/ir-migrations/src/__test__/utils/getIrForApi.ts b/packages/cli/generation/ir-migrations/src/__test__/utils/getIrForApi.ts index d09af60b15a..a4bec5d91a1 100644 --- a/packages/cli/generation/ir-migrations/src/__test__/utils/getIrForApi.ts +++ b/packages/cli/generation/ir-migrations/src/__test__/utils/getIrForApi.ts @@ -23,6 +23,8 @@ export async function getIrForApi(absolutePathToWorkspace: AbsoluteFilePath): Pr keywords: undefined, smartCasing: true, // Verify the special casing convention in tests. disableExamples: false, - readme: undefined + readme: undefined, + version: undefined, + packageName: undefined }); } diff --git a/packages/cli/generation/ir-migrations/src/migrations/v23-to-v22/__test__/__snapshots__/migrateFromV23ToV22.test.ts.snap b/packages/cli/generation/ir-migrations/src/migrations/v23-to-v22/__test__/__snapshots__/migrateFromV23ToV22.test.ts.snap index e7e6e081b4d..f0b23d696af 100644 --- a/packages/cli/generation/ir-migrations/src/migrations/v23-to-v22/__test__/__snapshots__/migrateFromV23ToV22.test.ts.snap +++ b/packages/cli/generation/ir-migrations/src/migrations/v23-to-v22/__test__/__snapshots__/migrateFromV23ToV22.test.ts.snap @@ -1717,6 +1717,7 @@ exports[`migrateFromV23ToV22 migrates extensive 1`] = ` "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", "sdkVersion": "X-Fern-SDK-Version", + "userAgent": undefined, }, }, "serviceTypeReferenceInfo": { diff --git a/packages/cli/generation/ir-migrations/src/migrations/v5-to-v4/__test__/__snapshots__/migrateFromV5ToV4.test.ts.snap b/packages/cli/generation/ir-migrations/src/migrations/v5-to-v4/__test__/__snapshots__/migrateFromV5ToV4.test.ts.snap index e88614489a2..8751b7e759b 100644 --- a/packages/cli/generation/ir-migrations/src/migrations/v5-to-v4/__test__/__snapshots__/migrateFromV5ToV4.test.ts.snap +++ b/packages/cli/generation/ir-migrations/src/migrations/v5-to-v4/__test__/__snapshots__/migrateFromV5ToV4.test.ts.snap @@ -119,6 +119,7 @@ exports[`migrateFromV5ToV4 correctly migrates 1`] = ` "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", "sdkVersion": "X-Fern-SDK-Version", + "userAgent": undefined, }, }, "services": { diff --git a/packages/cli/generation/ir-migrations/src/migrations/v9-to-v8/__test__/__snapshots__/migrateFromV9ToV8.test.ts.snap b/packages/cli/generation/ir-migrations/src/migrations/v9-to-v8/__test__/__snapshots__/migrateFromV9ToV8.test.ts.snap index ece42f0e40d..aef6be7a24d 100644 --- a/packages/cli/generation/ir-migrations/src/migrations/v9-to-v8/__test__/__snapshots__/migrateFromV9ToV8.test.ts.snap +++ b/packages/cli/generation/ir-migrations/src/migrations/v9-to-v8/__test__/__snapshots__/migrateFromV9ToV8.test.ts.snap @@ -88,6 +88,7 @@ exports[`migrateFromV9ToV8 migrates maps to list 1`] = ` "language": "X-Fern-Language", "sdkName": "X-Fern-SDK-Name", "sdkVersion": "X-Fern-SDK-Version", + "userAgent": undefined, }, }, "serviceTypeReferenceInfo": { diff --git a/packages/cli/generation/local-generation/local-workspace-runner/src/getIntermediateRepresentation.ts b/packages/cli/generation/local-generation/local-workspace-runner/src/getIntermediateRepresentation.ts index 6aaac2d8632..e634e2e357f 100644 --- a/packages/cli/generation/local-generation/local-workspace-runner/src/getIntermediateRepresentation.ts +++ b/packages/cli/generation/local-generation/local-workspace-runner/src/getIntermediateRepresentation.ts @@ -12,13 +12,17 @@ export async function getIntermediateRepresentation({ audiences, generatorInvocation, context, - irVersionOverride + irVersionOverride, + version, + packageName }: { workspace: FernWorkspace; audiences: Audiences; generatorInvocation: generatorsYml.GeneratorInvocation; context: TaskContext; irVersionOverride: string | undefined; + version: string | undefined; + packageName: string | undefined; }): Promise { const intermediateRepresentation = await generateIntermediateRepresentation({ workspace, @@ -27,7 +31,9 @@ export async function getIntermediateRepresentation({ keywords: generatorInvocation.keywords, smartCasing: generatorInvocation.smartCasing, disableExamples: generatorInvocation.disableExamples, - readme: generatorInvocation.readme + readme: generatorInvocation.readme, + version, + packageName }); context.logger.debug("Generated IR"); const migratedIntermediateRepresentation = diff --git a/packages/cli/generation/local-generation/local-workspace-runner/src/runGenerator.ts b/packages/cli/generation/local-generation/local-workspace-runner/src/runGenerator.ts index f9b1b7c254d..dbf035ef734 100644 --- a/packages/cli/generation/local-generation/local-workspace-runner/src/runGenerator.ts +++ b/packages/cli/generation/local-generation/local-workspace-runner/src/runGenerator.ts @@ -59,7 +59,8 @@ export async function writeFilesToDiskAndRunGenerator({ generatorInvocation, workspaceTempDir, context, - irVersionOverride + irVersionOverride, + outputVersionOverride }); context.logger.debug("Wrote IR to: " + absolutePathToIr); @@ -135,7 +136,8 @@ async function writeIrToFile({ generatorInvocation, workspaceTempDir, context, - irVersionOverride + irVersionOverride, + outputVersionOverride }: { workspace: FernWorkspace; audiences: Audiences; @@ -143,13 +145,16 @@ async function writeIrToFile({ workspaceTempDir: DirectoryResult; context: TaskContext; irVersionOverride: string | undefined; + outputVersionOverride: string | undefined; }): Promise { const intermediateRepresentation = await getIntermediateRepresentation({ workspace, audiences, generatorInvocation, context, - irVersionOverride + irVersionOverride, + packageName: generatorsYml.getPackageName({ generatorInvocation }), + version: outputVersionOverride }); context.logger.debug("Migrated IR"); const irFile = await tmp.file({ diff --git a/packages/cli/generation/remote-generation/remote-workspace-runner/package.json b/packages/cli/generation/remote-generation/remote-workspace-runner/package.json index aa230f6dca4..7f69b923174 100644 --- a/packages/cli/generation/remote-generation/remote-workspace-runner/package.json +++ b/packages/cli/generation/remote-generation/remote-workspace-runner/package.json @@ -32,7 +32,7 @@ "@fern-api/core": "workspace:*", "@fern-api/core-utils": "workspace:*", "@fern-api/docs-resolver": "workspace:*", - "@fern-api/fdr-sdk": "0.98.16-3955e989a", + "@fern-api/fdr-sdk": "0.98.18-aaf13f7f5", "@fern-api/fs-utils": "workspace:*", "@fern-api/ir-generator": "workspace:*", "@fern-api/ir-migrations": "workspace:*", diff --git a/packages/cli/generation/remote-generation/remote-workspace-runner/src/runRemoteGenerationForGenerator.ts b/packages/cli/generation/remote-generation/remote-workspace-runner/src/runRemoteGenerationForGenerator.ts index 37b89d3a19b..c4b82a1743f 100644 --- a/packages/cli/generation/remote-generation/remote-workspace-runner/src/runRemoteGenerationForGenerator.ts +++ b/packages/cli/generation/remote-generation/remote-workspace-runner/src/runRemoteGenerationForGenerator.ts @@ -1,6 +1,7 @@ import { FernToken } from "@fern-api/auth"; import { Audiences, fernConfigJson, generatorsYml } from "@fern-api/configuration"; import { createFdrService } from "@fern-api/core"; +import { FdrAPI, FdrClient } from "@fern-api/fdr-sdk"; import { AbsoluteFilePath } from "@fern-api/fs-utils"; import { generateIntermediateRepresentation } from "@fern-api/ir-generator"; import { convertIrToFdrApi } from "@fern-api/register"; @@ -40,6 +41,10 @@ export async function runRemoteGenerationForGenerator({ absolutePathToPreview: AbsoluteFilePath | undefined; readme: generatorsYml.ReadmeSchema | undefined; }): Promise { + const fdr = createFdrService({ token: token.value }); + + const packageName = generatorsYml.getPackageName({ generatorInvocation }); + const ir = await generateIntermediateRepresentation({ workspace, generationLanguage: generatorInvocation.language, @@ -47,10 +52,11 @@ export async function runRemoteGenerationForGenerator({ smartCasing: generatorInvocation.smartCasing, disableExamples: generatorInvocation.disableExamples, audiences, - readme + readme, + packageName, + version: version ?? (await computeSemanticVersion({ fdr, packageName, generatorInvocation })) }); - const fdr = createFdrService({ token: token.value }); const apiDefinition = convertIrToFdrApi({ ir, snippetsConfig: {} }); const response = await fdr.api.v1.register.registerApiDefinition({ orgId: organization, @@ -103,3 +109,55 @@ export async function runRemoteGenerationForGenerator({ context: interactiveTaskContext }); } + +async function computeSemanticVersion({ + fdr, + packageName, + generatorInvocation +}: { + fdr: FdrClient; + packageName: string | undefined; + generatorInvocation: generatorsYml.GeneratorInvocation; +}): Promise { + if (generatorInvocation.language == null) { + return undefined; + } + let language: FdrAPI.sdks.Language; + switch (generatorInvocation.language) { + case "csharp": + language = "Csharp"; + break; + case "go": + language = "Go"; + break; + case "java": + language = "Java"; + break; + case "python": + language = "Python"; + break; + case "ruby": + language = "Ruby"; + break; + case "typescript": + language = "TypeScript"; + break; + default: + return undefined; + } + if (packageName == null) { + return undefined; + } + const response = await fdr.sdks.versions.computeSemanticVersion({ + githubRepository: + generatorInvocation.outputMode.type === "githubV2" + ? `${generatorInvocation.outputMode.githubV2.owner}/${generatorInvocation.outputMode.githubV2.repo}` + : undefined, + language, + package: packageName + }); + if (!response.ok) { + return undefined; + } + return response.body.version; +} diff --git a/packages/cli/openapi-ir-to-fern/src/__test__/__snapshots__/seam.test.ts.snap b/packages/cli/openapi-ir-to-fern/src/__test__/__snapshots__/seam.test.ts.snap index 990c970f3e5..d4cbe14363f 100644 --- a/packages/cli/openapi-ir-to-fern/src/__test__/__snapshots__/seam.test.ts.snap +++ b/packages/cli/openapi-ir-to-fern/src/__test__/__snapshots__/seam.test.ts.snap @@ -3,7 +3,7 @@ exports[`seam seam docs 1`] = ` { "definitionFiles": { - "accessCodes.yml": { + "access_codes.yml": { "imports": { "root": "__package__.yml", }, @@ -831,7 +831,7 @@ exports[`seam seam docs 1`] = ` }, }, }, - "accessCodes/simulate.yml": { + "access_codes/simulate.yml": { "imports": { "root": "../__package__.yml", }, @@ -928,7 +928,7 @@ exports[`seam seam docs 1`] = ` }, }, }, - "accessCodes/unmanaged.yml": { + "access_codes/unmanaged.yml": { "imports": { "root": "../__package__.yml", }, @@ -1276,7 +1276,7 @@ exports[`seam seam docs 1`] = ` }, }, }, - "acs/accessGroups.yml": { + "acs/access_groups.yml": { "imports": { "root": "../__package__.yml", }, @@ -1633,7 +1633,7 @@ exports[`seam seam docs 1`] = ` }, }, }, - "acs/credentialPools.yml": { + "acs/credential_pools.yml": { "imports": { "root": "../__package__.yml", }, @@ -1711,7 +1711,7 @@ exports[`seam seam docs 1`] = ` }, }, }, - "acs/credentialProvisioningAutomations.yml": { + "acs/credential_provisioning_automations.yml": { "imports": { "root": "../__package__.yml", }, @@ -4017,7 +4017,7 @@ exports[`seam seam docs 1`] = ` }, }, }, - "actionAttempts.yml": { + "action_attempts.yml": { "imports": { "root": "__package__.yml", }, @@ -4148,7 +4148,7 @@ exports[`seam seam docs 1`] = ` }, }, }, - "clientSessions.yml": { + "client_sessions.yml": { "imports": { "root": "__package__.yml", }, @@ -4604,7 +4604,7 @@ exports[`seam seam docs 1`] = ` }, }, }, - "connectWebviews.yml": { + "connect_webviews.yml": { "imports": { "root": "__package__.yml", }, @@ -5001,7 +5001,7 @@ exports[`seam seam docs 1`] = ` }, }, }, - "connectedAccounts.yml": { + "connected_accounts.yml": { "imports": { "root": "__package__.yml", }, @@ -8525,7 +8525,7 @@ exports[`seam seam docs 1`] = ` }, }, }, - "noiseSensors/noiseThresholds.yml": { + "noise_sensors/noise_thresholds.yml": { "imports": { "root": "../__package__.yml", }, @@ -8902,7 +8902,7 @@ exports[`seam seam docs 1`] = ` }, }, }, - "noiseSensors/simulate.yml": { + "noise_sensors/simulate.yml": { "imports": { "root": "../__package__.yml", }, @@ -10296,7 +10296,7 @@ exports[`seam seam docs 1`] = ` }, }, }, - "thermostats/climateSettingSchedules.yml": { + "thermostats/climate_setting_schedules.yml": { "imports": { "root": "../__package__.yml", }, @@ -10700,7 +10700,7 @@ exports[`seam seam docs 1`] = ` }, }, }, - "userIdentities.yml": { + "user_identities.yml": { "imports": { "root": "__package__.yml", }, @@ -11637,7 +11637,7 @@ exports[`seam seam docs 1`] = ` }, }, }, - "userIdentities/enrollmentAutomations.yml": { + "user_identities/enrollment_automations.yml": { "imports": { "root": "../__package__.yml", }, @@ -17613,7 +17613,7 @@ exports[`seam seam docs 1`] = ` exports[`seam seam simple 1`] = ` { "definitionFiles": { - "accessCodes.yml": { + "access_codes.yml": { "imports": { "root": "__package__.yml", }, @@ -18441,7 +18441,7 @@ exports[`seam seam simple 1`] = ` }, }, }, - "accessCodes/simulate.yml": { + "access_codes/simulate.yml": { "imports": { "root": "../__package__.yml", }, @@ -18538,7 +18538,7 @@ exports[`seam seam simple 1`] = ` }, }, }, - "accessCodes/unmanaged.yml": { + "access_codes/unmanaged.yml": { "imports": { "root": "../__package__.yml", }, @@ -18886,7 +18886,7 @@ exports[`seam seam simple 1`] = ` }, }, }, - "acs/accessGroups.yml": { + "acs/access_groups.yml": { "imports": { "root": "../__package__.yml", }, @@ -19243,7 +19243,7 @@ exports[`seam seam simple 1`] = ` }, }, }, - "acs/credentialPools.yml": { + "acs/credential_pools.yml": { "imports": { "root": "../__package__.yml", }, @@ -19321,7 +19321,7 @@ exports[`seam seam simple 1`] = ` }, }, }, - "acs/credentialProvisioningAutomations.yml": { + "acs/credential_provisioning_automations.yml": { "imports": { "root": "../__package__.yml", }, @@ -21627,7 +21627,7 @@ exports[`seam seam simple 1`] = ` }, }, }, - "actionAttempts.yml": { + "action_attempts.yml": { "imports": { "root": "__package__.yml", }, @@ -21758,7 +21758,7 @@ exports[`seam seam simple 1`] = ` }, }, }, - "clientSessions.yml": { + "client_sessions.yml": { "imports": { "root": "__package__.yml", }, @@ -22214,7 +22214,7 @@ exports[`seam seam simple 1`] = ` }, }, }, - "connectWebviews.yml": { + "connect_webviews.yml": { "imports": { "root": "__package__.yml", }, @@ -22611,7 +22611,7 @@ exports[`seam seam simple 1`] = ` }, }, }, - "connectedAccounts.yml": { + "connected_accounts.yml": { "imports": { "root": "__package__.yml", }, @@ -26135,7 +26135,7 @@ exports[`seam seam simple 1`] = ` }, }, }, - "noiseSensors/noiseThresholds.yml": { + "noise_sensors/noise_thresholds.yml": { "imports": { "root": "../__package__.yml", }, @@ -26512,7 +26512,7 @@ exports[`seam seam simple 1`] = ` }, }, }, - "noiseSensors/simulate.yml": { + "noise_sensors/simulate.yml": { "imports": { "root": "../__package__.yml", }, @@ -27906,7 +27906,7 @@ exports[`seam seam simple 1`] = ` }, }, }, - "thermostats/climateSettingSchedules.yml": { + "thermostats/climate_setting_schedules.yml": { "imports": { "root": "../__package__.yml", }, @@ -28310,7 +28310,7 @@ exports[`seam seam simple 1`] = ` }, }, }, - "userIdentities.yml": { + "user_identities.yml": { "imports": { "root": "__package__.yml", }, @@ -29247,7 +29247,7 @@ exports[`seam seam simple 1`] = ` }, }, }, - "userIdentities/enrollmentAutomations.yml": { + "user_identities/enrollment_automations.yml": { "imports": { "root": "../__package__.yml", }, diff --git a/packages/cli/openapi-ir-to-fern/src/utils/getEndpointLocation.ts b/packages/cli/openapi-ir-to-fern/src/utils/getEndpointLocation.ts index 92d22774525..01f6cc0cf2f 100644 --- a/packages/cli/openapi-ir-to-fern/src/utils/getEndpointLocation.ts +++ b/packages/cli/openapi-ir-to-fern/src/utils/getEndpointLocation.ts @@ -15,7 +15,14 @@ export function getEndpointLocation(endpoint: Endpoint): EndpointLocation { const filenameWithoutExtension = endpoint.sdkName.groupName.length === 0 ? "__package__" - : `${endpoint.sdkName.groupName.map((part) => camelCase(part)).join("/")}`; + : `${endpoint.sdkName.groupName + .map((part) => { + if (part.includes(" ")) { + return camelCase(part); + } + return part; + }) + .join("/")}`; const filename = `${filenameWithoutExtension}.yml`; // only if the tag lines up with `x-fern-sdk-group-name` do we use it const isTagApplicable = filenameWithoutExtension.toLowerCase() === tag?.toLowerCase().replaceAll(" ", ""); diff --git a/packages/cli/openapi-parser/src/openapi/v3/extensions/fernExtensions.ts b/packages/cli/openapi-parser/src/openapi/v3/extensions/fernExtensions.ts index 87e4f7d1671..1dbc0583add 100644 --- a/packages/cli/openapi-parser/src/openapi/v3/extensions/fernExtensions.ts +++ b/packages/cli/openapi-parser/src/openapi/v3/extensions/fernExtensions.ts @@ -408,7 +408,31 @@ export const FernOpenAPIExtension = { * - "2.0" * - "Latest" */ - FERN_VERSION: "x-fern-version" + FERN_VERSION: "x-fern-version", + + /** + * Allows users to specify the encoding of the type. For example, suppose you need to configure + * Protobuf-encoding details like the following: + * + * User: + * properties: + * username: + * type: string + * x-fern-encoding: + * proto: + * type: user.v1.User + */ + ENCODING: "x-fern-encoding", + + /** + * Allows users to configure gRPC services. This must be specified on individual service + * declarations. + * + * x-fern-transport: + * grpc: + * service-name: UserService + */ + TRANSPORT: "x-fern-transport" } as const; export type FernOpenAPIExtension = Values; diff --git a/packages/cli/project-loader/src/loadProject.ts b/packages/cli/project-loader/src/loadProject.ts index f4fb3982871..95e36a2d2e4 100644 --- a/packages/cli/project-loader/src/loadProject.ts +++ b/packages/cli/project-loader/src/loadProject.ts @@ -1,9 +1,13 @@ import { APIS_DIRECTORY, + ASYNCAPI_DIRECTORY, + DEFINITION_DIRECTORY, fernConfigJson, FERN_DIRECTORY, generatorsYml, - getFernDirectory + GENERATORS_CONFIGURATION_FILENAME, + getFernDirectory, + OPENAPI_DIRECTORY } from "@fern-api/configuration"; import { AbsoluteFilePath, doesPathExist, join, RelativeFilePath } from "@fern-api/fs-utils"; import { TaskContext } from "@fern-api/task-context"; @@ -46,14 +50,24 @@ export async function loadProject({ return context.failAndThrow(`Directory "${nameOverride ?? FERN_DIRECTORY}" not found.`); } - const apiWorkspaces = await loadApis({ - cliName, - fernDirectory, - cliVersion, - context, - commandLineApiWorkspace, - defaultToAllApiWorkspaces - }); + let apiWorkspaces: APIWorkspace[] = []; + + if ( + (await doesPathExist(join(fernDirectory, RelativeFilePath.of(APIS_DIRECTORY)))) || + doesPathExist(join(fernDirectory, RelativeFilePath.of(DEFINITION_DIRECTORY))) || + doesPathExist(join(fernDirectory, RelativeFilePath.of(GENERATORS_CONFIGURATION_FILENAME))) || + doesPathExist(join(fernDirectory, RelativeFilePath.of(OPENAPI_DIRECTORY))) || + doesPathExist(join(fernDirectory, RelativeFilePath.of(ASYNCAPI_DIRECTORY))) + ) { + apiWorkspaces = await loadApis({ + cliName, + fernDirectory, + cliVersion, + context, + commandLineApiWorkspace, + defaultToAllApiWorkspaces + }); + } return { config: await fernConfigJson.loadProjectConfig({ directory: fernDirectory, context }), diff --git a/packages/cli/register/package.json b/packages/cli/register/package.json index 452dfa0346e..59a03904737 100644 --- a/packages/cli/register/package.json +++ b/packages/cli/register/package.json @@ -31,7 +31,7 @@ "@fern-api/configuration": "workspace:*", "@fern-api/core": "workspace:*", "@fern-api/core-utils": "workspace:*", - "@fern-api/fdr-sdk": "0.98.16-3955e989a", + "@fern-api/fdr-sdk": "0.98.18-aaf13f7f5", "@fern-api/ir-generator": "workspace:*", "@fern-api/ir-sdk": "workspace:*", "@fern-api/task-context": "workspace:*", diff --git a/packages/cli/register/src/registerApi.ts b/packages/cli/register/src/registerApi.ts index 9cfb1df083b..420311b63e2 100644 --- a/packages/cli/register/src/registerApi.ts +++ b/packages/cli/register/src/registerApi.ts @@ -30,7 +30,9 @@ export async function registerApi({ keywords: undefined, smartCasing: false, disableExamples: false, - readme: undefined + readme: undefined, + version: undefined, + packageName: undefined }); const fdrService = createFdrService({ diff --git a/packages/cli/workspace-loader/package.json b/packages/cli/workspace-loader/package.json index 92fb3e9e2a4..7698284b176 100644 --- a/packages/cli/workspace-loader/package.json +++ b/packages/cli/workspace-loader/package.json @@ -32,6 +32,7 @@ "@fern-api/core-utils": "workspace:*", "@fern-api/fs-utils": "workspace:*", "@fern-api/logger": "workspace:*", + "@fern-api/logging-execa": "workspace:*", "@fern-api/openapi-ir-to-fern": "workspace:*", "@fern-api/openapi-parser": "workspace:*", "@fern-api/semver-utils": "workspace:*", @@ -42,6 +43,7 @@ "chalk": "^5.3.0", "js-yaml": "^4.1.0", "lodash-es": "^4.17.21", + "object-hash": "^3.0.0", "tar": "^6.2.1", "tmp-promise": "^3.0.3", "zod": "^3.22.3" @@ -51,6 +53,7 @@ "@types/js-yaml": "^4.0.8", "@types/lodash-es": "^4.17.12", "@types/node": "^18.7.18", + "@types/object-hash": "^3.0.6", "@types/tar": "^6.1.11", "depcheck": "^1.4.6", "eslint": "^8.56.0", diff --git a/packages/cli/workspace-loader/src/handleFailedWorkspaceParserResult.ts b/packages/cli/workspace-loader/src/handleFailedWorkspaceParserResult.ts index 96dd0747090..3a7f4ca6e54 100644 --- a/packages/cli/workspace-loader/src/handleFailedWorkspaceParserResult.ts +++ b/packages/cli/workspace-loader/src/handleFailedWorkspaceParserResult.ts @@ -32,6 +32,11 @@ function handleWorkspaceParserFailureForFile({ logger: Logger; }): void { switch (failure.type) { + case WorkspaceLoaderFailureType.MISCONFIGURED_DIRECTORY: + logger.error( + "Misconfigured fern directory: please see the docs at https://buildwithfern.com/learn/api-definition/introduction/what-is-the-fern-folder" + ); + break; case WorkspaceLoaderFailureType.FILE_READ: logger.error("Failed to open file: " + relativeFilepath); break; diff --git a/packages/cli/workspace-loader/src/loadAPIWorkspace.ts b/packages/cli/workspace-loader/src/loadAPIWorkspace.ts index 22d806370e7..d3d943f47ad 100644 --- a/packages/cli/workspace-loader/src/loadAPIWorkspace.ts +++ b/packages/cli/workspace-loader/src/loadAPIWorkspace.ts @@ -1,4 +1,4 @@ -import { ASYNCAPI_DIRECTORY, generatorsYml, OPENAPI_DIRECTORY } from "@fern-api/configuration"; +import { ASYNCAPI_DIRECTORY, DEFINITION_DIRECTORY, generatorsYml, OPENAPI_DIRECTORY } from "@fern-api/configuration"; import { AbsoluteFilePath, doesPathExist, join, RelativeFilePath } from "@fern-api/fs-utils"; import { TaskContext } from "@fern-api/task-context"; import { loadAPIChangelog } from "./loadAPIChangelog"; @@ -38,18 +38,61 @@ export async function loadAPIWorkspace({ if (generatorsConfiguration?.api != null && generatorsConfiguration.api.definitions.length > 0) { const specs: Spec[] = []; - for (const definition of generatorsConfiguration.api.definitions) { - const absoluteFilepath = join(absolutePathToWorkspace, RelativeFilePath.of(definition.path)); const absoluteFilepathToOverrides = definition.overrides != null ? join(absolutePathToWorkspace, RelativeFilePath.of(definition.overrides)) : undefined; + if (definition.schema.type === "protobuf") { + const absoluteFilepathToProtobufRoot = join( + absolutePathToWorkspace, + RelativeFilePath.of(definition.schema.root) + ); + if (!(await doesPathExist(absoluteFilepathToProtobufRoot))) { + return { + didSucceed: false, + failures: { + [RelativeFilePath.of(definition.schema.root)]: { + type: WorkspaceLoaderFailureType.FILE_MISSING + } + } + }; + } + const absoluteFilepathToProtobufTarget = join( + absolutePathToWorkspace, + RelativeFilePath.of(definition.schema.target) + ); + if (!(await doesPathExist(absoluteFilepathToProtobufTarget))) { + return { + didSucceed: false, + failures: { + [RelativeFilePath.of(definition.schema.target)]: { + type: WorkspaceLoaderFailureType.FILE_MISSING + } + } + }; + } + specs.push({ + type: "protobuf", + absoluteFilepathToProtobufRoot, + absoluteFilepathToProtobufTarget, + absoluteFilepathToOverrides, + generateLocally: definition.schema.localGeneration, + settings: { + audiences: definition.audiences ?? [], + shouldUseTitleAsName: definition.settings?.shouldUseTitleAsName ?? true, + shouldUseUndiscriminatedUnionsWithLiterals: + definition.settings?.shouldUseUndiscriminatedUnionsWithLiterals ?? false + } + }); + continue; + } + const absoluteFilepath = join(absolutePathToWorkspace, RelativeFilePath.of(definition.schema.path)); if (!(await doesPathExist(absoluteFilepath))) { return { didSucceed: false, failures: { - [RelativeFilePath.of(definition.path)]: { + [RelativeFilePath.of(definition.schema.path)]: { type: WorkspaceLoaderFailureType.FILE_MISSING } } @@ -70,6 +113,7 @@ export async function loadAPIWorkspace({ }; } specs.push({ + type: "openapi", absoluteFilepath, absoluteFilepathToOverrides, settings: { @@ -103,12 +147,14 @@ export async function loadAPIWorkspace({ const specs: Spec[] = []; if (absolutePathToOpenAPI != null) { specs.push({ + type: "openapi", absoluteFilepath: absolutePathToOpenAPI, absoluteFilepathToOverrides: undefined }); } if (absolutePathToAsyncAPI != null) { specs.push({ + type: "openapi", absoluteFilepath: absolutePathToAsyncAPI, absoluteFilepathToOverrides: undefined }); @@ -134,18 +180,28 @@ export async function loadAPIWorkspace({ }) }; } + if (await doesPathExist(join(absolutePathToWorkspace, RelativeFilePath.of(DEFINITION_DIRECTORY)))) { + const fernWorkspace = new LazyFernWorkspace({ + absoluteFilepath: absolutePathToWorkspace, + generatorsConfiguration, + workspaceName, + changelog, + context, + cliVersion + }); - const fernWorkspace = new LazyFernWorkspace({ - absoluteFilepath: absolutePathToWorkspace, - generatorsConfiguration, - workspaceName, - changelog, - context, - cliVersion - }); + return { + didSucceed: true, + workspace: fernWorkspace + }; + } return { - didSucceed: true, - workspace: fernWorkspace + didSucceed: false, + failures: { + [RelativeFilePath.of(OPENAPI_DIRECTORY)]: { + type: WorkspaceLoaderFailureType.MISCONFIGURED_DIRECTORY + } + } }; } diff --git a/packages/cli/workspace-loader/src/loadDependency.ts b/packages/cli/workspace-loader/src/loadDependency.ts index 4f8680b2a88..af63b3d6e07 100644 --- a/packages/cli/workspace-loader/src/loadDependency.ts +++ b/packages/cli/workspace-loader/src/loadDependency.ts @@ -1,14 +1,16 @@ import { dependenciesYml } from "@fern-api/configuration"; import { createFiddleService } from "@fern-api/core"; import { assertNever, noop, visitObject } from "@fern-api/core-utils"; -import { AbsoluteFilePath } from "@fern-api/fs-utils"; +import { AbsoluteFilePath, doesPathExist, join, RelativeFilePath } from "@fern-api/fs-utils"; import { parseVersion } from "@fern-api/semver-utils"; import { TaskContext } from "@fern-api/task-context"; import { RootApiFileSchema, YAML_SCHEMA_VERSION } from "@fern-api/yaml-schema"; import { FernFiddle } from "@fern-fern/fiddle-sdk"; import axios from "axios"; import { createWriteStream } from "fs"; +import { mkdir, readFile, writeFile } from "fs/promises"; import { isEqual } from "lodash-es"; +import { homedir } from "os"; import path from "path"; import { pipeline } from "stream/promises"; import tar from "tar"; @@ -97,6 +99,22 @@ export async function loadDependency({ } } +const DEPENDENCIES_FOLDER_NAME = "dependencies"; +const DEFINITION_FOLDER_NAME = "definition"; +const METADATA_RESPONSE_FILENAME = "metadata.json"; +const LOCAL_STORAGE_FOLDER = process.env.LOCAL_STORAGE_FOLDER ?? ".fern"; + +function getPathToLocalStorageDependency(dependency: dependenciesYml.VersionedDependency): AbsoluteFilePath { + return join( + AbsoluteFilePath.of(homedir()), + RelativeFilePath.of(LOCAL_STORAGE_FOLDER), + RelativeFilePath.of(DEPENDENCIES_FOLDER_NAME), + RelativeFilePath.of(dependency.organization), + RelativeFilePath.of(dependency.apiName), + RelativeFilePath.of(dependency.version) + ); +} + async function validateLocalDependencyAndGetDefinition({ dependency, context, @@ -121,7 +139,10 @@ async function validateLocalDependencyAndGetDefinition({ return undefined; } - return await loadDependencyWorkspaceResult.workspace.getDefinition({ context }, settings); + const definition = await loadDependencyWorkspaceResult.workspace.getDefinition({ context }, settings); + context.logger.info("Loaded..."); + + return definition; } async function validateVersionedDependencyAndGetDefinition({ @@ -135,83 +156,97 @@ async function validateVersionedDependencyAndGetDefinition({ cliVersion: string; settings?: OSSWorkspace.Settings; }): Promise { - // load API - context.logger.info("Downloading manifest..."); - const response = await FIDDLE.definitionRegistry.get( - FernFiddle.OrganizationId(dependency.organization), - FernFiddle.ApiId(dependency.apiName), - dependency.version - ); - if (!response.ok) { - response.error._visit({ - orgDoesNotExistError: () => { - context.failWithoutThrowing("Organization does not exist"); - }, - apiDoesNotExistError: () => { - context.failWithoutThrowing("API does not exist"); - }, - versionDoesNotExistError: () => { - context.failWithoutThrowing("Version does not exist"); - }, - _other: (error) => { - context.failWithoutThrowing("Failed to download API manifest", error); - } - }); - return undefined; - } + const pathToDependency: AbsoluteFilePath = getPathToLocalStorageDependency(dependency); + const pathToDefinition = join(pathToDependency, RelativeFilePath.of(DEPENDENCIES_FOLDER_NAME)); + const pathToMetadata = join(pathToDependency, RelativeFilePath.of(METADATA_RESPONSE_FILENAME)); + + let metadata: FernFiddle.Api; + if (!(await doesPathExist(pathToDefinition)) || !(await doesPathExist(pathToMetadata))) { + // load API + context.logger.info("Downloading manifest..."); + const response = await FIDDLE.definitionRegistry.get( + FernFiddle.OrganizationId(dependency.organization), + FernFiddle.ApiId(dependency.apiName), + dependency.version + ); + if (!response.ok) { + response.error._visit({ + orgDoesNotExistError: () => { + context.failWithoutThrowing("Organization does not exist"); + }, + apiDoesNotExistError: () => { + context.failWithoutThrowing("API does not exist"); + }, + versionDoesNotExistError: () => { + context.failWithoutThrowing("Version does not exist"); + }, + _other: (error) => { + context.failWithoutThrowing("Failed to download API manifest", error); + } + }); + return undefined; + } - const parsedYamlVersionOfDependency = - response.body.yamlSchemaVersion != null ? parseInt(response.body.yamlSchemaVersion) : undefined; - const parsedCliVersion = parseVersion(cliVersion); - const parsedCliVersionOfDependency = parseVersion(response.body.cliVersion); + const parsedYamlVersionOfDependency = + response.body.yamlSchemaVersion != null ? parseInt(response.body.yamlSchemaVersion) : undefined; + const parsedCliVersion = parseVersion(cliVersion); + const parsedCliVersionOfDependency = parseVersion(response.body.cliVersion); - // ensure dependency is on the same YAML_SCHEMA_VERSION - if (parsedYamlVersionOfDependency != null) { - if (parsedYamlVersionOfDependency > YAML_SCHEMA_VERSION) { + // ensure dependency is on the same YAML_SCHEMA_VERSION + if (parsedYamlVersionOfDependency != null) { + if (parsedYamlVersionOfDependency > YAML_SCHEMA_VERSION) { + context.failWithoutThrowing( + `${dependency.organization}/${dependency.apiName}@${dependency.version} on a higher version of fern. Upgrade this workspace to ${response.body.cliVersion}` + ); + return undefined; + } else if (parsedYamlVersionOfDependency < YAML_SCHEMA_VERSION) { + context.failWithoutThrowing( + `${dependency.organization}/${dependency.apiName}@${dependency.version} on a lower version of fern. Upgrade it to ${cliVersion}` + ); + return undefined; + } + } + // otherwise, ensure CLI versions are on the same major + minor versions + else if ( + parsedCliVersion.major !== parsedCliVersionOfDependency.major || + parsedCliVersion.minor !== parsedCliVersionOfDependency.minor + ) { context.failWithoutThrowing( - `${dependency.organization}/${dependency.apiName}@${dependency.version} on a higher version of fern. Upgrade this workspace to ${response.body.cliVersion}` + `CLI version is ${response.body.cliVersion}. Expected ${parsedCliVersion.major}.${parsedCliVersion.minor}.x (to match current workspace).` ); return undefined; - } else if (parsedYamlVersionOfDependency < YAML_SCHEMA_VERSION) { - context.failWithoutThrowing( - `${dependency.organization}/${dependency.apiName}@${dependency.version} on a lower version of fern. Upgrade it to ${cliVersion}` - ); + } + + // download API + context.logger.info("Downloading..."); + context.logger.debug("Remote URL: " + response.body.definitionS3DownloadUrl); + + await mkdir(pathToDefinition, { recursive: true }); + + try { + await downloadDependency({ + s3PreSignedReadUrl: response.body.definitionS3DownloadUrl, + absolutePathToLocalOutput: pathToDefinition + }); + } catch (error) { + context.failWithoutThrowing("Failed to download API", error); return undefined; } - } - // otherwise, ensure CLI versions are on the same major + minor versions - else if ( - parsedCliVersion.major !== parsedCliVersionOfDependency.major || - parsedCliVersion.minor !== parsedCliVersionOfDependency.minor - ) { - context.failWithoutThrowing( - `CLI version is ${response.body.cliVersion}. Expected ${parsedCliVersion.major}.${parsedCliVersion.minor}.x (to match current workspace).` - ); - return undefined; - } - // download API - context.logger.info("Downloading..."); - const pathToDependency = AbsoluteFilePath.of((await tmp.dir()).path); - context.logger.debug("Remote URL: " + response.body.definitionS3DownloadUrl); - try { - await downloadDependency({ - s3PreSignedReadUrl: response.body.definitionS3DownloadUrl, - absolutePathToLocalOutput: pathToDependency - }); - } catch (error) { - context.failWithoutThrowing("Failed to download API", error); - return undefined; + metadata = response.body; + await writeFile(pathToMetadata, JSON.stringify(metadata)); + } else { + metadata = JSON.parse((await readFile(pathToMetadata)).toString()); } - // parse workspace context.logger.info("Parsing..."); const loadDependencyWorkspaceResult = await loadAPIWorkspace({ - absolutePathToWorkspace: pathToDependency, + absolutePathToWorkspace: pathToDefinition, context, - cliVersion: response.body.cliVersion, + cliVersion: metadata.cliVersion, workspaceName: undefined }); + if (!loadDependencyWorkspaceResult.didSucceed) { context.failWithoutThrowing( "Failed to parse dependency after downloading", diff --git a/packages/cli/workspace-loader/src/protobuf/ProtobufOpenAPIGenerator.ts b/packages/cli/workspace-loader/src/protobuf/ProtobufOpenAPIGenerator.ts new file mode 100644 index 00000000000..cdb0dc1a07e --- /dev/null +++ b/packages/cli/workspace-loader/src/protobuf/ProtobufOpenAPIGenerator.ts @@ -0,0 +1,114 @@ +import { AbsoluteFilePath, join, relative, RelativeFilePath } from "@fern-api/fs-utils"; +import { createLoggingExecutable } from "@fern-api/logging-execa"; +import { TaskContext } from "@fern-api/task-context"; +import { cp, writeFile } from "fs/promises"; +import tmp from "tmp-promise"; + +const PROTOBUF_GENERATOR_CONFIG_FILENAME = "buf.gen.yaml"; +const PROTOBUF_GENERATOR_OUTPUT_PATH = "output"; +const PROTOBUF_GENERATOR_OUTPUT_FILEPATH = `${PROTOBUF_GENERATOR_OUTPUT_PATH}/openapi.yaml`; +const PROTOBUF_GENERATOR_CONFIG = ` +version: v1 +plugins: + - plugin: openapi + out: ${PROTOBUF_GENERATOR_OUTPUT_PATH} + opt: + - title="" + - enum_type=string + - default_response=false +`; + +export class ProtobufOpenAPIGenerator { + private context: TaskContext; + + constructor({ context }: { context: TaskContext }) { + this.context = context; + } + + public async generate({ + absoluteFilepathToProtobufRoot, + absoluteFilepathToProtobufTarget, + local + }: { + absoluteFilepathToProtobufRoot: AbsoluteFilePath; + absoluteFilepathToProtobufTarget: AbsoluteFilePath; + local: boolean; + }): Promise { + if (local) { + return this.generateLocal({ absoluteFilepathToProtobufRoot, absoluteFilepathToProtobufTarget }); + } + return this.generateRemote(); + } + + private async generateLocal({ + absoluteFilepathToProtobufRoot, + absoluteFilepathToProtobufTarget + }: { + absoluteFilepathToProtobufRoot: AbsoluteFilePath; + absoluteFilepathToProtobufTarget: AbsoluteFilePath; + }): Promise { + const protobufGeneratorConfigPath = await this.setupProtobufGeneratorConfig({ + absoluteFilepathToProtobufRoot + }); + const protoTargetRelativeFilePath = relative(absoluteFilepathToProtobufRoot, absoluteFilepathToProtobufTarget); + return this.doGenerateLocal({ + cwd: protobufGeneratorConfigPath, + target: protoTargetRelativeFilePath + }); + } + + private async setupProtobufGeneratorConfig({ + absoluteFilepathToProtobufRoot + }: { + absoluteFilepathToProtobufRoot: AbsoluteFilePath; + }): Promise { + const protobufGeneratorConfigPath = AbsoluteFilePath.of((await tmp.dir()).path); + await cp(absoluteFilepathToProtobufRoot, protobufGeneratorConfigPath, { recursive: true }); + await writeFile( + join(protobufGeneratorConfigPath, RelativeFilePath.of(PROTOBUF_GENERATOR_CONFIG_FILENAME)), + PROTOBUF_GENERATOR_CONFIG + ); + return protobufGeneratorConfigPath; + } + + private async doGenerateLocal({ + cwd, + target + }: { + cwd: AbsoluteFilePath; + target: RelativeFilePath; + }): Promise { + const which = createLoggingExecutable("which", { + cwd, + logger: this.context.logger + }); + + try { + await which(["buf"]); + } catch (err) { + this.context.failAndThrow( + "Missing required dependency; please install 'buf' to continue (e.g. 'brew install buf')." + ); + } + + try { + await which(["protoc-gen-openapi"]); + } catch (err) { + this.context.failAndThrow( + "Missing required dependency; please install 'protoc-gen-openapi' to continue (e.g. 'brew install go && go install github.com/google/gnostic/cmd/protoc-gen-openapi@latest')." + ); + } + + const buf = createLoggingExecutable("buf", { + cwd, + logger: this.context.logger + }); + await buf(["config", "init"]); + await buf(["generate", target]); + return join(cwd, RelativeFilePath.of(PROTOBUF_GENERATOR_OUTPUT_FILEPATH)); + } + + private async generateRemote(): Promise { + this.context.failAndThrow("Remote Protobuf generation is unimplemented."); + } +} diff --git a/packages/cli/workspace-loader/src/types/Result.ts b/packages/cli/workspace-loader/src/types/Result.ts index e135e804a3b..e1626a40fcb 100644 --- a/packages/cli/workspace-loader/src/types/Result.ts +++ b/packages/cli/workspace-loader/src/types/Result.ts @@ -37,6 +37,10 @@ export declare namespace WorkspaceLoader { type: WorkspaceLoaderFailureType.FILE_MISSING; } + export interface MisconfiguredDirectoryFailure { + type: WorkspaceLoaderFailureType.MISCONFIGURED_DIRECTORY; + } + export interface StructureValidationFailure { type: WorkspaceLoaderFailureType.STRUCTURE_VALIDATION; error: ZodError; @@ -46,7 +50,8 @@ export declare namespace WorkspaceLoader { | DependencyNotListedFailure | FailedToLoadDependencyFailure | ExportPackageHasDefinitionsFailure - | ExportingPackageMarkerHasOtherKeysFailure; + | ExportingPackageMarkerHasOtherKeysFailure + | MisconfiguredDirectoryFailure; export interface DependencyNotListedFailure { type: WorkspaceLoaderFailureType.DEPENDENCY_NOT_LISTED; @@ -77,5 +82,6 @@ export enum WorkspaceLoaderFailureType { DEPENDENCY_NOT_LISTED = "DEPENDENCY_NOT_LISTED", FAILED_TO_LOAD_DEPENDENCY = "FAILED_TO_LOAD_DEPENDENCY", EXPORTING_PACKAGE_MARKER_OTHER_KEYS = "EXPORTING_PACKAGE_MARKER_OTHER_KEYS", - EXPORT_PACKAGE_HAS_DEFINITIONS = "EXPORT_PACKAGE_HAS_DEFINITIONS" + EXPORT_PACKAGE_HAS_DEFINITIONS = "EXPORT_PACKAGE_HAS_DEFINITIONS", + MISCONFIGURED_DIRECTORY = "MISCONFIGURED_DIRECTORY" } diff --git a/packages/cli/workspace-loader/src/types/Workspace.ts b/packages/cli/workspace-loader/src/types/Workspace.ts index 106507dc9b1..50597991870 100644 --- a/packages/cli/workspace-loader/src/types/Workspace.ts +++ b/packages/cli/workspace-loader/src/types/Workspace.ts @@ -16,12 +16,23 @@ export interface DocsWorkspace { config: docsYml.RawSchemas.DocsConfiguration; } -export interface Spec { +export type Spec = OpenAPISpec | ProtobufSpec; + +export interface OpenAPISpec { + type: "openapi"; absoluteFilepath: AbsoluteFilePath; absoluteFilepathToOverrides: AbsoluteFilePath | undefined; settings?: SpecImportSettings; } +export interface ProtobufSpec { + type: "protobuf"; + absoluteFilepathToProtobufRoot: AbsoluteFilePath; + absoluteFilepathToProtobufTarget: AbsoluteFilePath; + absoluteFilepathToOverrides: AbsoluteFilePath | undefined; + generateLocally: boolean; + settings?: SpecImportSettings; +} export interface SpecImportSettings { audiences: string[]; shouldUseTitleAsName: boolean; diff --git a/packages/cli/workspace-loader/src/utils/getAllOpenAPISpecs.ts b/packages/cli/workspace-loader/src/utils/getAllOpenAPISpecs.ts new file mode 100644 index 00000000000..47ccf34b0d3 --- /dev/null +++ b/packages/cli/workspace-loader/src/utils/getAllOpenAPISpecs.ts @@ -0,0 +1,41 @@ +import { TaskContext } from "@fern-api/task-context"; +import { ProtobufOpenAPIGenerator } from "../protobuf/ProtobufOpenAPIGenerator"; +import { OpenAPISpec, ProtobufSpec, Spec } from "../types/Workspace"; + +export async function getAllOpenAPISpecs({ + context, + specs +}: { + context: TaskContext; + specs: Spec[]; +}): Promise { + const generator = new ProtobufOpenAPIGenerator({ context }); + const openApiSpecs: OpenAPISpec[] = specs.filter((spec): spec is OpenAPISpec => spec.type === "openapi"); + const protobufSpecs: ProtobufSpec[] = specs.filter((spec): spec is ProtobufSpec => spec.type === "protobuf"); + const convertedOpenApiSpecs = await Promise.all( + protobufSpecs.map(async (protobufSpec) => { + return convertProtobufToOpenAPI({ generator, protobufSpec }); + }) + ); + return [...openApiSpecs, ...convertedOpenApiSpecs]; +} + +export async function convertProtobufToOpenAPI({ + generator, + protobufSpec +}: { + generator: ProtobufOpenAPIGenerator; + protobufSpec: ProtobufSpec; +}): Promise { + const openAPIAbsoluteFilePath = await generator.generate({ + absoluteFilepathToProtobufRoot: protobufSpec.absoluteFilepathToProtobufRoot, + absoluteFilepathToProtobufTarget: protobufSpec.absoluteFilepathToProtobufTarget, + local: protobufSpec.generateLocally + }); + return { + type: "openapi", + absoluteFilepath: openAPIAbsoluteFilePath, + absoluteFilepathToOverrides: protobufSpec.absoluteFilepathToOverrides, + settings: protobufSpec.settings + }; +} diff --git a/packages/cli/workspace-loader/src/utils/index.ts b/packages/cli/workspace-loader/src/utils/index.ts index ddaff59b601..267d8d2f885 100644 --- a/packages/cli/workspace-loader/src/utils/index.ts +++ b/packages/cli/workspace-loader/src/utils/index.ts @@ -1,5 +1,6 @@ export { getAllDefinitionFiles } from "./getAllDefinitionFiles"; export { getAllNamedDefinitionFiles } from "./getAllNamedDefinitionFiles"; +export { getAllOpenAPISpecs } from "./getAllOpenAPISpecs"; export { getAllPackageMarkers } from "./getAllPackageMarkers"; export { getDefinitionFile } from "./getDefinitionFile"; export { visitAllDefinitionFiles } from "./visitAllDefinitionFiles"; diff --git a/packages/cli/workspace-loader/src/workspaces/AbstractAPIWorkspace.ts b/packages/cli/workspace-loader/src/workspaces/AbstractAPIWorkspace.ts index 5f9039780a2..477102c0be7 100644 --- a/packages/cli/workspace-loader/src/workspaces/AbstractAPIWorkspace.ts +++ b/packages/cli/workspace-loader/src/workspaces/AbstractAPIWorkspace.ts @@ -1,3 +1,4 @@ +import { AbsoluteFilePath } from "@fern-api/fs-utils"; import { TaskContext } from "@fern-api/task-context"; import { FernDefinition } from "../types/Workspace"; import { FernWorkspace } from "./FernWorkspace"; @@ -19,4 +20,9 @@ export abstract class AbstractAPIWorkspace { { context }: { context?: TaskContext }, settings?: Settings ): Promise; + + /** + * @returns all filepaths related to this workspace + */ + public abstract getAbsoluteFilepaths(): AbsoluteFilePath[]; } diff --git a/packages/cli/workspace-loader/src/workspaces/FernWorkspace.ts b/packages/cli/workspace-loader/src/workspaces/FernWorkspace.ts index 276d9c5ffc0..c77e3c07981 100644 --- a/packages/cli/workspace-loader/src/workspaces/FernWorkspace.ts +++ b/packages/cli/workspace-loader/src/workspaces/FernWorkspace.ts @@ -1,6 +1,7 @@ import { DEFINITION_DIRECTORY, dependenciesYml, generatorsYml } from "@fern-api/configuration"; import { AbsoluteFilePath, join, RelativeFilePath } from "@fern-api/fs-utils"; import { TaskContext } from "@fern-api/task-context"; +import hash from "object-hash"; import { handleFailedWorkspaceParserResultRaw } from "../handleFailedWorkspaceParserResult"; import { listFernFiles } from "../listFernFiles"; import { parseYamlFiles } from "../parseYamlFiles"; @@ -50,6 +51,10 @@ export class FernWorkspace extends AbstractAPIWorkspace { public async toFernWorkspace(): Promise { return this; } + + public getAbsoluteFilepaths(): AbsoluteFilePath[] { + return [this.absoluteFilepath]; + } } export declare namespace LazyFernWorkspace { @@ -75,7 +80,7 @@ export class LazyFernWorkspace extends AbstractAPIWorkspace = {}; constructor({ absoluteFilepath, @@ -105,65 +110,68 @@ export class LazyFernWorkspace extends AbstractAPIWorkspace { - if (this.downloaded) { - context?.logger.disable(); - } - - const defaultedContext = context || this.context; - const absolutePathToDefinition = join(this.absoluteFilepath, RelativeFilePath.of(DEFINITION_DIRECTORY)); - const dependenciesConfiguration = await dependenciesYml.loadDependenciesConfiguration({ - absolutePathToWorkspace: this.absoluteFilepath, - context: defaultedContext - }); - - const yamlFiles = await listFernFiles(absolutePathToDefinition, "{yml,yaml}"); - - const parseResult = await parseYamlFiles(yamlFiles); - if (!parseResult.didSucceed) { - handleFailedWorkspaceParserResultRaw(parseResult.failures, defaultedContext.logger); - return defaultedContext.failAndThrow(); + const key = hash(settings ?? {}); + let workspace = this.fernWorkspaces[key]; + + if (workspace == null) { + const defaultedContext = context || this.context; + const absolutePathToDefinition = join(this.absoluteFilepath, RelativeFilePath.of(DEFINITION_DIRECTORY)); + const dependenciesConfiguration = await dependenciesYml.loadDependenciesConfiguration({ + absolutePathToWorkspace: this.absoluteFilepath, + context: defaultedContext + }); + + const yamlFiles = await listFernFiles(absolutePathToDefinition, "{yml,yaml}"); + + const parseResult = await parseYamlFiles(yamlFiles); + if (!parseResult.didSucceed) { + handleFailedWorkspaceParserResultRaw(parseResult.failures, defaultedContext.logger); + return defaultedContext.failAndThrow(); + } + + const structuralValidationResult = validateStructureOfYamlFiles({ + files: parseResult.files, + absolutePathToDefinition + }); + if (!structuralValidationResult.didSucceed) { + handleFailedWorkspaceParserResultRaw(structuralValidationResult.failures, defaultedContext.logger); + return defaultedContext.failAndThrow(); + } + + const processPackageMarkersResult = await processPackageMarkers({ + dependenciesConfiguration, + structuralValidationResult, + context: defaultedContext, + cliVersion: this.cliVersion, + settings + }); + if (!processPackageMarkersResult.didSucceed) { + handleFailedWorkspaceParserResultRaw(processPackageMarkersResult.failures, defaultedContext.logger); + return defaultedContext.failAndThrow(); + } + + workspace = new FernWorkspace({ + absoluteFilepath: this.absoluteFilepath, + generatorsConfiguration: this.generatorsConfiguration, + dependenciesConfiguration, + workspaceName: this.workspaceName, + definition: { + absoluteFilepath: absolutePathToDefinition, + rootApiFile: structuralValidationResult.rootApiFile, + namedDefinitionFiles: structuralValidationResult.namedDefinitionFiles, + packageMarkers: processPackageMarkersResult.packageMarkers, + importedDefinitions: processPackageMarkersResult.importedDefinitions + }, + changelog: this.changelog + }); + + this.fernWorkspaces[key] = workspace; } - const structuralValidationResult = validateStructureOfYamlFiles({ - files: parseResult.files, - absolutePathToDefinition - }); - if (!structuralValidationResult.didSucceed) { - handleFailedWorkspaceParserResultRaw(structuralValidationResult.failures, defaultedContext.logger); - return defaultedContext.failAndThrow(); - } - - const processPackageMarkersResult = await processPackageMarkers({ - dependenciesConfiguration, - structuralValidationResult, - context: defaultedContext, - cliVersion: this.cliVersion, - settings - }); - if (!processPackageMarkersResult.didSucceed) { - handleFailedWorkspaceParserResultRaw(processPackageMarkersResult.failures, defaultedContext.logger); - return defaultedContext.failAndThrow(); - } - - if (!this.downloaded) { - this.downloaded = true; - } else { - context?.logger.enable(); - } + return workspace; + } - return new FernWorkspace({ - absoluteFilepath: this.absoluteFilepath, - generatorsConfiguration: this.generatorsConfiguration, - dependenciesConfiguration, - workspaceName: this.workspaceName, - definition: { - absoluteFilepath: absolutePathToDefinition, - rootApiFile: structuralValidationResult.rootApiFile, - namedDefinitionFiles: structuralValidationResult.namedDefinitionFiles, - packageMarkers: processPackageMarkersResult.packageMarkers, - importedDefinitions: processPackageMarkersResult.importedDefinitions - }, - changelog: this.changelog - }); + public getAbsoluteFilepaths(): AbsoluteFilePath[] { + return [this.absoluteFilepath]; } } diff --git a/packages/cli/workspace-loader/src/workspaces/OSSWorkspace.ts b/packages/cli/workspace-loader/src/workspaces/OSSWorkspace.ts index ae0404524b1..e9dfe03d39f 100644 --- a/packages/cli/workspace-loader/src/workspaces/OSSWorkspace.ts +++ b/packages/cli/workspace-loader/src/workspaces/OSSWorkspace.ts @@ -1,4 +1,5 @@ import { FERN_PACKAGE_MARKER_FILENAME, generatorsYml } from "@fern-api/configuration"; +import { isNonNullish } from "@fern-api/core-utils"; import { AbsoluteFilePath, RelativeFilePath } from "@fern-api/fs-utils"; import { convert } from "@fern-api/openapi-ir-to-fern"; import { parse, ParseOpenAPIOptions } from "@fern-api/openapi-parser"; @@ -6,6 +7,7 @@ import { TaskContext } from "@fern-api/task-context"; import yaml from "js-yaml"; import { mapValues as mapValuesLodash } from "lodash-es"; import { APIChangelog, FernDefinition, Spec } from "../types/Workspace"; +import { getAllOpenAPISpecs } from "../utils/getAllOpenAPISpecs"; import { AbstractAPIWorkspace } from "./AbstractAPIWorkspace"; import { FernWorkspace } from "./FernWorkspace"; @@ -63,8 +65,9 @@ export class OSSWorkspace extends AbstractAPIWorkspace { }, settings?: OSSWorkspace.Settings ): Promise { + const openApiSpecs = await getAllOpenAPISpecs({ context, specs: this.specs }); const openApiIr = await parse({ - specs: this.specs, + specs: openApiSpecs, taskContext: context, optionOverrides: getOptionsOverridesFromSettings(settings) }); @@ -119,6 +122,18 @@ export class OSSWorkspace extends AbstractAPIWorkspace { changelog: this.changelog }); } + + public getAbsoluteFilepaths(): AbsoluteFilePath[] { + return [ + this.absoluteFilepath, + ...this.specs + .flatMap((spec) => [ + spec.type === "protobuf" ? spec.absoluteFilepathToProtobufTarget : spec.absoluteFilepath, + spec.absoluteFilepathToOverrides + ]) + .filter(isNonNullish) + ]; + } } export function getOSSWorkspaceSettingsFromGeneratorInvocation( diff --git a/packages/cli/workspace-loader/tsconfig.json b/packages/cli/workspace-loader/tsconfig.json index ce924cb7ffa..415cd9f732e 100644 --- a/packages/cli/workspace-loader/tsconfig.json +++ b/packages/cli/workspace-loader/tsconfig.json @@ -5,6 +5,7 @@ "references": [ { "path": "../../commons/core-utils" }, { "path": "../../commons/fs-utils" }, + { "path": "../../commons/logging-execa" }, { "path": "../../core" }, { "path": "../configuration" }, { "path": "../logger" }, diff --git a/packages/cli/yaml/docs-validator/package.json b/packages/cli/yaml/docs-validator/package.json index 1324a2a297c..60c40f42f3f 100644 --- a/packages/cli/yaml/docs-validator/package.json +++ b/packages/cli/yaml/docs-validator/package.json @@ -36,8 +36,10 @@ "@types/tinycolor2": "^1.4.6", "chardet": "^2.0.0", "file-type": "^19.0.0", - "next-mdx-remote": "^4.4.1", - "remark-gfm": "^3.0.1", + "next-mdx-remote": "^5.0.0", + "rehype-katex": "^7.0.0", + "remark-gfm": "^4.0.0", + "remark-math": "^6.0.0", "tinycolor2": "^1.6.0", "zod": "^3.22.3" }, diff --git a/packages/cli/yaml/docs-validator/src/rules/valid-markdown/valid-markdown.ts b/packages/cli/yaml/docs-validator/src/rules/valid-markdown/valid-markdown.ts index acd214730f0..1f86ec64233 100644 --- a/packages/cli/yaml/docs-validator/src/rules/valid-markdown/valid-markdown.ts +++ b/packages/cli/yaml/docs-validator/src/rules/valid-markdown/valid-markdown.ts @@ -1,5 +1,7 @@ import { serialize } from "next-mdx-remote/serialize"; import remarkGfm from "remark-gfm"; +import remarkMath from "remark-math"; +import rehypeKatex from "rehype-katex"; import { z } from "zod"; import { Rule } from "../../Rule"; @@ -28,7 +30,8 @@ export const ValidMarkdownRule: Rule = { } }; -const REMARK_PLUGINS = [remarkGfm]; +const REMARK_PLUGINS = [remarkGfm, remarkMath]; +const REHYPE_PLUGINS = [rehypeKatex]; type MarkdownParseResult = MarkdownParseSuccess | MarkdownParseFailure; @@ -65,6 +68,7 @@ async function parseMarkdown({ markdown }: { markdown: string }): Promise; diff --git a/packages/cli/yaml/yaml-schema/src/schemas/EncodingSchema.ts b/packages/cli/yaml/yaml-schema/src/schemas/EncodingSchema.ts new file mode 100644 index 00000000000..a3520907f21 --- /dev/null +++ b/packages/cli/yaml/yaml-schema/src/schemas/EncodingSchema.ts @@ -0,0 +1,8 @@ +import { z } from "zod"; +import { ProtobufTypeSchema } from "./ProtobufTypeSchema"; + +export const EncodingSchema = z.strictObject({ + proto: z.optional(ProtobufTypeSchema) +}); + +export type EncodingSchema = z.infer; diff --git a/packages/cli/yaml/yaml-schema/src/schemas/GrpcSchema.ts b/packages/cli/yaml/yaml-schema/src/schemas/GrpcSchema.ts new file mode 100644 index 00000000000..95fbcb79719 --- /dev/null +++ b/packages/cli/yaml/yaml-schema/src/schemas/GrpcSchema.ts @@ -0,0 +1,7 @@ +import { z } from "zod"; + +export const GrpcSchema = z.strictObject({ + "service-name": z.string().describe("The name of the gRPC service.") +}); + +export type GrpcSchema = z.infer; diff --git a/packages/cli/yaml/yaml-schema/src/schemas/HttpServiceSchema.ts b/packages/cli/yaml/yaml-schema/src/schemas/HttpServiceSchema.ts index 48a43ae70ea..62d60f81327 100644 --- a/packages/cli/yaml/yaml-schema/src/schemas/HttpServiceSchema.ts +++ b/packages/cli/yaml/yaml-schema/src/schemas/HttpServiceSchema.ts @@ -3,6 +3,7 @@ import { DeclarationWithoutDocsSchema } from "./DeclarationSchema"; import { HttpEndpointSchema } from "./HttpEndpointSchema"; import { HttpHeaderSchema } from "./HttpHeaderSchema"; import { HttpPathParameterSchema } from "./HttpPathParameterSchema"; +import { TransportSchema } from "./TransportSchema"; export const HttpServiceSchema = DeclarationWithoutDocsSchema.extend({ auth: z.boolean(), @@ -12,6 +13,7 @@ export const HttpServiceSchema = DeclarationWithoutDocsSchema.extend({ "path-parameters": z.optional(z.record(z.string(), HttpPathParameterSchema)), idempotent: z.optional(z.boolean()), headers: z.optional(z.record(HttpHeaderSchema)), + transport: z.optional(TransportSchema), endpoints: z.record(HttpEndpointSchema) }); diff --git a/packages/cli/yaml/yaml-schema/src/schemas/ProtobufTypeSchema.ts b/packages/cli/yaml/yaml-schema/src/schemas/ProtobufTypeSchema.ts new file mode 100644 index 00000000000..4d9f8d6b369 --- /dev/null +++ b/packages/cli/yaml/yaml-schema/src/schemas/ProtobufTypeSchema.ts @@ -0,0 +1,7 @@ +import { z } from "zod"; + +export const ProtobufTypeSchema = z.strictObject({ + type: z.optional(z.string()).describe("The name of the Protobuf type (e.g. google.protobuf.Struct).") +}); + +export type ProtobufTypeSchema = z.infer; diff --git a/packages/cli/yaml/yaml-schema/src/schemas/TransportSchema.ts b/packages/cli/yaml/yaml-schema/src/schemas/TransportSchema.ts new file mode 100644 index 00000000000..fa9cbf97422 --- /dev/null +++ b/packages/cli/yaml/yaml-schema/src/schemas/TransportSchema.ts @@ -0,0 +1,8 @@ +import { z } from "zod"; +import { GrpcSchema } from "./GrpcSchema"; + +export const TransportSchema = z.strictObject({ + grpc: z.optional(GrpcSchema) +}); + +export type TransportSchema = z.infer; diff --git a/packages/cli/yaml/yaml-schema/src/schemas/index.ts b/packages/cli/yaml/yaml-schema/src/schemas/index.ts index 028a04965f4..520f314580c 100644 --- a/packages/cli/yaml/yaml-schema/src/schemas/index.ts +++ b/packages/cli/yaml/yaml-schema/src/schemas/index.ts @@ -13,6 +13,7 @@ export { CasingOverridesSchema } from "./CasingOverridesSchema"; export { CursorPaginationSchema } from "./CursorPaginationSchema"; export { DeclarationSchema, DeclarationWithoutDocsSchema } from "./DeclarationSchema"; export { DiscriminatedUnionSchema } from "./DiscriminatedUnionSchema"; +export { EncodingSchema } from "./EncodingSchema"; export { EnumSchema } from "./EnumSchema"; export { EnumValueSchema } from "./EnumValueSchema"; export { EnvironmentSchema } from "./EnvironmentSchema"; @@ -33,6 +34,7 @@ export { ExampleTypeValueSchema } from "./ExampleTypeValueSchema"; export { ExampleWebhookCallSchema } from "./ExampleWebhookCallSchema"; export { ExampleWebSocketSession } from "./ExampleWebSocketSession"; export * from "./file-schemas"; +export { GrpcSchema } from "./GrpcSchema"; export { HeaderAuthSchemeSchema } from "./HeaderAuthSchemeSchema"; export { HttpEndpointSchema, HttpMethodSchema } from "./HttpEndpointSchema"; export { HttpHeaderSchema } from "./HttpHeaderSchema"; @@ -59,12 +61,14 @@ export { ObjectSchema } from "./ObjectSchema"; export { OffsetPaginationSchema } from "./OffsetPaginationSchema"; export { PaginationSchema } from "./PaginationSchema"; export { PropertyErrorDiscriminationSchema } from "./PropertyErrorDiscriminationSchema"; +export { ProtobufTypeSchema } from "./ProtobufTypeSchema"; export { ResponseErrorsSchema } from "./ResponseErrorsSchema"; export { SingleBaseUrlEnvironmentSchema } from "./SingleBaseUrlEnvironmentSchema"; export { SingleUnionTypeKeySchema } from "./SingleUnionTypeKeySchema"; export { SingleUnionTypeSchema } from "./SingleUnionTypeSchema"; export { StatusCodeErrorDiscriminationSchema } from "./StatusCodeErrorDiscriminationSchema"; export { StringValidationSchema } from "./StringValidationSchema"; +export { TransportSchema } from "./TransportSchema"; export { TypeDeclarationSchema } from "./TypeDeclarationSchema"; export { TypeReferenceSchema, TypeReferenceWithDocsSchema } from "./TypeReferenceSchema"; export { UndiscriminatedUnionSchema } from "./UndiscriminatedUnionSchema"; diff --git a/packages/core/package.json b/packages/core/package.json index 182d5b0e43c..d716eb507f3 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -27,7 +27,7 @@ "depcheck": "depcheck" }, "dependencies": { - "@fern-api/fdr-sdk": "0.98.16-3955e989a", + "@fern-api/fdr-sdk": "0.98.18-aaf13f7f5", "@fern-api/venus-api-sdk": "0.0.38", "@fern-fern/fdr-test-sdk": "^0.0.5297", "@fern-fern/fiddle-sdk": "0.0.584" diff --git a/packages/ir-sdk/fern/apis/ir-types-latest/VERSION b/packages/ir-sdk/fern/apis/ir-types-latest/VERSION index eda335eb3e7..1fa183ec2ed 100644 --- a/packages/ir-sdk/fern/apis/ir-types-latest/VERSION +++ b/packages/ir-sdk/fern/apis/ir-types-latest/VERSION @@ -1 +1 @@ -53.2.0 +53.4.0 diff --git a/packages/ir-sdk/fern/apis/ir-types-latest/changelog/CHANGELOG.md b/packages/ir-sdk/fern/apis/ir-types-latest/changelog/CHANGELOG.md index eca01131538..07d967467b0 100644 --- a/packages/ir-sdk/fern/apis/ir-types-latest/changelog/CHANGELOG.md +++ b/packages/ir-sdk/fern/apis/ir-types-latest/changelog/CHANGELOG.md @@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [v53.4.0] - 2024-08-05 \*\* (TODO: Make required in next major) + +- Feature: Add `User-Agent` header so that SDK generators can start sending the user agent. + +## [v53.3.0] - 2024-08-05 + +- Feature: Add gRPC/Protobuf types (defined in `proto.yml`) to generate gRPC/Protobuf mappers. + ## [v53.2.0] - 2024-07-30 - Improvement: The IR now contains an `extendedProperties` field where all properties from extended types are denormalized. This removes logic diff --git a/packages/ir-sdk/fern/apis/ir-types-latest/definition/api.yml b/packages/ir-sdk/fern/apis/ir-types-latest/definition/api.yml index 28b8e93f4e2..fad938d2e18 100644 --- a/packages/ir-sdk/fern/apis/ir-types-latest/definition/api.yml +++ b/packages/ir-sdk/fern/apis/ir-types-latest/definition/api.yml @@ -1,4 +1,5 @@ name: ir docs: | - Add `float` primitive type. - - Add a `PrimitiveTypeV2` variant for every `PrimitiveTypeV1`. \ No newline at end of file + - Add a `PrimitiveTypeV2` variant for every `PrimitiveTypeV1`. + - Add gRPC/Protobuf types. \ No newline at end of file diff --git a/packages/ir-sdk/fern/apis/ir-types-latest/definition/http.yml b/packages/ir-sdk/fern/apis/ir-types-latest/definition/http.yml index face7c87a71..148c422033d 100644 --- a/packages/ir-sdk/fern/apis/ir-types-latest/definition/http.yml +++ b/packages/ir-sdk/fern/apis/ir-types-latest/definition/http.yml @@ -2,9 +2,10 @@ imports: commons: commons.yml - types: types.yml - errors: errors.yml environment: environment.yml + errors: errors.yml + proto: proto.yml + types: types.yml variables: variables.yml types: HttpService: @@ -16,10 +17,21 @@ types: endpoints: list headers: list pathParameters: list + encoding: optional + transport: optional DeclaredServiceName: properties: fernFilepath: commons.FernFilepath + Transport: + union: + http: {} + grpc: GrpcTransport + + GrpcTransport: + properties: + service: proto.ProtobufService + HttpEndpoint: extends: commons.Declaration properties: diff --git a/packages/ir-sdk/fern/apis/ir-types-latest/definition/ir.yml b/packages/ir-sdk/fern/apis/ir-types-latest/definition/ir.yml index 3b74c4b43cc..adfd2d3dafb 100644 --- a/packages/ir-sdk/fern/apis/ir-types-latest/definition/ir.yml +++ b/packages/ir-sdk/fern/apis/ir-types-latest/definition/ir.yml @@ -1,13 +1,14 @@ # yaml-language-server: $schema=https://raw.githubusercontent.com/fern-api/fern/main/fern.schema.json imports: - types: types.yml - errors: errors.yml auth: auth.yml - http: http.yml + commons: commons.yml constants: constants.yml environment: environment.yml - commons: commons.yml + errors: errors.yml + http: http.yml + proto: proto.yml + types: types.yml variables: variables.yml webhooks: webhooks.yml websocket: websocket.yml @@ -55,6 +56,7 @@ types: variables: list serviceTypeReferenceInfo: ServiceTypeReferenceInfo readmeConfig: optional + sourceConfig: optional ReadmeConfig: docs: | The configuration used to generate a README.md file. If present, the generator @@ -74,6 +76,28 @@ types: If specified, configures the list of endpoints to associate with each feature. type: optional>> + SourceConfig: + properties: + sources: + docs: The raw API definitions that produced the IR. + type: list + ApiDefinitionSourceId: + docs: | + Uniquely identifies a specific API definition source. This allows us to clearly identify + what source a given type, endpoint, etc was derived from. + type: string + ApiDefinitionSource: + union: + proto: ProtoSource + openapi: {} + ProtoSource: + properties: + id: ApiDefinitionSourceId + protoRootUrl: + docs: | + The URL containing the `.proto` root directory source. This can be used + to pull down the original `.proto` source files during code generation. + type: string SdkConfig: properties: isAuthMandatory: boolean @@ -86,6 +110,16 @@ types: language: string sdkName: string sdkVersion: string + userAgent: optional + UserAgent: + properties: + header: + type: literal<"User-Agent"> + docs: The user agent header for ease of access to generators. + value: + type: string + docs: Formatted as "/" + ApiVersionScheme: docs: | The available set of versions for the API. This is used to generate a special diff --git a/packages/ir-sdk/fern/apis/ir-types-latest/definition/proto.yml b/packages/ir-sdk/fern/apis/ir-types-latest/definition/proto.yml new file mode 100644 index 00000000000..75f82571326 --- /dev/null +++ b/packages/ir-sdk/fern/apis/ir-types-latest/definition/proto.yml @@ -0,0 +1,156 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/fern-api/fern/main/fern.schema.json + +imports: + commons: commons.yml + +types: + ProtobufService: + docs: | + Defines the information related to a Protobuf service declaration. This is + primarily meant to be used to instantiate the internal gRPC client used by + the SDK. + + For example, consider the following C# snippet which instantiates a + `UserService` gRPC client: + + ```csharp + using User.Grpc; + + public class RawGrpcClient + { + public UserService.UserServiceClient UserServiceClient; + + public RawGrpcClient(...) + { + GrpcChannel channel = GrpcChannel.ForAddress(...); + UserServiceClient = new UserService.UserServiceClient(channel); + } + } + ``` + properties: + file: + docs: | + The `.proto` source file that defines this service. + type: ProtobufFile + name: + docs: | + The name of the service defined in the `.proto` file (e.g. UserService). + type: commons.Name + + ProtobufType: + docs: | + A Protobuf type declaration. + union: + wellKnown: WellKnownProtobufType + userDefined: UserDefinedProtobufType + + UserDefinedProtobufType: + docs: | + Defines the information related to the original `.proto` source file + that defines this type. This is primarily meant to be used to generate + Protobuf mapper methods, which are used in gRPC-compatbile SDKs. + + For example, consider the following Go snippet which requires the + `go_package` setting: + + ```go + import "github.com/acme/acme-go/proto" + + type GetUserRequest struct { + Username string + Email string + } + + func (u *GetUserRequest) ToProto() *proto.GetUserRequest { + if u == nil { + return nil + } + return &proto.GetUserRequest{ + Username u.Username, + Email: u.Email, + } + } + ``` + properties: + file: + docs: | + The `.proto` source file that defines this type. + type: ProtobufFile + name: + docs: | + This name is _usually_ equivalent to the associated DeclaredTypeName's name. + However, its repeated here just in case the naming convention differs, which + is most relevant for APIs that specify `smart-casing`. + type: commons.Name + + WellKnownProtobufType: + docs: | + The set of well-known types supported by Protobuf. These types are often included + in the target runtime library, so they usually require special handling. + + The full list of well-known types can be found at https://protobuf.dev/reference/protobuf/google.protobuf + union: + any: {} + api: {} + boolValue: {} + bytesValue: {} + doubleValue: {} + duration: {} + empty: {} + enum: {} + enumValue: {} + field: {} + fieldCardinality: {} + fieldKind: {} + fieldMask: {} + floatValue: {} + int32Value: {} + int64Value: {} + listValue: {} + method: {} + mixin: {} + nullValue: {} + option: {} + sourceContext: {} + stringValue: {} + struct: {} + syntax: {} + timestamp: {} + type: {} + uint32Value: {} + uint64Value: {} + value: {} + + ProtobufFile: + properties: + filepath: + docs: | + The `.proto` source path, relative to the Protobuf root directory. + This is how the file is referenced in `import` statements. + type: string + packageName: + docs: | + The `.proto` package name. If not specified, a package name was not declared. + type: optional + options: + docs: | + Specifies a variety of language-specific options. + type: optional + + ProtobufFileOptions: + properties: + csharp: optional + + CsharpProtobufFileOptions: + properties: + namespace: + docs: | + Populated by the `csharp_namespace` file option, e.g. + + ```protobuf + option csharp_namespace = Grpc.Health.V1; + ``` + + This is used to determine what import path is required to reference the + associated type(s). + type: string diff --git a/packages/ir-sdk/fern/apis/ir-types-latest/definition/types.yml b/packages/ir-sdk/fern/apis/ir-types-latest/definition/types.yml index ffc2063b43a..ee72acf4f56 100644 --- a/packages/ir-sdk/fern/apis/ir-types-latest/definition/types.yml +++ b/packages/ir-sdk/fern/apis/ir-types-latest/definition/types.yml @@ -2,7 +2,25 @@ imports: commons: commons.yml + proto: proto.yml types: + Source: + docs: | + The original source of the declared type (e.g. a `.proto` file). + union: + proto: proto.ProtobufType + + Encoding: + properties: + json: optional + proto: optional + + JsonEncoding: + properties: {} + + ProtoEncoding: + properties: {} + TypeDeclaration: docs: "A type, which is a name and a shape" extends: commons.Declaration @@ -14,6 +32,8 @@ types: referencedTypes: docs: All other named types that this type references (directly or indirectly) type: set + encoding: optional + source: optional DeclaredTypeName: properties: diff --git a/packages/ir-sdk/fern/apis/ir-types-latest/generators.yml b/packages/ir-sdk/fern/apis/ir-types-latest/generators.yml index 8038b2f98a9..c09be919d24 100644 --- a/packages/ir-sdk/fern/apis/ir-types-latest/generators.yml +++ b/packages/ir-sdk/fern/apis/ir-types-latest/generators.yml @@ -36,7 +36,7 @@ groups: python: generators: - name: fernapi/fern-pydantic-model - version: 1.1.0-rc0 + version: 1.3.1 output: location: pypi url: pypi.buildwithfern.com diff --git a/packages/ir-sdk/src/sdk/api/resources/http/types/GrpcTransport.ts b/packages/ir-sdk/src/sdk/api/resources/http/types/GrpcTransport.ts new file mode 100644 index 00000000000..c2a8a7e3a63 --- /dev/null +++ b/packages/ir-sdk/src/sdk/api/resources/http/types/GrpcTransport.ts @@ -0,0 +1,9 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as FernIr from "../../.."; + +export interface GrpcTransport { + service: FernIr.ProtobufService; +} diff --git a/packages/ir-sdk/src/sdk/api/resources/http/types/HttpService.ts b/packages/ir-sdk/src/sdk/api/resources/http/types/HttpService.ts index f1f7f58a90a..14d0e36a9f1 100644 --- a/packages/ir-sdk/src/sdk/api/resources/http/types/HttpService.ts +++ b/packages/ir-sdk/src/sdk/api/resources/http/types/HttpService.ts @@ -12,4 +12,6 @@ export interface HttpService { endpoints: FernIr.HttpEndpoint[]; headers: FernIr.HttpHeader[]; pathParameters: FernIr.PathParameter[]; + encoding: FernIr.Encoding | undefined; + transport: FernIr.Transport | undefined; } diff --git a/packages/ir-sdk/src/sdk/api/resources/http/types/Transport.ts b/packages/ir-sdk/src/sdk/api/resources/http/types/Transport.ts new file mode 100644 index 00000000000..e321455f7b5 --- /dev/null +++ b/packages/ir-sdk/src/sdk/api/resources/http/types/Transport.ts @@ -0,0 +1,59 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as FernIr from "../../.."; + +export type Transport = FernIr.Transport.Http | FernIr.Transport.Grpc; + +export declare namespace Transport { + interface Http extends _Utils { + type: "http"; + } + + interface Grpc extends FernIr.GrpcTransport, _Utils { + type: "grpc"; + } + + interface _Utils { + _visit: <_Result>(visitor: FernIr.Transport._Visitor<_Result>) => _Result; + } + + interface _Visitor<_Result> { + http: () => _Result; + grpc: (value: FernIr.GrpcTransport) => _Result; + _other: (value: { type: string }) => _Result; + } +} + +export const Transport = { + http: (): FernIr.Transport.Http => { + return { + type: "http", + _visit: function <_Result>(this: FernIr.Transport.Http, visitor: FernIr.Transport._Visitor<_Result>) { + return FernIr.Transport._visit(this, visitor); + }, + }; + }, + + grpc: (value: FernIr.GrpcTransport): FernIr.Transport.Grpc => { + return { + ...value, + type: "grpc", + _visit: function <_Result>(this: FernIr.Transport.Grpc, visitor: FernIr.Transport._Visitor<_Result>) { + return FernIr.Transport._visit(this, visitor); + }, + }; + }, + + _visit: <_Result>(value: FernIr.Transport, visitor: FernIr.Transport._Visitor<_Result>): _Result => { + switch (value.type) { + case "http": + return visitor.http(); + case "grpc": + return visitor.grpc(value); + default: + return visitor._other(value as any); + } + }, +} as const; diff --git a/packages/ir-sdk/src/sdk/api/resources/http/types/index.ts b/packages/ir-sdk/src/sdk/api/resources/http/types/index.ts index 08c74998454..da94e6505ec 100644 --- a/packages/ir-sdk/src/sdk/api/resources/http/types/index.ts +++ b/packages/ir-sdk/src/sdk/api/resources/http/types/index.ts @@ -1,5 +1,7 @@ export * from "./HttpService"; export * from "./DeclaredServiceName"; +export * from "./Transport"; +export * from "./GrpcTransport"; export * from "./HttpEndpoint"; export * from "./EndpointName"; export * from "./HttpPath"; diff --git a/packages/ir-sdk/src/sdk/api/resources/index.ts b/packages/ir-sdk/src/sdk/api/resources/index.ts index 477da62b3a3..7191fc2c3a5 100644 --- a/packages/ir-sdk/src/sdk/api/resources/index.ts +++ b/packages/ir-sdk/src/sdk/api/resources/index.ts @@ -12,6 +12,8 @@ export * as http from "./http"; export * from "./http/types"; export * as ir from "./ir"; export * from "./ir/types"; +export * as proto from "./proto"; +export * from "./proto/types"; export * as types from "./types"; export * from "./types/types"; export * as variables from "./variables"; diff --git a/packages/ir-sdk/src/sdk/api/resources/ir/types/ApiDefinitionSource.ts b/packages/ir-sdk/src/sdk/api/resources/ir/types/ApiDefinitionSource.ts new file mode 100644 index 00000000000..43a01ae3901 --- /dev/null +++ b/packages/ir-sdk/src/sdk/api/resources/ir/types/ApiDefinitionSource.ts @@ -0,0 +1,68 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as FernIr from "../../.."; + +export type ApiDefinitionSource = FernIr.ApiDefinitionSource.Proto | FernIr.ApiDefinitionSource.Openapi; + +export declare namespace ApiDefinitionSource { + interface Proto extends FernIr.ProtoSource, _Utils { + type: "proto"; + } + + interface Openapi extends _Utils { + type: "openapi"; + } + + interface _Utils { + _visit: <_Result>(visitor: FernIr.ApiDefinitionSource._Visitor<_Result>) => _Result; + } + + interface _Visitor<_Result> { + proto: (value: FernIr.ProtoSource) => _Result; + openapi: () => _Result; + _other: (value: { type: string }) => _Result; + } +} + +export const ApiDefinitionSource = { + proto: (value: FernIr.ProtoSource): FernIr.ApiDefinitionSource.Proto => { + return { + ...value, + type: "proto", + _visit: function <_Result>( + this: FernIr.ApiDefinitionSource.Proto, + visitor: FernIr.ApiDefinitionSource._Visitor<_Result> + ) { + return FernIr.ApiDefinitionSource._visit(this, visitor); + }, + }; + }, + + openapi: (): FernIr.ApiDefinitionSource.Openapi => { + return { + type: "openapi", + _visit: function <_Result>( + this: FernIr.ApiDefinitionSource.Openapi, + visitor: FernIr.ApiDefinitionSource._Visitor<_Result> + ) { + return FernIr.ApiDefinitionSource._visit(this, visitor); + }, + }; + }, + + _visit: <_Result>( + value: FernIr.ApiDefinitionSource, + visitor: FernIr.ApiDefinitionSource._Visitor<_Result> + ): _Result => { + switch (value.type) { + case "proto": + return visitor.proto(value); + case "openapi": + return visitor.openapi(); + default: + return visitor._other(value as any); + } + }, +} as const; diff --git a/packages/ir-sdk/src/sdk/api/resources/ir/types/ApiDefinitionSourceId.ts b/packages/ir-sdk/src/sdk/api/resources/ir/types/ApiDefinitionSourceId.ts new file mode 100644 index 00000000000..df9c1627271 --- /dev/null +++ b/packages/ir-sdk/src/sdk/api/resources/ir/types/ApiDefinitionSourceId.ts @@ -0,0 +1,9 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +/** + * Uniquely identifies a specific API definition source. This allows us to clearly identify + * what source a given type, endpoint, etc was derived from. + */ +export type ApiDefinitionSourceId = string; diff --git a/packages/ir-sdk/src/sdk/api/resources/ir/types/IntermediateRepresentation.ts b/packages/ir-sdk/src/sdk/api/resources/ir/types/IntermediateRepresentation.ts index 661102029b4..f8728950c3e 100644 --- a/packages/ir-sdk/src/sdk/api/resources/ir/types/IntermediateRepresentation.ts +++ b/packages/ir-sdk/src/sdk/api/resources/ir/types/IntermediateRepresentation.ts @@ -40,4 +40,5 @@ export interface IntermediateRepresentation { variables: FernIr.VariableDeclaration[]; serviceTypeReferenceInfo: FernIr.ServiceTypeReferenceInfo; readmeConfig: FernIr.ReadmeConfig | undefined; + sourceConfig: FernIr.SourceConfig | undefined; } diff --git a/packages/ir-sdk/src/sdk/api/resources/ir/types/PlatformHeaders.ts b/packages/ir-sdk/src/sdk/api/resources/ir/types/PlatformHeaders.ts index 9a1e1ee5c57..d0d8a1dd503 100644 --- a/packages/ir-sdk/src/sdk/api/resources/ir/types/PlatformHeaders.ts +++ b/packages/ir-sdk/src/sdk/api/resources/ir/types/PlatformHeaders.ts @@ -2,8 +2,11 @@ * This file was auto-generated by Fern from our API Definition. */ +import * as FernIr from "../../.."; + export interface PlatformHeaders { language: string; sdkName: string; sdkVersion: string; + userAgent: FernIr.UserAgent | undefined; } diff --git a/packages/ir-sdk/src/sdk/api/resources/ir/types/ProtoSource.ts b/packages/ir-sdk/src/sdk/api/resources/ir/types/ProtoSource.ts new file mode 100644 index 00000000000..c7fc38d8582 --- /dev/null +++ b/packages/ir-sdk/src/sdk/api/resources/ir/types/ProtoSource.ts @@ -0,0 +1,14 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as FernIr from "../../.."; + +export interface ProtoSource { + id: FernIr.ApiDefinitionSourceId; + /** + * The URL containing the `.proto` root directory source. This can be used + * to pull down the original `.proto` source files during code generation. + */ + protoRootUrl: string; +} diff --git a/packages/ir-sdk/src/sdk/api/resources/ir/types/SourceConfig.ts b/packages/ir-sdk/src/sdk/api/resources/ir/types/SourceConfig.ts new file mode 100644 index 00000000000..2f04ff1ee7a --- /dev/null +++ b/packages/ir-sdk/src/sdk/api/resources/ir/types/SourceConfig.ts @@ -0,0 +1,10 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as FernIr from "../../.."; + +export interface SourceConfig { + /** The raw API definitions that produced the IR. */ + sources: FernIr.ApiDefinitionSource[]; +} diff --git a/packages/ir-sdk/src/sdk/api/resources/ir/types/UserAgent.ts b/packages/ir-sdk/src/sdk/api/resources/ir/types/UserAgent.ts new file mode 100644 index 00000000000..7f5950af0a1 --- /dev/null +++ b/packages/ir-sdk/src/sdk/api/resources/ir/types/UserAgent.ts @@ -0,0 +1,10 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +export interface UserAgent { + /** The user agent header for ease of access to generators. */ + header: "User-Agent"; + /** Formatted as "/" */ + value: string; +} diff --git a/packages/ir-sdk/src/sdk/api/resources/ir/types/index.ts b/packages/ir-sdk/src/sdk/api/resources/ir/types/index.ts index 3db624ee879..17947bff65f 100644 --- a/packages/ir-sdk/src/sdk/api/resources/ir/types/index.ts +++ b/packages/ir-sdk/src/sdk/api/resources/ir/types/index.ts @@ -1,7 +1,12 @@ export * from "./IntermediateRepresentation"; export * from "./ReadmeConfig"; +export * from "./SourceConfig"; +export * from "./ApiDefinitionSourceId"; +export * from "./ApiDefinitionSource"; +export * from "./ProtoSource"; export * from "./SdkConfig"; export * from "./PlatformHeaders"; +export * from "./UserAgent"; export * from "./ApiVersionScheme"; export * from "./HeaderApiVersionScheme"; export * from "./ErrorDiscriminationStrategy"; diff --git a/packages/ir-sdk/src/sdk/api/resources/proto/index.ts b/packages/ir-sdk/src/sdk/api/resources/proto/index.ts new file mode 100644 index 00000000000..eea524d6557 --- /dev/null +++ b/packages/ir-sdk/src/sdk/api/resources/proto/index.ts @@ -0,0 +1 @@ +export * from "./types"; diff --git a/packages/ir-sdk/src/sdk/api/resources/proto/types/CsharpProtobufFileOptions.ts b/packages/ir-sdk/src/sdk/api/resources/proto/types/CsharpProtobufFileOptions.ts new file mode 100644 index 00000000000..a7e4fda88ab --- /dev/null +++ b/packages/ir-sdk/src/sdk/api/resources/proto/types/CsharpProtobufFileOptions.ts @@ -0,0 +1,17 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +export interface CsharpProtobufFileOptions { + /** + * Populated by the `csharp_namespace` file option, e.g. + * + * ```protobuf + * option csharp_namespace = Grpc.Health.V1; + * ``` + * + * This is used to determine what import path is required to reference the + * associated type(s). + */ + namespace: string; +} diff --git a/packages/ir-sdk/src/sdk/api/resources/proto/types/ProtobufFile.ts b/packages/ir-sdk/src/sdk/api/resources/proto/types/ProtobufFile.ts new file mode 100644 index 00000000000..12e85978e44 --- /dev/null +++ b/packages/ir-sdk/src/sdk/api/resources/proto/types/ProtobufFile.ts @@ -0,0 +1,17 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as FernIr from "../../.."; + +export interface ProtobufFile { + /** + * The `.proto` source path, relative to the Protobuf root directory. + * This is how the file is referenced in `import` statements. + */ + filepath: string; + /** The `.proto` package name. If not specified, a package name was not declared. */ + packageName: string | undefined; + /** Specifies a variety of language-specific options. */ + options: FernIr.ProtobufFileOptions | undefined; +} diff --git a/packages/ir-sdk/src/sdk/api/resources/proto/types/ProtobufFileOptions.ts b/packages/ir-sdk/src/sdk/api/resources/proto/types/ProtobufFileOptions.ts new file mode 100644 index 00000000000..063a3b461fe --- /dev/null +++ b/packages/ir-sdk/src/sdk/api/resources/proto/types/ProtobufFileOptions.ts @@ -0,0 +1,9 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as FernIr from "../../.."; + +export interface ProtobufFileOptions { + csharp: FernIr.CsharpProtobufFileOptions | undefined; +} diff --git a/packages/ir-sdk/src/sdk/api/resources/proto/types/ProtobufService.ts b/packages/ir-sdk/src/sdk/api/resources/proto/types/ProtobufService.ts new file mode 100644 index 00000000000..faa5dd2c686 --- /dev/null +++ b/packages/ir-sdk/src/sdk/api/resources/proto/types/ProtobufService.ts @@ -0,0 +1,35 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as FernIr from "../../.."; + +/** + * Defines the information related to a Protobuf service declaration. This is + * primarily meant to be used to instantiate the internal gRPC client used by + * the SDK. + * + * For example, consider the following C# snippet which instantiates a + * `UserService` gRPC client: + * + * ```csharp + * using User.Grpc; + * + * public class RawGrpcClient + * { + * public UserService.UserServiceClient UserServiceClient; + * + * public RawGrpcClient(...) + * { + * GrpcChannel channel = GrpcChannel.ForAddress(...); + * UserServiceClient = new UserService.UserServiceClient(channel); + * } + * } + * ``` + */ +export interface ProtobufService { + /** The `.proto` source file that defines this service. */ + file: FernIr.ProtobufFile; + /** The name of the service defined in the `.proto` file (e.g. UserService). */ + name: FernIr.Name; +} diff --git a/packages/ir-sdk/src/sdk/api/resources/proto/types/ProtobufType.ts b/packages/ir-sdk/src/sdk/api/resources/proto/types/ProtobufType.ts new file mode 100644 index 00000000000..39e5ffbc49a --- /dev/null +++ b/packages/ir-sdk/src/sdk/api/resources/proto/types/ProtobufType.ts @@ -0,0 +1,70 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as FernIr from "../../.."; + +/** + * A Protobuf type declaration. + */ +export type ProtobufType = FernIr.ProtobufType.WellKnown | FernIr.ProtobufType.UserDefined; + +export declare namespace ProtobufType { + interface WellKnown extends _Utils { + type: "wellKnown"; + value: FernIr.WellKnownProtobufType; + } + + interface UserDefined extends FernIr.UserDefinedProtobufType, _Utils { + type: "userDefined"; + } + + interface _Utils { + _visit: <_Result>(visitor: FernIr.ProtobufType._Visitor<_Result>) => _Result; + } + + interface _Visitor<_Result> { + wellKnown: (value: FernIr.WellKnownProtobufType) => _Result; + userDefined: (value: FernIr.UserDefinedProtobufType) => _Result; + _other: (value: { type: string }) => _Result; + } +} + +export const ProtobufType = { + wellKnown: (value: FernIr.WellKnownProtobufType): FernIr.ProtobufType.WellKnown => { + return { + value: value, + type: "wellKnown", + _visit: function <_Result>( + this: FernIr.ProtobufType.WellKnown, + visitor: FernIr.ProtobufType._Visitor<_Result> + ) { + return FernIr.ProtobufType._visit(this, visitor); + }, + }; + }, + + userDefined: (value: FernIr.UserDefinedProtobufType): FernIr.ProtobufType.UserDefined => { + return { + ...value, + type: "userDefined", + _visit: function <_Result>( + this: FernIr.ProtobufType.UserDefined, + visitor: FernIr.ProtobufType._Visitor<_Result> + ) { + return FernIr.ProtobufType._visit(this, visitor); + }, + }; + }, + + _visit: <_Result>(value: FernIr.ProtobufType, visitor: FernIr.ProtobufType._Visitor<_Result>): _Result => { + switch (value.type) { + case "wellKnown": + return visitor.wellKnown(value.value); + case "userDefined": + return visitor.userDefined(value); + default: + return visitor._other(value as any); + } + }, +} as const; diff --git a/packages/ir-sdk/src/sdk/api/resources/proto/types/UserDefinedProtobufType.ts b/packages/ir-sdk/src/sdk/api/resources/proto/types/UserDefinedProtobufType.ts new file mode 100644 index 00000000000..a0ab29acdd5 --- /dev/null +++ b/packages/ir-sdk/src/sdk/api/resources/proto/types/UserDefinedProtobufType.ts @@ -0,0 +1,43 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as FernIr from "../../.."; + +/** + * Defines the information related to the original `.proto` source file + * that defines this type. This is primarily meant to be used to generate + * Protobuf mapper methods, which are used in gRPC-compatbile SDKs. + * + * For example, consider the following Go snippet which requires the + * `go_package` setting: + * + * ```go + * import "github.com/acme/acme-go/proto" + * + * type GetUserRequest struct { + * Username string + * Email string + * } + * + * func (u *GetUserRequest) ToProto() *proto.GetUserRequest { + * if u == nil { + * return nil + * } + * return &proto.GetUserRequest{ + * Username u.Username, + * Email: u.Email, + * } + * } + * ``` + */ +export interface UserDefinedProtobufType { + /** The `.proto` source file that defines this type. */ + file: FernIr.ProtobufFile; + /** + * This name is _usually_ equivalent to the associated DeclaredTypeName's name. + * However, its repeated here just in case the naming convention differs, which + * is most relevant for APIs that specify `smart-casing`. + */ + name: FernIr.Name; +} diff --git a/packages/ir-sdk/src/sdk/api/resources/proto/types/WellKnownProtobufType.ts b/packages/ir-sdk/src/sdk/api/resources/proto/types/WellKnownProtobufType.ts new file mode 100644 index 00000000000..ffa6372f34f --- /dev/null +++ b/packages/ir-sdk/src/sdk/api/resources/proto/types/WellKnownProtobufType.ts @@ -0,0 +1,635 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as FernIr from "../../.."; + +/** + * The set of well-known types supported by Protobuf. These types are often included + * in the target runtime library, so they usually require special handling. + * + * The full list of well-known types can be found at https://protobuf.dev/reference/protobuf/google.protobuf + */ +export type WellKnownProtobufType = + | FernIr.WellKnownProtobufType.Any + | FernIr.WellKnownProtobufType.Api + | FernIr.WellKnownProtobufType.BoolValue + | FernIr.WellKnownProtobufType.BytesValue + | FernIr.WellKnownProtobufType.DoubleValue + | FernIr.WellKnownProtobufType.Duration + | FernIr.WellKnownProtobufType.Empty + | FernIr.WellKnownProtobufType.Enum + | FernIr.WellKnownProtobufType.EnumValue + | FernIr.WellKnownProtobufType.Field + | FernIr.WellKnownProtobufType.FieldCardinality + | FernIr.WellKnownProtobufType.FieldKind + | FernIr.WellKnownProtobufType.FieldMask + | FernIr.WellKnownProtobufType.FloatValue + | FernIr.WellKnownProtobufType.Int32Value + | FernIr.WellKnownProtobufType.Int64Value + | FernIr.WellKnownProtobufType.ListValue + | FernIr.WellKnownProtobufType.Method + | FernIr.WellKnownProtobufType.Mixin + | FernIr.WellKnownProtobufType.NullValue + | FernIr.WellKnownProtobufType.Option + | FernIr.WellKnownProtobufType.SourceContext + | FernIr.WellKnownProtobufType.StringValue + | FernIr.WellKnownProtobufType.Struct + | FernIr.WellKnownProtobufType.Syntax + | FernIr.WellKnownProtobufType.Timestamp + | FernIr.WellKnownProtobufType.Type + | FernIr.WellKnownProtobufType.Uint32Value + | FernIr.WellKnownProtobufType.Uint64Value + | FernIr.WellKnownProtobufType.Value; + +export declare namespace WellKnownProtobufType { + interface Any extends _Utils { + type: "any"; + } + + interface Api extends _Utils { + type: "api"; + } + + interface BoolValue extends _Utils { + type: "boolValue"; + } + + interface BytesValue extends _Utils { + type: "bytesValue"; + } + + interface DoubleValue extends _Utils { + type: "doubleValue"; + } + + interface Duration extends _Utils { + type: "duration"; + } + + interface Empty extends _Utils { + type: "empty"; + } + + interface Enum extends _Utils { + type: "enum"; + } + + interface EnumValue extends _Utils { + type: "enumValue"; + } + + interface Field extends _Utils { + type: "field"; + } + + interface FieldCardinality extends _Utils { + type: "fieldCardinality"; + } + + interface FieldKind extends _Utils { + type: "fieldKind"; + } + + interface FieldMask extends _Utils { + type: "fieldMask"; + } + + interface FloatValue extends _Utils { + type: "floatValue"; + } + + interface Int32Value extends _Utils { + type: "int32Value"; + } + + interface Int64Value extends _Utils { + type: "int64Value"; + } + + interface ListValue extends _Utils { + type: "listValue"; + } + + interface Method extends _Utils { + type: "method"; + } + + interface Mixin extends _Utils { + type: "mixin"; + } + + interface NullValue extends _Utils { + type: "nullValue"; + } + + interface Option extends _Utils { + type: "option"; + } + + interface SourceContext extends _Utils { + type: "sourceContext"; + } + + interface StringValue extends _Utils { + type: "stringValue"; + } + + interface Struct extends _Utils { + type: "struct"; + } + + interface Syntax extends _Utils { + type: "syntax"; + } + + interface Timestamp extends _Utils { + type: "timestamp"; + } + + interface Type extends _Utils { + type: "type"; + } + + interface Uint32Value extends _Utils { + type: "uint32Value"; + } + + interface Uint64Value extends _Utils { + type: "uint64Value"; + } + + interface Value extends _Utils { + type: "value"; + } + + interface _Utils { + _visit: <_Result>(visitor: FernIr.WellKnownProtobufType._Visitor<_Result>) => _Result; + } + + interface _Visitor<_Result> { + any: () => _Result; + api: () => _Result; + boolValue: () => _Result; + bytesValue: () => _Result; + doubleValue: () => _Result; + duration: () => _Result; + empty: () => _Result; + enum: () => _Result; + enumValue: () => _Result; + field: () => _Result; + fieldCardinality: () => _Result; + fieldKind: () => _Result; + fieldMask: () => _Result; + floatValue: () => _Result; + int32Value: () => _Result; + int64Value: () => _Result; + listValue: () => _Result; + method: () => _Result; + mixin: () => _Result; + nullValue: () => _Result; + option: () => _Result; + sourceContext: () => _Result; + stringValue: () => _Result; + struct: () => _Result; + syntax: () => _Result; + timestamp: () => _Result; + type: () => _Result; + uint32Value: () => _Result; + uint64Value: () => _Result; + value: () => _Result; + _other: (value: { type: string }) => _Result; + } +} + +export const WellKnownProtobufType = { + any: (): FernIr.WellKnownProtobufType.Any => { + return { + type: "any", + _visit: function <_Result>( + this: FernIr.WellKnownProtobufType.Any, + visitor: FernIr.WellKnownProtobufType._Visitor<_Result> + ) { + return FernIr.WellKnownProtobufType._visit(this, visitor); + }, + }; + }, + + api: (): FernIr.WellKnownProtobufType.Api => { + return { + type: "api", + _visit: function <_Result>( + this: FernIr.WellKnownProtobufType.Api, + visitor: FernIr.WellKnownProtobufType._Visitor<_Result> + ) { + return FernIr.WellKnownProtobufType._visit(this, visitor); + }, + }; + }, + + boolValue: (): FernIr.WellKnownProtobufType.BoolValue => { + return { + type: "boolValue", + _visit: function <_Result>( + this: FernIr.WellKnownProtobufType.BoolValue, + visitor: FernIr.WellKnownProtobufType._Visitor<_Result> + ) { + return FernIr.WellKnownProtobufType._visit(this, visitor); + }, + }; + }, + + bytesValue: (): FernIr.WellKnownProtobufType.BytesValue => { + return { + type: "bytesValue", + _visit: function <_Result>( + this: FernIr.WellKnownProtobufType.BytesValue, + visitor: FernIr.WellKnownProtobufType._Visitor<_Result> + ) { + return FernIr.WellKnownProtobufType._visit(this, visitor); + }, + }; + }, + + doubleValue: (): FernIr.WellKnownProtobufType.DoubleValue => { + return { + type: "doubleValue", + _visit: function <_Result>( + this: FernIr.WellKnownProtobufType.DoubleValue, + visitor: FernIr.WellKnownProtobufType._Visitor<_Result> + ) { + return FernIr.WellKnownProtobufType._visit(this, visitor); + }, + }; + }, + + duration: (): FernIr.WellKnownProtobufType.Duration => { + return { + type: "duration", + _visit: function <_Result>( + this: FernIr.WellKnownProtobufType.Duration, + visitor: FernIr.WellKnownProtobufType._Visitor<_Result> + ) { + return FernIr.WellKnownProtobufType._visit(this, visitor); + }, + }; + }, + + empty: (): FernIr.WellKnownProtobufType.Empty => { + return { + type: "empty", + _visit: function <_Result>( + this: FernIr.WellKnownProtobufType.Empty, + visitor: FernIr.WellKnownProtobufType._Visitor<_Result> + ) { + return FernIr.WellKnownProtobufType._visit(this, visitor); + }, + }; + }, + + enum: (): FernIr.WellKnownProtobufType.Enum => { + return { + type: "enum", + _visit: function <_Result>( + this: FernIr.WellKnownProtobufType.Enum, + visitor: FernIr.WellKnownProtobufType._Visitor<_Result> + ) { + return FernIr.WellKnownProtobufType._visit(this, visitor); + }, + }; + }, + + enumValue: (): FernIr.WellKnownProtobufType.EnumValue => { + return { + type: "enumValue", + _visit: function <_Result>( + this: FernIr.WellKnownProtobufType.EnumValue, + visitor: FernIr.WellKnownProtobufType._Visitor<_Result> + ) { + return FernIr.WellKnownProtobufType._visit(this, visitor); + }, + }; + }, + + field: (): FernIr.WellKnownProtobufType.Field => { + return { + type: "field", + _visit: function <_Result>( + this: FernIr.WellKnownProtobufType.Field, + visitor: FernIr.WellKnownProtobufType._Visitor<_Result> + ) { + return FernIr.WellKnownProtobufType._visit(this, visitor); + }, + }; + }, + + fieldCardinality: (): FernIr.WellKnownProtobufType.FieldCardinality => { + return { + type: "fieldCardinality", + _visit: function <_Result>( + this: FernIr.WellKnownProtobufType.FieldCardinality, + visitor: FernIr.WellKnownProtobufType._Visitor<_Result> + ) { + return FernIr.WellKnownProtobufType._visit(this, visitor); + }, + }; + }, + + fieldKind: (): FernIr.WellKnownProtobufType.FieldKind => { + return { + type: "fieldKind", + _visit: function <_Result>( + this: FernIr.WellKnownProtobufType.FieldKind, + visitor: FernIr.WellKnownProtobufType._Visitor<_Result> + ) { + return FernIr.WellKnownProtobufType._visit(this, visitor); + }, + }; + }, + + fieldMask: (): FernIr.WellKnownProtobufType.FieldMask => { + return { + type: "fieldMask", + _visit: function <_Result>( + this: FernIr.WellKnownProtobufType.FieldMask, + visitor: FernIr.WellKnownProtobufType._Visitor<_Result> + ) { + return FernIr.WellKnownProtobufType._visit(this, visitor); + }, + }; + }, + + floatValue: (): FernIr.WellKnownProtobufType.FloatValue => { + return { + type: "floatValue", + _visit: function <_Result>( + this: FernIr.WellKnownProtobufType.FloatValue, + visitor: FernIr.WellKnownProtobufType._Visitor<_Result> + ) { + return FernIr.WellKnownProtobufType._visit(this, visitor); + }, + }; + }, + + int32Value: (): FernIr.WellKnownProtobufType.Int32Value => { + return { + type: "int32Value", + _visit: function <_Result>( + this: FernIr.WellKnownProtobufType.Int32Value, + visitor: FernIr.WellKnownProtobufType._Visitor<_Result> + ) { + return FernIr.WellKnownProtobufType._visit(this, visitor); + }, + }; + }, + + int64Value: (): FernIr.WellKnownProtobufType.Int64Value => { + return { + type: "int64Value", + _visit: function <_Result>( + this: FernIr.WellKnownProtobufType.Int64Value, + visitor: FernIr.WellKnownProtobufType._Visitor<_Result> + ) { + return FernIr.WellKnownProtobufType._visit(this, visitor); + }, + }; + }, + + listValue: (): FernIr.WellKnownProtobufType.ListValue => { + return { + type: "listValue", + _visit: function <_Result>( + this: FernIr.WellKnownProtobufType.ListValue, + visitor: FernIr.WellKnownProtobufType._Visitor<_Result> + ) { + return FernIr.WellKnownProtobufType._visit(this, visitor); + }, + }; + }, + + method: (): FernIr.WellKnownProtobufType.Method => { + return { + type: "method", + _visit: function <_Result>( + this: FernIr.WellKnownProtobufType.Method, + visitor: FernIr.WellKnownProtobufType._Visitor<_Result> + ) { + return FernIr.WellKnownProtobufType._visit(this, visitor); + }, + }; + }, + + mixin: (): FernIr.WellKnownProtobufType.Mixin => { + return { + type: "mixin", + _visit: function <_Result>( + this: FernIr.WellKnownProtobufType.Mixin, + visitor: FernIr.WellKnownProtobufType._Visitor<_Result> + ) { + return FernIr.WellKnownProtobufType._visit(this, visitor); + }, + }; + }, + + nullValue: (): FernIr.WellKnownProtobufType.NullValue => { + return { + type: "nullValue", + _visit: function <_Result>( + this: FernIr.WellKnownProtobufType.NullValue, + visitor: FernIr.WellKnownProtobufType._Visitor<_Result> + ) { + return FernIr.WellKnownProtobufType._visit(this, visitor); + }, + }; + }, + + option: (): FernIr.WellKnownProtobufType.Option => { + return { + type: "option", + _visit: function <_Result>( + this: FernIr.WellKnownProtobufType.Option, + visitor: FernIr.WellKnownProtobufType._Visitor<_Result> + ) { + return FernIr.WellKnownProtobufType._visit(this, visitor); + }, + }; + }, + + sourceContext: (): FernIr.WellKnownProtobufType.SourceContext => { + return { + type: "sourceContext", + _visit: function <_Result>( + this: FernIr.WellKnownProtobufType.SourceContext, + visitor: FernIr.WellKnownProtobufType._Visitor<_Result> + ) { + return FernIr.WellKnownProtobufType._visit(this, visitor); + }, + }; + }, + + stringValue: (): FernIr.WellKnownProtobufType.StringValue => { + return { + type: "stringValue", + _visit: function <_Result>( + this: FernIr.WellKnownProtobufType.StringValue, + visitor: FernIr.WellKnownProtobufType._Visitor<_Result> + ) { + return FernIr.WellKnownProtobufType._visit(this, visitor); + }, + }; + }, + + struct: (): FernIr.WellKnownProtobufType.Struct => { + return { + type: "struct", + _visit: function <_Result>( + this: FernIr.WellKnownProtobufType.Struct, + visitor: FernIr.WellKnownProtobufType._Visitor<_Result> + ) { + return FernIr.WellKnownProtobufType._visit(this, visitor); + }, + }; + }, + + syntax: (): FernIr.WellKnownProtobufType.Syntax => { + return { + type: "syntax", + _visit: function <_Result>( + this: FernIr.WellKnownProtobufType.Syntax, + visitor: FernIr.WellKnownProtobufType._Visitor<_Result> + ) { + return FernIr.WellKnownProtobufType._visit(this, visitor); + }, + }; + }, + + timestamp: (): FernIr.WellKnownProtobufType.Timestamp => { + return { + type: "timestamp", + _visit: function <_Result>( + this: FernIr.WellKnownProtobufType.Timestamp, + visitor: FernIr.WellKnownProtobufType._Visitor<_Result> + ) { + return FernIr.WellKnownProtobufType._visit(this, visitor); + }, + }; + }, + + type: (): FernIr.WellKnownProtobufType.Type => { + return { + type: "type", + _visit: function <_Result>( + this: FernIr.WellKnownProtobufType.Type, + visitor: FernIr.WellKnownProtobufType._Visitor<_Result> + ) { + return FernIr.WellKnownProtobufType._visit(this, visitor); + }, + }; + }, + + uint32Value: (): FernIr.WellKnownProtobufType.Uint32Value => { + return { + type: "uint32Value", + _visit: function <_Result>( + this: FernIr.WellKnownProtobufType.Uint32Value, + visitor: FernIr.WellKnownProtobufType._Visitor<_Result> + ) { + return FernIr.WellKnownProtobufType._visit(this, visitor); + }, + }; + }, + + uint64Value: (): FernIr.WellKnownProtobufType.Uint64Value => { + return { + type: "uint64Value", + _visit: function <_Result>( + this: FernIr.WellKnownProtobufType.Uint64Value, + visitor: FernIr.WellKnownProtobufType._Visitor<_Result> + ) { + return FernIr.WellKnownProtobufType._visit(this, visitor); + }, + }; + }, + + value: (): FernIr.WellKnownProtobufType.Value => { + return { + type: "value", + _visit: function <_Result>( + this: FernIr.WellKnownProtobufType.Value, + visitor: FernIr.WellKnownProtobufType._Visitor<_Result> + ) { + return FernIr.WellKnownProtobufType._visit(this, visitor); + }, + }; + }, + + _visit: <_Result>( + value: FernIr.WellKnownProtobufType, + visitor: FernIr.WellKnownProtobufType._Visitor<_Result> + ): _Result => { + switch (value.type) { + case "any": + return visitor.any(); + case "api": + return visitor.api(); + case "boolValue": + return visitor.boolValue(); + case "bytesValue": + return visitor.bytesValue(); + case "doubleValue": + return visitor.doubleValue(); + case "duration": + return visitor.duration(); + case "empty": + return visitor.empty(); + case "enum": + return visitor.enum(); + case "enumValue": + return visitor.enumValue(); + case "field": + return visitor.field(); + case "fieldCardinality": + return visitor.fieldCardinality(); + case "fieldKind": + return visitor.fieldKind(); + case "fieldMask": + return visitor.fieldMask(); + case "floatValue": + return visitor.floatValue(); + case "int32Value": + return visitor.int32Value(); + case "int64Value": + return visitor.int64Value(); + case "listValue": + return visitor.listValue(); + case "method": + return visitor.method(); + case "mixin": + return visitor.mixin(); + case "nullValue": + return visitor.nullValue(); + case "option": + return visitor.option(); + case "sourceContext": + return visitor.sourceContext(); + case "stringValue": + return visitor.stringValue(); + case "struct": + return visitor.struct(); + case "syntax": + return visitor.syntax(); + case "timestamp": + return visitor.timestamp(); + case "type": + return visitor.type(); + case "uint32Value": + return visitor.uint32Value(); + case "uint64Value": + return visitor.uint64Value(); + case "value": + return visitor.value(); + default: + return visitor._other(value as any); + } + }, +} as const; diff --git a/packages/ir-sdk/src/sdk/api/resources/proto/types/index.ts b/packages/ir-sdk/src/sdk/api/resources/proto/types/index.ts new file mode 100644 index 00000000000..87df1112594 --- /dev/null +++ b/packages/ir-sdk/src/sdk/api/resources/proto/types/index.ts @@ -0,0 +1,7 @@ +export * from "./ProtobufService"; +export * from "./ProtobufType"; +export * from "./UserDefinedProtobufType"; +export * from "./WellKnownProtobufType"; +export * from "./ProtobufFile"; +export * from "./ProtobufFileOptions"; +export * from "./CsharpProtobufFileOptions"; diff --git a/packages/ir-sdk/src/sdk/api/resources/types/types/Encoding.ts b/packages/ir-sdk/src/sdk/api/resources/types/types/Encoding.ts new file mode 100644 index 00000000000..e153dcde0ee --- /dev/null +++ b/packages/ir-sdk/src/sdk/api/resources/types/types/Encoding.ts @@ -0,0 +1,10 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as FernIr from "../../.."; + +export interface Encoding { + json: FernIr.JsonEncoding | undefined; + proto: FernIr.ProtoEncoding | undefined; +} diff --git a/packages/ir-sdk/src/sdk/api/resources/types/types/JsonEncoding.ts b/packages/ir-sdk/src/sdk/api/resources/types/types/JsonEncoding.ts new file mode 100644 index 00000000000..ea19ceefc27 --- /dev/null +++ b/packages/ir-sdk/src/sdk/api/resources/types/types/JsonEncoding.ts @@ -0,0 +1,5 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +export interface JsonEncoding {} diff --git a/packages/ir-sdk/src/sdk/api/resources/types/types/ProtoEncoding.ts b/packages/ir-sdk/src/sdk/api/resources/types/types/ProtoEncoding.ts new file mode 100644 index 00000000000..163963c255e --- /dev/null +++ b/packages/ir-sdk/src/sdk/api/resources/types/types/ProtoEncoding.ts @@ -0,0 +1,5 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +export interface ProtoEncoding {} diff --git a/packages/ir-sdk/src/sdk/api/resources/types/types/Source.ts b/packages/ir-sdk/src/sdk/api/resources/types/types/Source.ts new file mode 100644 index 00000000000..c81cf639476 --- /dev/null +++ b/packages/ir-sdk/src/sdk/api/resources/types/types/Source.ts @@ -0,0 +1,47 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as FernIr from "../../.."; + +/** + * The original source of the declared type (e.g. a `.proto` file). + */ +export type Source = FernIr.Source.Proto; + +export declare namespace Source { + interface Proto extends _Utils { + type: "proto"; + value: FernIr.ProtobufType; + } + + interface _Utils { + _visit: <_Result>(visitor: FernIr.Source._Visitor<_Result>) => _Result; + } + + interface _Visitor<_Result> { + proto: (value: FernIr.ProtobufType) => _Result; + _other: (value: { type: string }) => _Result; + } +} + +export const Source = { + proto: (value: FernIr.ProtobufType): FernIr.Source.Proto => { + return { + value: value, + type: "proto", + _visit: function <_Result>(this: FernIr.Source.Proto, visitor: FernIr.Source._Visitor<_Result>) { + return FernIr.Source._visit(this, visitor); + }, + }; + }, + + _visit: <_Result>(value: FernIr.Source, visitor: FernIr.Source._Visitor<_Result>): _Result => { + switch (value.type) { + case "proto": + return visitor.proto(value.value); + default: + return visitor._other(value as any); + } + }, +} as const; diff --git a/packages/ir-sdk/src/sdk/api/resources/types/types/TypeDeclaration.ts b/packages/ir-sdk/src/sdk/api/resources/types/types/TypeDeclaration.ts index 183f21cd816..5e8bb46c4de 100644 --- a/packages/ir-sdk/src/sdk/api/resources/types/types/TypeDeclaration.ts +++ b/packages/ir-sdk/src/sdk/api/resources/types/types/TypeDeclaration.ts @@ -14,4 +14,6 @@ export interface TypeDeclaration extends FernIr.Declaration { userProvidedExamples: FernIr.ExampleType[]; /** All other named types that this type references (directly or indirectly) */ referencedTypes: Set; + encoding: FernIr.Encoding | undefined; + source: FernIr.Source | undefined; } diff --git a/packages/ir-sdk/src/sdk/api/resources/types/types/index.ts b/packages/ir-sdk/src/sdk/api/resources/types/types/index.ts index d39ad129787..65c44137295 100644 --- a/packages/ir-sdk/src/sdk/api/resources/types/types/index.ts +++ b/packages/ir-sdk/src/sdk/api/resources/types/types/index.ts @@ -1,3 +1,7 @@ +export * from "./Source"; +export * from "./Encoding"; +export * from "./JsonEncoding"; +export * from "./ProtoEncoding"; export * from "./TypeDeclaration"; export * from "./DeclaredTypeName"; export * from "./Type"; diff --git a/packages/ir-sdk/src/sdk/serialization/resources/http/types/GrpcTransport.ts b/packages/ir-sdk/src/sdk/serialization/resources/http/types/GrpcTransport.ts new file mode 100644 index 00000000000..2d7ee935c45 --- /dev/null +++ b/packages/ir-sdk/src/sdk/serialization/resources/http/types/GrpcTransport.ts @@ -0,0 +1,18 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../.."; +import * as FernIr from "../../../../api"; +import * as core from "../../../../core"; + +export const GrpcTransport: core.serialization.ObjectSchema = + core.serialization.objectWithoutOptionalProperties({ + service: core.serialization.lazyObject(async () => (await import("../../..")).ProtobufService), + }); + +export declare namespace GrpcTransport { + interface Raw { + service: serializers.ProtobufService.Raw; + } +} diff --git a/packages/ir-sdk/src/sdk/serialization/resources/http/types/HttpService.ts b/packages/ir-sdk/src/sdk/serialization/resources/http/types/HttpService.ts index e4b77a7f881..06129a29c53 100644 --- a/packages/ir-sdk/src/sdk/serialization/resources/http/types/HttpService.ts +++ b/packages/ir-sdk/src/sdk/serialization/resources/http/types/HttpService.ts @@ -21,6 +21,8 @@ export const HttpService: core.serialization.ObjectSchema (await import("../../..")).PathParameter) ), + encoding: core.serialization.lazyObject(async () => (await import("../../..")).Encoding).optional(), + transport: core.serialization.lazy(async () => (await import("../../..")).Transport).optional(), }); export declare namespace HttpService { @@ -32,5 +34,7 @@ export declare namespace HttpService { endpoints: serializers.HttpEndpoint.Raw[]; headers: serializers.HttpHeader.Raw[]; pathParameters: serializers.PathParameter.Raw[]; + encoding?: serializers.Encoding.Raw | null; + transport?: serializers.Transport.Raw | null; } } diff --git a/packages/ir-sdk/src/sdk/serialization/resources/http/types/Transport.ts b/packages/ir-sdk/src/sdk/serialization/resources/http/types/Transport.ts new file mode 100644 index 00000000000..2a70784364c --- /dev/null +++ b/packages/ir-sdk/src/sdk/serialization/resources/http/types/Transport.ts @@ -0,0 +1,38 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../.."; +import * as FernIr from "../../../../api"; +import * as core from "../../../../core"; + +export const Transport: core.serialization.Schema = core.serialization + .union("type", { + http: core.serialization.object({}), + grpc: core.serialization.lazyObject(async () => (await import("../../..")).GrpcTransport), + }) + .transform({ + transform: (value) => { + switch (value.type) { + case "http": + return FernIr.Transport.http(); + case "grpc": + return FernIr.Transport.grpc(value); + default: + return value as FernIr.Transport; + } + }, + untransform: ({ _visit, ...value }) => value as any, + }); + +export declare namespace Transport { + type Raw = Transport.Http | Transport.Grpc; + + interface Http { + type: "http"; + } + + interface Grpc extends serializers.GrpcTransport.Raw { + type: "grpc"; + } +} diff --git a/packages/ir-sdk/src/sdk/serialization/resources/http/types/index.ts b/packages/ir-sdk/src/sdk/serialization/resources/http/types/index.ts index 08c74998454..da94e6505ec 100644 --- a/packages/ir-sdk/src/sdk/serialization/resources/http/types/index.ts +++ b/packages/ir-sdk/src/sdk/serialization/resources/http/types/index.ts @@ -1,5 +1,7 @@ export * from "./HttpService"; export * from "./DeclaredServiceName"; +export * from "./Transport"; +export * from "./GrpcTransport"; export * from "./HttpEndpoint"; export * from "./EndpointName"; export * from "./HttpPath"; diff --git a/packages/ir-sdk/src/sdk/serialization/resources/index.ts b/packages/ir-sdk/src/sdk/serialization/resources/index.ts index 477da62b3a3..7191fc2c3a5 100644 --- a/packages/ir-sdk/src/sdk/serialization/resources/index.ts +++ b/packages/ir-sdk/src/sdk/serialization/resources/index.ts @@ -12,6 +12,8 @@ export * as http from "./http"; export * from "./http/types"; export * as ir from "./ir"; export * from "./ir/types"; +export * as proto from "./proto"; +export * from "./proto/types"; export * as types from "./types"; export * from "./types/types"; export * as variables from "./variables"; diff --git a/packages/ir-sdk/src/sdk/serialization/resources/ir/types/ApiDefinitionSource.ts b/packages/ir-sdk/src/sdk/serialization/resources/ir/types/ApiDefinitionSource.ts new file mode 100644 index 00000000000..d3c4fbaa7ee --- /dev/null +++ b/packages/ir-sdk/src/sdk/serialization/resources/ir/types/ApiDefinitionSource.ts @@ -0,0 +1,41 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../.."; +import * as FernIr from "../../../../api"; +import * as core from "../../../../core"; + +export const ApiDefinitionSource: core.serialization.Schema< + serializers.ApiDefinitionSource.Raw, + FernIr.ApiDefinitionSource +> = core.serialization + .union("type", { + proto: core.serialization.lazyObject(async () => (await import("../../..")).ProtoSource), + openapi: core.serialization.object({}), + }) + .transform({ + transform: (value) => { + switch (value.type) { + case "proto": + return FernIr.ApiDefinitionSource.proto(value); + case "openapi": + return FernIr.ApiDefinitionSource.openapi(); + default: + return value as FernIr.ApiDefinitionSource; + } + }, + untransform: ({ _visit, ...value }) => value as any, + }); + +export declare namespace ApiDefinitionSource { + type Raw = ApiDefinitionSource.Proto | ApiDefinitionSource.Openapi; + + interface Proto extends serializers.ProtoSource.Raw { + type: "proto"; + } + + interface Openapi { + type: "openapi"; + } +} diff --git a/packages/ir-sdk/src/sdk/serialization/resources/ir/types/ApiDefinitionSourceId.ts b/packages/ir-sdk/src/sdk/serialization/resources/ir/types/ApiDefinitionSourceId.ts new file mode 100644 index 00000000000..f37ef96b1bb --- /dev/null +++ b/packages/ir-sdk/src/sdk/serialization/resources/ir/types/ApiDefinitionSourceId.ts @@ -0,0 +1,16 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../.."; +import * as FernIr from "../../../../api"; +import * as core from "../../../../core"; + +export const ApiDefinitionSourceId: core.serialization.Schema< + serializers.ApiDefinitionSourceId.Raw, + FernIr.ApiDefinitionSourceId +> = core.serialization.string(); + +export declare namespace ApiDefinitionSourceId { + type Raw = string; +} diff --git a/packages/ir-sdk/src/sdk/serialization/resources/ir/types/IntermediateRepresentation.ts b/packages/ir-sdk/src/sdk/serialization/resources/ir/types/IntermediateRepresentation.ts index c68645f6d6d..516c4f06379 100644 --- a/packages/ir-sdk/src/sdk/serialization/resources/ir/types/IntermediateRepresentation.ts +++ b/packages/ir-sdk/src/sdk/serialization/resources/ir/types/IntermediateRepresentation.ts @@ -64,6 +64,7 @@ export const IntermediateRepresentation: core.serialization.ObjectSchema< async () => (await import("../../..")).ServiceTypeReferenceInfo ), readmeConfig: core.serialization.lazyObject(async () => (await import("../../..")).ReadmeConfig).optional(), + sourceConfig: core.serialization.lazyObject(async () => (await import("../../..")).SourceConfig).optional(), }); export declare namespace IntermediateRepresentation { @@ -92,5 +93,6 @@ export declare namespace IntermediateRepresentation { variables: serializers.VariableDeclaration.Raw[]; serviceTypeReferenceInfo: serializers.ServiceTypeReferenceInfo.Raw; readmeConfig?: serializers.ReadmeConfig.Raw | null; + sourceConfig?: serializers.SourceConfig.Raw | null; } } diff --git a/packages/ir-sdk/src/sdk/serialization/resources/ir/types/PlatformHeaders.ts b/packages/ir-sdk/src/sdk/serialization/resources/ir/types/PlatformHeaders.ts index e32a5b3f1d4..e583b90a8b7 100644 --- a/packages/ir-sdk/src/sdk/serialization/resources/ir/types/PlatformHeaders.ts +++ b/packages/ir-sdk/src/sdk/serialization/resources/ir/types/PlatformHeaders.ts @@ -11,6 +11,7 @@ export const PlatformHeaders: core.serialization.ObjectSchema (await import("../../..")).UserAgent).optional(), }); export declare namespace PlatformHeaders { @@ -18,5 +19,6 @@ export declare namespace PlatformHeaders { language: string; sdkName: string; sdkVersion: string; + userAgent?: serializers.UserAgent.Raw | null; } } diff --git a/packages/ir-sdk/src/sdk/serialization/resources/ir/types/ProtoSource.ts b/packages/ir-sdk/src/sdk/serialization/resources/ir/types/ProtoSource.ts new file mode 100644 index 00000000000..b67eba8a26f --- /dev/null +++ b/packages/ir-sdk/src/sdk/serialization/resources/ir/types/ProtoSource.ts @@ -0,0 +1,20 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../.."; +import * as FernIr from "../../../../api"; +import * as core from "../../../../core"; + +export const ProtoSource: core.serialization.ObjectSchema = + core.serialization.objectWithoutOptionalProperties({ + id: core.serialization.lazy(async () => (await import("../../..")).ApiDefinitionSourceId), + protoRootUrl: core.serialization.string(), + }); + +export declare namespace ProtoSource { + interface Raw { + id: serializers.ApiDefinitionSourceId.Raw; + protoRootUrl: string; + } +} diff --git a/packages/ir-sdk/src/sdk/serialization/resources/ir/types/SourceConfig.ts b/packages/ir-sdk/src/sdk/serialization/resources/ir/types/SourceConfig.ts new file mode 100644 index 00000000000..dc966080314 --- /dev/null +++ b/packages/ir-sdk/src/sdk/serialization/resources/ir/types/SourceConfig.ts @@ -0,0 +1,20 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../.."; +import * as FernIr from "../../../../api"; +import * as core from "../../../../core"; + +export const SourceConfig: core.serialization.ObjectSchema = + core.serialization.objectWithoutOptionalProperties({ + sources: core.serialization.list( + core.serialization.lazy(async () => (await import("../../..")).ApiDefinitionSource) + ), + }); + +export declare namespace SourceConfig { + interface Raw { + sources: serializers.ApiDefinitionSource.Raw[]; + } +} diff --git a/packages/ir-sdk/src/sdk/serialization/resources/ir/types/UserAgent.ts b/packages/ir-sdk/src/sdk/serialization/resources/ir/types/UserAgent.ts new file mode 100644 index 00000000000..b1d347ed2e9 --- /dev/null +++ b/packages/ir-sdk/src/sdk/serialization/resources/ir/types/UserAgent.ts @@ -0,0 +1,20 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../.."; +import * as FernIr from "../../../../api"; +import * as core from "../../../../core"; + +export const UserAgent: core.serialization.ObjectSchema = + core.serialization.objectWithoutOptionalProperties({ + header: core.serialization.stringLiteral("User-Agent"), + value: core.serialization.string(), + }); + +export declare namespace UserAgent { + interface Raw { + header: "User-Agent"; + value: string; + } +} diff --git a/packages/ir-sdk/src/sdk/serialization/resources/ir/types/index.ts b/packages/ir-sdk/src/sdk/serialization/resources/ir/types/index.ts index 3db624ee879..17947bff65f 100644 --- a/packages/ir-sdk/src/sdk/serialization/resources/ir/types/index.ts +++ b/packages/ir-sdk/src/sdk/serialization/resources/ir/types/index.ts @@ -1,7 +1,12 @@ export * from "./IntermediateRepresentation"; export * from "./ReadmeConfig"; +export * from "./SourceConfig"; +export * from "./ApiDefinitionSourceId"; +export * from "./ApiDefinitionSource"; +export * from "./ProtoSource"; export * from "./SdkConfig"; export * from "./PlatformHeaders"; +export * from "./UserAgent"; export * from "./ApiVersionScheme"; export * from "./HeaderApiVersionScheme"; export * from "./ErrorDiscriminationStrategy"; diff --git a/packages/ir-sdk/src/sdk/serialization/resources/proto/index.ts b/packages/ir-sdk/src/sdk/serialization/resources/proto/index.ts new file mode 100644 index 00000000000..eea524d6557 --- /dev/null +++ b/packages/ir-sdk/src/sdk/serialization/resources/proto/index.ts @@ -0,0 +1 @@ +export * from "./types"; diff --git a/packages/ir-sdk/src/sdk/serialization/resources/proto/types/CsharpProtobufFileOptions.ts b/packages/ir-sdk/src/sdk/serialization/resources/proto/types/CsharpProtobufFileOptions.ts new file mode 100644 index 00000000000..c5e9318f5f7 --- /dev/null +++ b/packages/ir-sdk/src/sdk/serialization/resources/proto/types/CsharpProtobufFileOptions.ts @@ -0,0 +1,20 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../.."; +import * as FernIr from "../../../../api"; +import * as core from "../../../../core"; + +export const CsharpProtobufFileOptions: core.serialization.ObjectSchema< + serializers.CsharpProtobufFileOptions.Raw, + FernIr.CsharpProtobufFileOptions +> = core.serialization.objectWithoutOptionalProperties({ + namespace: core.serialization.string(), +}); + +export declare namespace CsharpProtobufFileOptions { + interface Raw { + namespace: string; + } +} diff --git a/packages/ir-sdk/src/sdk/serialization/resources/proto/types/ProtobufFile.ts b/packages/ir-sdk/src/sdk/serialization/resources/proto/types/ProtobufFile.ts new file mode 100644 index 00000000000..9555229ed2c --- /dev/null +++ b/packages/ir-sdk/src/sdk/serialization/resources/proto/types/ProtobufFile.ts @@ -0,0 +1,22 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../.."; +import * as FernIr from "../../../../api"; +import * as core from "../../../../core"; + +export const ProtobufFile: core.serialization.ObjectSchema = + core.serialization.objectWithoutOptionalProperties({ + filepath: core.serialization.string(), + packageName: core.serialization.string().optional(), + options: core.serialization.lazyObject(async () => (await import("../../..")).ProtobufFileOptions).optional(), + }); + +export declare namespace ProtobufFile { + interface Raw { + filepath: string; + packageName?: string | null; + options?: serializers.ProtobufFileOptions.Raw | null; + } +} diff --git a/packages/ir-sdk/src/sdk/serialization/resources/proto/types/ProtobufFileOptions.ts b/packages/ir-sdk/src/sdk/serialization/resources/proto/types/ProtobufFileOptions.ts new file mode 100644 index 00000000000..c59b3c6c5ff --- /dev/null +++ b/packages/ir-sdk/src/sdk/serialization/resources/proto/types/ProtobufFileOptions.ts @@ -0,0 +1,20 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../.."; +import * as FernIr from "../../../../api"; +import * as core from "../../../../core"; + +export const ProtobufFileOptions: core.serialization.ObjectSchema< + serializers.ProtobufFileOptions.Raw, + FernIr.ProtobufFileOptions +> = core.serialization.objectWithoutOptionalProperties({ + csharp: core.serialization.lazyObject(async () => (await import("../../..")).CsharpProtobufFileOptions).optional(), +}); + +export declare namespace ProtobufFileOptions { + interface Raw { + csharp?: serializers.CsharpProtobufFileOptions.Raw | null; + } +} diff --git a/packages/ir-sdk/src/sdk/serialization/resources/proto/types/ProtobufService.ts b/packages/ir-sdk/src/sdk/serialization/resources/proto/types/ProtobufService.ts new file mode 100644 index 00000000000..9953ed0cff5 --- /dev/null +++ b/packages/ir-sdk/src/sdk/serialization/resources/proto/types/ProtobufService.ts @@ -0,0 +1,20 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../.."; +import * as FernIr from "../../../../api"; +import * as core from "../../../../core"; + +export const ProtobufService: core.serialization.ObjectSchema = + core.serialization.objectWithoutOptionalProperties({ + file: core.serialization.lazyObject(async () => (await import("../../..")).ProtobufFile), + name: core.serialization.lazyObject(async () => (await import("../../..")).Name), + }); + +export declare namespace ProtobufService { + interface Raw { + file: serializers.ProtobufFile.Raw; + name: serializers.Name.Raw; + } +} diff --git a/packages/ir-sdk/src/sdk/serialization/resources/proto/types/ProtobufType.ts b/packages/ir-sdk/src/sdk/serialization/resources/proto/types/ProtobufType.ts new file mode 100644 index 00000000000..c69840ecaa5 --- /dev/null +++ b/packages/ir-sdk/src/sdk/serialization/resources/proto/types/ProtobufType.ts @@ -0,0 +1,42 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../.."; +import * as FernIr from "../../../../api"; +import * as core from "../../../../core"; + +export const ProtobufType: core.serialization.Schema = + core.serialization + .union("type", { + wellKnown: core.serialization.object({ + value: core.serialization.lazy(async () => (await import("../../..")).WellKnownProtobufType), + }), + userDefined: core.serialization.lazyObject(async () => (await import("../../..")).UserDefinedProtobufType), + }) + .transform({ + transform: (value) => { + switch (value.type) { + case "wellKnown": + return FernIr.ProtobufType.wellKnown(value.value); + case "userDefined": + return FernIr.ProtobufType.userDefined(value); + default: + return value as FernIr.ProtobufType; + } + }, + untransform: ({ _visit, ...value }) => value as any, + }); + +export declare namespace ProtobufType { + type Raw = ProtobufType.WellKnown | ProtobufType.UserDefined; + + interface WellKnown { + type: "wellKnown"; + value: serializers.WellKnownProtobufType.Raw; + } + + interface UserDefined extends serializers.UserDefinedProtobufType.Raw { + type: "userDefined"; + } +} diff --git a/packages/ir-sdk/src/sdk/serialization/resources/proto/types/UserDefinedProtobufType.ts b/packages/ir-sdk/src/sdk/serialization/resources/proto/types/UserDefinedProtobufType.ts new file mode 100644 index 00000000000..4793c994010 --- /dev/null +++ b/packages/ir-sdk/src/sdk/serialization/resources/proto/types/UserDefinedProtobufType.ts @@ -0,0 +1,22 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../.."; +import * as FernIr from "../../../../api"; +import * as core from "../../../../core"; + +export const UserDefinedProtobufType: core.serialization.ObjectSchema< + serializers.UserDefinedProtobufType.Raw, + FernIr.UserDefinedProtobufType +> = core.serialization.objectWithoutOptionalProperties({ + file: core.serialization.lazyObject(async () => (await import("../../..")).ProtobufFile), + name: core.serialization.lazyObject(async () => (await import("../../..")).Name), +}); + +export declare namespace UserDefinedProtobufType { + interface Raw { + file: serializers.ProtobufFile.Raw; + name: serializers.Name.Raw; + } +} diff --git a/packages/ir-sdk/src/sdk/serialization/resources/proto/types/WellKnownProtobufType.ts b/packages/ir-sdk/src/sdk/serialization/resources/proto/types/WellKnownProtobufType.ts new file mode 100644 index 00000000000..bd307adfdef --- /dev/null +++ b/packages/ir-sdk/src/sdk/serialization/resources/proto/types/WellKnownProtobufType.ts @@ -0,0 +1,267 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../.."; +import * as FernIr from "../../../../api"; +import * as core from "../../../../core"; + +export const WellKnownProtobufType: core.serialization.Schema< + serializers.WellKnownProtobufType.Raw, + FernIr.WellKnownProtobufType +> = core.serialization + .union("type", { + any: core.serialization.object({}), + api: core.serialization.object({}), + boolValue: core.serialization.object({}), + bytesValue: core.serialization.object({}), + doubleValue: core.serialization.object({}), + duration: core.serialization.object({}), + empty: core.serialization.object({}), + enum: core.serialization.object({}), + enumValue: core.serialization.object({}), + field: core.serialization.object({}), + fieldCardinality: core.serialization.object({}), + fieldKind: core.serialization.object({}), + fieldMask: core.serialization.object({}), + floatValue: core.serialization.object({}), + int32Value: core.serialization.object({}), + int64Value: core.serialization.object({}), + listValue: core.serialization.object({}), + method: core.serialization.object({}), + mixin: core.serialization.object({}), + nullValue: core.serialization.object({}), + option: core.serialization.object({}), + sourceContext: core.serialization.object({}), + stringValue: core.serialization.object({}), + struct: core.serialization.object({}), + syntax: core.serialization.object({}), + timestamp: core.serialization.object({}), + type: core.serialization.object({}), + uint32Value: core.serialization.object({}), + uint64Value: core.serialization.object({}), + value: core.serialization.object({}), + }) + .transform({ + transform: (value) => { + switch (value.type) { + case "any": + return FernIr.WellKnownProtobufType.any(); + case "api": + return FernIr.WellKnownProtobufType.api(); + case "boolValue": + return FernIr.WellKnownProtobufType.boolValue(); + case "bytesValue": + return FernIr.WellKnownProtobufType.bytesValue(); + case "doubleValue": + return FernIr.WellKnownProtobufType.doubleValue(); + case "duration": + return FernIr.WellKnownProtobufType.duration(); + case "empty": + return FernIr.WellKnownProtobufType.empty(); + case "enum": + return FernIr.WellKnownProtobufType.enum(); + case "enumValue": + return FernIr.WellKnownProtobufType.enumValue(); + case "field": + return FernIr.WellKnownProtobufType.field(); + case "fieldCardinality": + return FernIr.WellKnownProtobufType.fieldCardinality(); + case "fieldKind": + return FernIr.WellKnownProtobufType.fieldKind(); + case "fieldMask": + return FernIr.WellKnownProtobufType.fieldMask(); + case "floatValue": + return FernIr.WellKnownProtobufType.floatValue(); + case "int32Value": + return FernIr.WellKnownProtobufType.int32Value(); + case "int64Value": + return FernIr.WellKnownProtobufType.int64Value(); + case "listValue": + return FernIr.WellKnownProtobufType.listValue(); + case "method": + return FernIr.WellKnownProtobufType.method(); + case "mixin": + return FernIr.WellKnownProtobufType.mixin(); + case "nullValue": + return FernIr.WellKnownProtobufType.nullValue(); + case "option": + return FernIr.WellKnownProtobufType.option(); + case "sourceContext": + return FernIr.WellKnownProtobufType.sourceContext(); + case "stringValue": + return FernIr.WellKnownProtobufType.stringValue(); + case "struct": + return FernIr.WellKnownProtobufType.struct(); + case "syntax": + return FernIr.WellKnownProtobufType.syntax(); + case "timestamp": + return FernIr.WellKnownProtobufType.timestamp(); + case "type": + return FernIr.WellKnownProtobufType.type(); + case "uint32Value": + return FernIr.WellKnownProtobufType.uint32Value(); + case "uint64Value": + return FernIr.WellKnownProtobufType.uint64Value(); + case "value": + return FernIr.WellKnownProtobufType.value(); + default: + return value as FernIr.WellKnownProtobufType; + } + }, + untransform: ({ _visit, ...value }) => value as any, + }); + +export declare namespace WellKnownProtobufType { + type Raw = + | WellKnownProtobufType.Any + | WellKnownProtobufType.Api + | WellKnownProtobufType.BoolValue + | WellKnownProtobufType.BytesValue + | WellKnownProtobufType.DoubleValue + | WellKnownProtobufType.Duration + | WellKnownProtobufType.Empty + | WellKnownProtobufType.Enum + | WellKnownProtobufType.EnumValue + | WellKnownProtobufType.Field + | WellKnownProtobufType.FieldCardinality + | WellKnownProtobufType.FieldKind + | WellKnownProtobufType.FieldMask + | WellKnownProtobufType.FloatValue + | WellKnownProtobufType.Int32Value + | WellKnownProtobufType.Int64Value + | WellKnownProtobufType.ListValue + | WellKnownProtobufType.Method + | WellKnownProtobufType.Mixin + | WellKnownProtobufType.NullValue + | WellKnownProtobufType.Option + | WellKnownProtobufType.SourceContext + | WellKnownProtobufType.StringValue + | WellKnownProtobufType.Struct + | WellKnownProtobufType.Syntax + | WellKnownProtobufType.Timestamp + | WellKnownProtobufType.Type + | WellKnownProtobufType.Uint32Value + | WellKnownProtobufType.Uint64Value + | WellKnownProtobufType.Value; + + interface Any { + type: "any"; + } + + interface Api { + type: "api"; + } + + interface BoolValue { + type: "boolValue"; + } + + interface BytesValue { + type: "bytesValue"; + } + + interface DoubleValue { + type: "doubleValue"; + } + + interface Duration { + type: "duration"; + } + + interface Empty { + type: "empty"; + } + + interface Enum { + type: "enum"; + } + + interface EnumValue { + type: "enumValue"; + } + + interface Field { + type: "field"; + } + + interface FieldCardinality { + type: "fieldCardinality"; + } + + interface FieldKind { + type: "fieldKind"; + } + + interface FieldMask { + type: "fieldMask"; + } + + interface FloatValue { + type: "floatValue"; + } + + interface Int32Value { + type: "int32Value"; + } + + interface Int64Value { + type: "int64Value"; + } + + interface ListValue { + type: "listValue"; + } + + interface Method { + type: "method"; + } + + interface Mixin { + type: "mixin"; + } + + interface NullValue { + type: "nullValue"; + } + + interface Option { + type: "option"; + } + + interface SourceContext { + type: "sourceContext"; + } + + interface StringValue { + type: "stringValue"; + } + + interface Struct { + type: "struct"; + } + + interface Syntax { + type: "syntax"; + } + + interface Timestamp { + type: "timestamp"; + } + + interface Type { + type: "type"; + } + + interface Uint32Value { + type: "uint32Value"; + } + + interface Uint64Value { + type: "uint64Value"; + } + + interface Value { + type: "value"; + } +} diff --git a/packages/ir-sdk/src/sdk/serialization/resources/proto/types/index.ts b/packages/ir-sdk/src/sdk/serialization/resources/proto/types/index.ts new file mode 100644 index 00000000000..87df1112594 --- /dev/null +++ b/packages/ir-sdk/src/sdk/serialization/resources/proto/types/index.ts @@ -0,0 +1,7 @@ +export * from "./ProtobufService"; +export * from "./ProtobufType"; +export * from "./UserDefinedProtobufType"; +export * from "./WellKnownProtobufType"; +export * from "./ProtobufFile"; +export * from "./ProtobufFileOptions"; +export * from "./CsharpProtobufFileOptions"; diff --git a/packages/ir-sdk/src/sdk/serialization/resources/types/types/Encoding.ts b/packages/ir-sdk/src/sdk/serialization/resources/types/types/Encoding.ts new file mode 100644 index 00000000000..0bb4063a1b1 --- /dev/null +++ b/packages/ir-sdk/src/sdk/serialization/resources/types/types/Encoding.ts @@ -0,0 +1,20 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../.."; +import * as FernIr from "../../../../api"; +import * as core from "../../../../core"; + +export const Encoding: core.serialization.ObjectSchema = + core.serialization.objectWithoutOptionalProperties({ + json: core.serialization.lazyObject(async () => (await import("../../..")).JsonEncoding).optional(), + proto: core.serialization.lazyObject(async () => (await import("../../..")).ProtoEncoding).optional(), + }); + +export declare namespace Encoding { + interface Raw { + json?: serializers.JsonEncoding.Raw | null; + proto?: serializers.ProtoEncoding.Raw | null; + } +} diff --git a/packages/ir-sdk/src/sdk/serialization/resources/types/types/JsonEncoding.ts b/packages/ir-sdk/src/sdk/serialization/resources/types/types/JsonEncoding.ts new file mode 100644 index 00000000000..bbddc22e0c3 --- /dev/null +++ b/packages/ir-sdk/src/sdk/serialization/resources/types/types/JsonEncoding.ts @@ -0,0 +1,14 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../.."; +import * as FernIr from "../../../../api"; +import * as core from "../../../../core"; + +export const JsonEncoding: core.serialization.ObjectSchema = + core.serialization.objectWithoutOptionalProperties({}); + +export declare namespace JsonEncoding { + interface Raw {} +} diff --git a/packages/ir-sdk/src/sdk/serialization/resources/types/types/ProtoEncoding.ts b/packages/ir-sdk/src/sdk/serialization/resources/types/types/ProtoEncoding.ts new file mode 100644 index 00000000000..0ec970d8fb5 --- /dev/null +++ b/packages/ir-sdk/src/sdk/serialization/resources/types/types/ProtoEncoding.ts @@ -0,0 +1,14 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../.."; +import * as FernIr from "../../../../api"; +import * as core from "../../../../core"; + +export const ProtoEncoding: core.serialization.ObjectSchema = + core.serialization.objectWithoutOptionalProperties({}); + +export declare namespace ProtoEncoding { + interface Raw {} +} diff --git a/packages/ir-sdk/src/sdk/serialization/resources/types/types/Source.ts b/packages/ir-sdk/src/sdk/serialization/resources/types/types/Source.ts new file mode 100644 index 00000000000..c5c52f795c5 --- /dev/null +++ b/packages/ir-sdk/src/sdk/serialization/resources/types/types/Source.ts @@ -0,0 +1,34 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../.."; +import * as FernIr from "../../../../api"; +import * as core from "../../../../core"; + +export const Source: core.serialization.Schema = core.serialization + .union("type", { + proto: core.serialization.object({ + value: core.serialization.lazy(async () => (await import("../../..")).ProtobufType), + }), + }) + .transform({ + transform: (value) => { + switch (value.type) { + case "proto": + return FernIr.Source.proto(value.value); + default: + return value as FernIr.Source; + } + }, + untransform: ({ _visit, ...value }) => value as any, + }); + +export declare namespace Source { + type Raw = Source.Proto; + + interface Proto { + type: "proto"; + value: serializers.ProtobufType.Raw; + } +} diff --git a/packages/ir-sdk/src/sdk/serialization/resources/types/types/TypeDeclaration.ts b/packages/ir-sdk/src/sdk/serialization/resources/types/types/TypeDeclaration.ts index 4cbc78e1ba4..afa39e8f962 100644 --- a/packages/ir-sdk/src/sdk/serialization/resources/types/types/TypeDeclaration.ts +++ b/packages/ir-sdk/src/sdk/serialization/resources/types/types/TypeDeclaration.ts @@ -20,6 +20,8 @@ export const TypeDeclaration: core.serialization.ObjectSchema (await import("../../..")).TypeId) ), + encoding: core.serialization.lazyObject(async () => (await import("../../..")).Encoding).optional(), + source: core.serialization.lazy(async () => (await import("../../..")).Source).optional(), }) .extend(core.serialization.lazyObject(async () => (await import("../../..")).Declaration)); @@ -30,5 +32,7 @@ export declare namespace TypeDeclaration { autogeneratedExamples: serializers.ExampleType.Raw[]; userProvidedExamples: serializers.ExampleType.Raw[]; referencedTypes: serializers.TypeId.Raw[]; + encoding?: serializers.Encoding.Raw | null; + source?: serializers.Source.Raw | null; } } diff --git a/packages/ir-sdk/src/sdk/serialization/resources/types/types/index.ts b/packages/ir-sdk/src/sdk/serialization/resources/types/types/index.ts index d39ad129787..65c44137295 100644 --- a/packages/ir-sdk/src/sdk/serialization/resources/types/types/index.ts +++ b/packages/ir-sdk/src/sdk/serialization/resources/types/types/index.ts @@ -1,3 +1,7 @@ +export * from "./Source"; +export * from "./Encoding"; +export * from "./JsonEncoding"; +export * from "./ProtoEncoding"; export * from "./TypeDeclaration"; export * from "./DeclaredTypeName"; export * from "./Type"; diff --git a/packages/seed/src/cli.ts b/packages/seed/src/cli.ts index 25d3f96f8a8..925f99ae5b8 100644 --- a/packages/seed/src/cli.ts +++ b/packages/seed/src/cli.ts @@ -118,8 +118,6 @@ function addTestCommand(cli: Argv) { keepDocker: argv.keepDocker, scriptRunner: scriptRunner }); - scriptRunners.push(scriptRunner); - CONSOLE_LOGGER.info(`${generator.workspaceName} does not support local mode. Running in docker.`); } tests.push( diff --git a/packages/seed/src/commands/test/test-runner/LocalTestRunner.ts b/packages/seed/src/commands/test/test-runner/LocalTestRunner.ts index 9918037aa54..8605f027500 100644 --- a/packages/seed/src/commands/test/test-runner/LocalTestRunner.ts +++ b/packages/seed/src/commands/test/test-runner/LocalTestRunner.ts @@ -71,7 +71,9 @@ export class LocalTestRunner extends TestRunner { }, context: taskContext, irVersionOverride: irVersion, - generatorInvocation + generatorInvocation, + packageName: undefined, + version: undefined }); let generatorConfig = getGeneratorConfig({ generatorInvocation, diff --git a/packages/seed/src/commands/test/test-runner/TestRunner.ts b/packages/seed/src/commands/test/test-runner/TestRunner.ts index 005e7929a46..594ec288b39 100644 --- a/packages/seed/src/commands/test/test-runner/TestRunner.ts +++ b/packages/seed/src/commands/test/test-runner/TestRunner.ts @@ -126,7 +126,7 @@ export abstract class TestRunner { RelativeFilePath.of(configuration.outputFolder) ); const language = this.generator.workspaceConfig.language; - const outputVersion = configuration?.outputVersion; + const outputVersion = configuration?.outputVersion ?? "0.0.1"; const customConfig = this.generator.workspaceConfig.defaultCustomConfig != null || configuration?.customConfig != null ? { diff --git a/seed/csharp-model/grpc-proto/.github/workflows/ci.yml b/seed/csharp-model/grpc-proto/.github/workflows/ci.yml new file mode 100644 index 00000000000..bc4fa1a98cb --- /dev/null +++ b/seed/csharp-model/grpc-proto/.github/workflows/ci.yml @@ -0,0 +1,69 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - uses: actions/checkout@master + + - name: Setup .NET + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 8.x + + - name: Install tools + run: | + dotnet tool restore + + - name: Build Release + run: dotnet build src -c Release /p:ContinuousIntegrationBuild=true + + unit-tests: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - uses: actions/checkout@master + + - name: Setup .NET + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 8.x + + - name: Install tools + run: | + dotnet tool restore + + - name: Run Tests + run: | + dotnet test src + + + publish: + needs: [compile] + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Setup .NET + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 8.x + + - name: Publish + env: + NUGET_API_KEY: ${{ secrets.NUGET_API_TOKEN }} + run: | + dotnet pack src -c Release + dotnet nuget push src/SeedApi/bin/Release/*.nupkg --api-key $NUGET_API_KEY --source "nuget.org" diff --git a/seed/csharp-model/grpc-proto/.gitignore b/seed/csharp-model/grpc-proto/.gitignore new file mode 100644 index 00000000000..9965de29662 --- /dev/null +++ b/seed/csharp-model/grpc-proto/.gitignore @@ -0,0 +1,477 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET +project.lock.json +project.fragment.lock.json +artifacts/ + +# Tye +.tye/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml + +## +## Visual studio for Mac +## + + +# globs +Makefile.in +*.userprefs +*.usertasks +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.tar.gz +tarballs/ +test-results/ + +# Mac bundle stuff +*.dmg +*.app + +# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk diff --git a/seed/go-sdk/examples/.mock/fern.config.json b/seed/csharp-model/grpc-proto/.mock/fern.config.json similarity index 100% rename from seed/go-sdk/examples/.mock/fern.config.json rename to seed/csharp-model/grpc-proto/.mock/fern.config.json diff --git a/seed/csharp-model/grpc-proto/.mock/generators.yml b/seed/csharp-model/grpc-proto/.mock/generators.yml new file mode 100644 index 00000000000..902c4f50168 --- /dev/null +++ b/seed/csharp-model/grpc-proto/.mock/generators.yml @@ -0,0 +1,5 @@ +api: + - proto: + root: proto + target: proto/user/v1/user.proto + local-generation: true \ No newline at end of file diff --git a/seed/csharp-model/grpc-proto/.mock/proto/google/api/annotations.proto b/seed/csharp-model/grpc-proto/.mock/proto/google/api/annotations.proto new file mode 100644 index 00000000000..8ff42098404 --- /dev/null +++ b/seed/csharp-model/grpc-proto/.mock/proto/google/api/annotations.proto @@ -0,0 +1,31 @@ +// Copyright 2015 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/api/http.proto"; +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "AnnotationsProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // See `HttpRule`. + HttpRule http = 72295728; +} \ No newline at end of file diff --git a/seed/csharp-model/grpc-proto/.mock/proto/google/api/field_behavior.proto b/seed/csharp-model/grpc-proto/.mock/proto/google/api/field_behavior.proto new file mode 100644 index 00000000000..128799c558d --- /dev/null +++ b/seed/csharp-model/grpc-proto/.mock/proto/google/api/field_behavior.proto @@ -0,0 +1,104 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "FieldBehaviorProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.FieldOptions { + // A designation of a specific field behavior (required, output only, etc.) + // in protobuf messages. + // + // Examples: + // + // string name = 1 [(google.api.field_behavior) = REQUIRED]; + // State state = 1 [(google.api.field_behavior) = OUTPUT_ONLY]; + // google.protobuf.Duration ttl = 1 + // [(google.api.field_behavior) = INPUT_ONLY]; + // google.protobuf.Timestamp expire_time = 1 + // [(google.api.field_behavior) = OUTPUT_ONLY, + // (google.api.field_behavior) = IMMUTABLE]; + repeated google.api.FieldBehavior field_behavior = 1052; +} + +// An indicator of the behavior of a given field (for example, that a field +// is required in requests, or given as output but ignored as input). +// This **does not** change the behavior in protocol buffers itself; it only +// denotes the behavior and may affect how API tooling handles the field. +// +// Note: This enum **may** receive new values in the future. +enum FieldBehavior { + // Conventional default for enums. Do not use this. + FIELD_BEHAVIOR_UNSPECIFIED = 0; + + // Specifically denotes a field as optional. + // While all fields in protocol buffers are optional, this may be specified + // for emphasis if appropriate. + OPTIONAL = 1; + + // Denotes a field as required. + // This indicates that the field **must** be provided as part of the request, + // and failure to do so will cause an error (usually `INVALID_ARGUMENT`). + REQUIRED = 2; + + // Denotes a field as output only. + // This indicates that the field is provided in responses, but including the + // field in a request does nothing (the server *must* ignore it and + // *must not* throw an error as a result of the field's presence). + OUTPUT_ONLY = 3; + + // Denotes a field as input only. + // This indicates that the field is provided in requests, and the + // corresponding field is not included in output. + INPUT_ONLY = 4; + + // Denotes a field as immutable. + // This indicates that the field may be set once in a request to create a + // resource, but may not be changed thereafter. + IMMUTABLE = 5; + + // Denotes that a (repeated) field is an unordered list. + // This indicates that the service may provide the elements of the list + // in any arbitrary order, rather than the order the user originally + // provided. Additionally, the list's order may or may not be stable. + UNORDERED_LIST = 6; + + // Denotes that this field returns a non-empty default value if not set. + // This indicates that if the user provides the empty value in a request, + // a non-empty value will be returned. The user will not be aware of what + // non-empty value to expect. + NON_EMPTY_DEFAULT = 7; + + // Denotes that the field in a resource (a message annotated with + // google.api.resource) is used in the resource name to uniquely identify the + // resource. For AIP-compliant APIs, this should only be applied to the + // `name` field on the resource. + // + // This behavior should not be applied to references to other resources within + // the message. + // + // The identifier field of resources often have different field behavior + // depending on the request it is embedded in (e.g. for Create methods name + // is optional and unused, while for Update methods it is required). Instead + // of method-specific annotations, only `IDENTIFIER` is required. + IDENTIFIER = 8; +} \ No newline at end of file diff --git a/seed/csharp-model/grpc-proto/.mock/proto/google/api/http.proto b/seed/csharp-model/grpc-proto/.mock/proto/google/api/http.proto new file mode 100644 index 00000000000..c8392381eb9 --- /dev/null +++ b/seed/csharp-model/grpc-proto/.mock/proto/google/api/http.proto @@ -0,0 +1,379 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "HttpProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Defines the HTTP configuration for an API service. It contains a list of +// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method +// to one or more HTTP REST API methods. +message Http { + // A list of HTTP configuration rules that apply to individual API methods. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated HttpRule rules = 1; + + // When set to true, URL path parameters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // + // The default behavior is to not decode RFC 6570 reserved characters in multi + // segment matches. + bool fully_decode_reserved_expansion = 2; +} + +// # gRPC Transcoding +// +// gRPC Transcoding is a feature for mapping between a gRPC method and one or +// more HTTP REST endpoints. It allows developers to build a single API service +// that supports both gRPC APIs and REST APIs. Many systems, including [Google +// APIs](https://github.com/googleapis/googleapis), +// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC +// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), +// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature +// and use it for large scale production services. +// +// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies +// how different portions of the gRPC request message are mapped to the URL +// path, URL query parameters, and HTTP request body. It also controls how the +// gRPC response message is mapped to the HTTP response body. `HttpRule` is +// typically specified as an `google.api.http` annotation on the gRPC method. +// +// Each mapping specifies a URL path template and an HTTP method. The path +// template may refer to one or more fields in the gRPC request message, as long +// as each field is a non-repeated field with a primitive (non-message) type. +// The path template controls how fields of the request message are mapped to +// the URL path. +// +// Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/{name=messages/*}" +// }; +// } +// } +// message GetMessageRequest { +// string name = 1; // Mapped to URL path. +// } +// message Message { +// string text = 1; // The resource content. +// } +// +// This enables an HTTP REST to gRPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` +// +// Any fields in the request message which are not bound by the path template +// automatically become HTTP query parameters if there is no HTTP request body. +// For example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get:"/v1/messages/{message_id}" +// }; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // Mapped to URL path. +// int64 revision = 2; // Mapped to URL query parameter `revision`. +// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. +// } +// +// This enables a HTTP JSON to RPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | +// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: +// "foo"))` +// +// Note that fields which are mapped to URL query parameters must have a +// primitive type or a repeated primitive type or a non-repeated message type. +// In the case of a repeated type, the parameter can be repeated in the URL +// as `...?param=A¶m=B`. In the case of a message type, each field of the +// message is mapped to a separate parameter, such as +// `...?foo.a=A&foo.b=B&foo.c=C`. +// +// For HTTP methods that allow a request body, the `body` field +// specifies the mapping. Consider a REST update method on the +// message resource collection: +// +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } +// +// The following HTTP JSON to RPC mapping is enabled, where the +// representation of the JSON in the request body is determined by +// protos JSON encoding: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" message { text: "Hi!" })` +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. This enables the following alternative definition of +// the update method: +// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } +// +// +// The following HTTP JSON to RPC mapping is enabled: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" text: "Hi!")` +// +// Note that when using `*` in the body mapping, it is not possible to +// have HTTP parameters, as all fields not bound by the path end in +// the body. This makes this option more rarely used in practice when +// defining REST APIs. The common usage of `*` is in custom methods +// which don't use the URL at all for transferring data. +// +// It is possible to define multiple HTTP methods for one RPC by using +// the `additional_bindings` option. Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } +// +// This enables the following two alternative HTTP JSON to RPC mappings: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` +// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: +// "123456")` +// +// ## Rules for HTTP mapping +// +// 1. Leaf request fields (recursive expansion nested messages in the request +// message) are classified into three categories: +// - Fields referred by the path template. They are passed via the URL path. +// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They +// are passed via the HTTP +// request body. +// - All other fields are passed via the URL query parameters, and the +// parameter name is the field path in the request message. A repeated +// field can be represented as multiple query parameters under the same +// name. +// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL +// query parameter, all fields +// are passed via URL path and HTTP request body. +// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP +// request body, all +// fields are passed via URL path and URL query parameters. +// +// ### Path template syntax +// +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +// +// The syntax `*` matches a single URL path segment. The syntax `**` matches +// zero or more URL path segments, which must be the last part of the URL path +// except the `Verb`. +// +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable +// matches a single path segment, its template may be omitted, e.g. `{var}` +// is equivalent to `{var=*}`. +// +// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` +// contains any reserved character, such characters should be percent-encoded +// before the matching. +// +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path on the client +// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The +// server side does the reverse decoding. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{var}`. +// +// If a variable contains multiple path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path on the +// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. +// The server side does the reverse decoding, except "%2F" and "%2f" are left +// unchanged. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{+var}`. +// +// ## Using gRPC API Service Configuration +// +// gRPC API Service Configuration (service config) is a configuration language +// for configuring a gRPC service to become a user-facing product. The +// service config is simply the YAML representation of the `google.api.Service` +// proto message. +// +// As an alternative to annotating your proto file, you can configure gRPC +// transcoding in your service config YAML files. You do this by specifying a +// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same +// effect as the proto annotation. This can be particularly useful if you +// have a proto that is reused in multiple services. Note that any transcoding +// specified in the service config will override any matching transcoding +// configuration in the proto. +// +// Example: +// +// http: +// rules: +// # Selects a gRPC method and applies HttpRule to it. +// - selector: example.v1.Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} +// +// ## Special notes +// +// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the +// proto to JSON conversion must follow the [proto3 +// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). +// +// While the single segment variable follows the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String +// Expansion, the multi segment variable **does not** follow RFC 6570 Section +// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding +// for multi segment variables. +// +// The path variables **must not** refer to any repeated or mapped field, +// because client libraries are not capable of handling such variable expansion. +// +// The path variables **must not** capture the leading "/" character. The reason +// is that the most common use case "{var}" does not capture the leading "/" +// character. For consistency, all path variables must share the same behavior. +// +// Repeated message fields must not be mapped to URL query parameters, because +// no client library can support such complicated mapping. +// +// If an API needs to use a JSON array for request or response body, it can map +// the request or response body to a repeated field. However, some gRPC +// Transcoding implementations may not support this feature. +message HttpRule { + // Selects a method to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax + // details. + string selector = 1; + + // Determines the URL pattern is matched by this rules. This pattern can be + // used with any of the {get|put|post|delete|patch} methods. A custom method + // can be defined using the 'custom' field. + oneof pattern { + // Maps to HTTP GET. Used for listing and getting information about + // resources. + string get = 2; + + // Maps to HTTP PUT. Used for replacing a resource. + string put = 3; + + // Maps to HTTP POST. Used for creating a resource or performing an action. + string post = 4; + + // Maps to HTTP DELETE. Used for deleting a resource. + string delete = 5; + + // Maps to HTTP PATCH. Used for updating a resource. + string patch = 6; + + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. + CustomHttpPattern custom = 8; + } + + // The name of the request field whose value is mapped to the HTTP request + // body, or `*` for mapping all request fields not captured by the path + // pattern to the HTTP body, or omitted for not having any HTTP request body. + // + // NOTE: the referred field must be present at the top-level of the request + // message type. + string body = 7; + + // Optional. The name of the response field whose value is mapped to the HTTP + // response body. When omitted, the entire response message will be used + // as the HTTP response body. + // + // NOTE: The referred field must be present at the top-level of the response + // message type. + string response_body = 12; + + // Additional HTTP bindings for the selector. Nested bindings must + // not contain an `additional_bindings` field themselves (that is, + // the nesting may only be one level deep). + repeated HttpRule additional_bindings = 11; +} + +// A custom pattern is used for defining custom HTTP verb. +message CustomHttpPattern { + // The name of this custom HTTP verb. + string kind = 1; + + // The path matched by this custom verb. + string path = 2; +} \ No newline at end of file diff --git a/seed/csharp-model/grpc-proto/.mock/proto/user/v1/user.proto b/seed/csharp-model/grpc-proto/.mock/proto/user/v1/user.proto new file mode 100644 index 00000000000..a3ad0195bd4 --- /dev/null +++ b/seed/csharp-model/grpc-proto/.mock/proto/user/v1/user.proto @@ -0,0 +1,39 @@ +syntax = "proto3"; + +package user.v1; + +import "google/api/annotations.proto"; +import "google/api/field_behavior.proto"; +import "google/protobuf/struct.proto"; + +option go_package = "user/v1"; +option csharp_namespace = "User.V1"; + +message UserModel { + string username = 1; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateRequest { + string username = 1; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateResponse { + UserModel user = 1; +} + +service User { + rpc Create(CreateRequest) returns (CreateResponse) { + option (google.api.http) = { + post: "/users" + body: "*" + }; + } +} \ No newline at end of file diff --git a/seed/go-sdk/examples/snippet-templates.json b/seed/csharp-model/grpc-proto/snippet-templates.json similarity index 100% rename from seed/go-sdk/examples/snippet-templates.json rename to seed/csharp-model/grpc-proto/snippet-templates.json diff --git a/seed/csharp-model/grpc-proto/snippet.json b/seed/csharp-model/grpc-proto/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/csharp-model/grpc-proto/src/SeedApi.Test/SeedApi.Test.csproj b/seed/csharp-model/grpc-proto/src/SeedApi.Test/SeedApi.Test.csproj new file mode 100644 index 00000000000..fa0676afacd --- /dev/null +++ b/seed/csharp-model/grpc-proto/src/SeedApi.Test/SeedApi.Test.csproj @@ -0,0 +1,24 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/seed/csharp-model/grpc-proto/src/SeedApi/Core/CollectionItemSerializer.cs b/seed/csharp-model/grpc-proto/src/SeedApi/Core/CollectionItemSerializer.cs new file mode 100644 index 00000000000..2e1e0f550be --- /dev/null +++ b/seed/csharp-model/grpc-proto/src/SeedApi/Core/CollectionItemSerializer.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace SeedApi.Core; + +/// +/// Json collection converter. +/// +/// Type of item to convert. +/// Converter to use for individual items. +public class CollectionItemSerializer + : JsonConverter> + where TConverterType : JsonConverter +{ + /// + /// Reads a json string and deserializes it into an object. + /// + /// Json reader. + /// Type to convert. + /// Serializer options. + /// Created object. + public override IEnumerable? Read( + ref Utf8JsonReader reader, + System.Type typeToConvert, + JsonSerializerOptions options + ) + { + if (reader.TokenType == JsonTokenType.Null) + { + return default; + } + + var jsonSerializerOptions = new JsonSerializerOptions(options); + jsonSerializerOptions.Converters.Clear(); + jsonSerializerOptions.Converters.Add(Activator.CreateInstance()); + + var returnValue = new List(); + + while (reader.TokenType != JsonTokenType.EndArray) + { + if (reader.TokenType != JsonTokenType.StartArray) + { + var item = (TDatatype)( + JsonSerializer.Deserialize(ref reader, typeof(TDatatype), jsonSerializerOptions) + ?? throw new Exception( + $"Failed to deserialize collection item of type {typeof(TDatatype)}" + ) + ); + returnValue.Add(item); + } + + reader.Read(); + } + + return returnValue; + } + + /// + /// Writes a json string. + /// + /// Json writer. + /// Value to write. + /// Serializer options. + public override void Write( + Utf8JsonWriter writer, + IEnumerable? value, + JsonSerializerOptions options + ) + { + if (value == null) + { + writer.WriteNullValue(); + return; + } + + JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions(options); + jsonSerializerOptions.Converters.Clear(); + jsonSerializerOptions.Converters.Add(Activator.CreateInstance()); + + writer.WriteStartArray(); + + foreach (var data in value) + { + JsonSerializer.Serialize(writer, data, jsonSerializerOptions); + } + + writer.WriteEndArray(); + } +} diff --git a/seed/csharp-model/grpc-proto/src/SeedApi/Core/OneOfSerializer.cs b/seed/csharp-model/grpc-proto/src/SeedApi/Core/OneOfSerializer.cs new file mode 100644 index 00000000000..d86f8e24e70 --- /dev/null +++ b/seed/csharp-model/grpc-proto/src/SeedApi/Core/OneOfSerializer.cs @@ -0,0 +1,67 @@ +using System.Reflection; +using System.Text.Json; +using System.Text.Json.Serialization; +using OneOf; + +namespace SeedApi.Core; + +public class OneOfSerializer : JsonConverter + where TOneOf : IOneOf +{ + public override TOneOf? Read( + ref Utf8JsonReader reader, + System.Type typeToConvert, + JsonSerializerOptions options + ) + { + if (reader.TokenType is JsonTokenType.Null) + return default; + + foreach (var (type, cast) in s_types) + { + try + { + var readerCopy = reader; + var result = JsonSerializer.Deserialize(ref readerCopy, type, options); + reader.Skip(); + return (TOneOf)cast.Invoke(null, [result])!; + } + catch (JsonException) { } + } + + throw new JsonException( + $"Cannot deserialize into one of the supported types for {typeToConvert}" + ); + } + + private static readonly (System.Type type, MethodInfo cast)[] s_types = GetOneOfTypes(); + + public override void Write(Utf8JsonWriter writer, TOneOf value, JsonSerializerOptions options) + { + JsonSerializer.Serialize(writer, value.Value, options); + } + + private static (System.Type type, MethodInfo cast)[] GetOneOfTypes() + { + var casts = typeof(TOneOf) + .GetRuntimeMethods() + .Where(m => m.IsSpecialName && m.Name == "op_Implicit") + .ToArray(); + var type = typeof(TOneOf); + while (type != null) + { + if ( + type.IsGenericType + && (type.Name.StartsWith("OneOf`") || type.Name.StartsWith("OneOfBase`")) + ) + { + return type.GetGenericArguments() + .Select(t => (t, casts.First(c => c.GetParameters()[0].ParameterType == t))) + .ToArray(); + } + + type = type.BaseType; + } + throw new InvalidOperationException($"{typeof(TOneOf)} isn't OneOf or OneOfBase"); + } +} diff --git a/seed/csharp-model/grpc-proto/src/SeedApi/Core/StringEnumSerializer.cs b/seed/csharp-model/grpc-proto/src/SeedApi/Core/StringEnumSerializer.cs new file mode 100644 index 00000000000..fbaf79b865e --- /dev/null +++ b/seed/csharp-model/grpc-proto/src/SeedApi/Core/StringEnumSerializer.cs @@ -0,0 +1,53 @@ +using System.Runtime.Serialization; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace SeedApi.Core; + +public class StringEnumSerializer : JsonConverter + where TEnum : struct, System.Enum +{ + private readonly Dictionary _enumToString = new(); + private readonly Dictionary _stringToEnum = new(); + + public StringEnumSerializer() + { + var type = typeof(TEnum); + var values = Enum.GetValues(type); + + foreach (var value in values) + { + var enumValue = (TEnum)value; + var enumMember = type.GetMember(enumValue.ToString())[0]; + var attr = enumMember + .GetCustomAttributes(typeof(EnumMemberAttribute), false) + .Cast() + .FirstOrDefault(); + + var stringValue = + attr?.Value + ?? value.ToString() + ?? throw new Exception("Unexpected null enum toString value"); + + _enumToString.Add(enumValue, stringValue); + _stringToEnum.Add(stringValue, enumValue); + } + } + + public override TEnum Read( + ref Utf8JsonReader reader, + System.Type typeToConvert, + JsonSerializerOptions options + ) + { + var stringValue = + reader.GetString() + ?? throw new Exception("The JSON value could not be read as a string."); + return _stringToEnum.TryGetValue(stringValue, out var enumValue) ? enumValue : default; + } + + public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options) + { + writer.WriteStringValue(_enumToString[value]); + } +} diff --git a/seed/csharp-model/grpc-proto/src/SeedApi/CreateResponse.cs b/seed/csharp-model/grpc-proto/src/SeedApi/CreateResponse.cs new file mode 100644 index 00000000000..0374ef2a9b7 --- /dev/null +++ b/seed/csharp-model/grpc-proto/src/SeedApi/CreateResponse.cs @@ -0,0 +1,12 @@ +using System.Text.Json.Serialization; +using SeedApi; + +#nullable enable + +namespace SeedApi; + +public record CreateResponse +{ + [JsonPropertyName("user")] + public UserModel? User { get; set; } +} diff --git a/seed/csharp-model/grpc-proto/src/SeedApi/SeedApi.csproj b/seed/csharp-model/grpc-proto/src/SeedApi/SeedApi.csproj new file mode 100644 index 00000000000..bfc69cc48f9 --- /dev/null +++ b/seed/csharp-model/grpc-proto/src/SeedApi/SeedApi.csproj @@ -0,0 +1,45 @@ + + + + + net462;net8.0;net7.0;net6.0;netstandard2.0 + enable + false + 12 + enable + 0.0.1 + README.md + https://github.com/grpc-proto/fern + + + + true + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + + + + diff --git a/seed/csharp-model/grpc-proto/src/SeedApi/UserModel.cs b/seed/csharp-model/grpc-proto/src/SeedApi/UserModel.cs new file mode 100644 index 00000000000..11f6420ce23 --- /dev/null +++ b/seed/csharp-model/grpc-proto/src/SeedApi/UserModel.cs @@ -0,0 +1,23 @@ +using System.Text.Json.Serialization; + +#nullable enable + +namespace SeedApi; + +public record UserModel +{ + [JsonPropertyName("username")] + public string? Username { get; set; } + + [JsonPropertyName("email")] + public string? Email { get; set; } + + [JsonPropertyName("age")] + public uint? Age { get; set; } + + [JsonPropertyName("weight")] + public float? Weight { get; set; } + + [JsonPropertyName("metadata")] + public Dictionary? Metadata { get; set; } +} diff --git a/seed/csharp-model/seed.yml b/seed/csharp-model/seed.yml index c5093e356f7..4f980e1a803 100644 --- a/seed/csharp-model/seed.yml +++ b/seed/csharp-model/seed.yml @@ -15,4 +15,6 @@ local: - yarn workspace @fern-api/fern-csharp-model dist:cli runCommand: node model/dist/bundle.cjs allowedFailures: - - objects-with-imports \ No newline at end of file + - objects-with-imports + # TODO: Add support for recursive undiscriminated unions. + - grpc \ No newline at end of file diff --git a/seed/csharp-sdk/grpc-proto/.github/workflows/ci.yml b/seed/csharp-sdk/grpc-proto/.github/workflows/ci.yml new file mode 100644 index 00000000000..bc4fa1a98cb --- /dev/null +++ b/seed/csharp-sdk/grpc-proto/.github/workflows/ci.yml @@ -0,0 +1,69 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - uses: actions/checkout@master + + - name: Setup .NET + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 8.x + + - name: Install tools + run: | + dotnet tool restore + + - name: Build Release + run: dotnet build src -c Release /p:ContinuousIntegrationBuild=true + + unit-tests: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - uses: actions/checkout@master + + - name: Setup .NET + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 8.x + + - name: Install tools + run: | + dotnet tool restore + + - name: Run Tests + run: | + dotnet test src + + + publish: + needs: [compile] + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Setup .NET + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 8.x + + - name: Publish + env: + NUGET_API_KEY: ${{ secrets.NUGET_API_TOKEN }} + run: | + dotnet pack src -c Release + dotnet nuget push src/SeedApi/bin/Release/*.nupkg --api-key $NUGET_API_KEY --source "nuget.org" diff --git a/seed/csharp-sdk/grpc-proto/.gitignore b/seed/csharp-sdk/grpc-proto/.gitignore new file mode 100644 index 00000000000..9965de29662 --- /dev/null +++ b/seed/csharp-sdk/grpc-proto/.gitignore @@ -0,0 +1,477 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET +project.lock.json +project.fragment.lock.json +artifacts/ + +# Tye +.tye/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml + +## +## Visual studio for Mac +## + + +# globs +Makefile.in +*.userprefs +*.usertasks +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.tar.gz +tarballs/ +test-results/ + +# Mac bundle stuff +*.dmg +*.app + +# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk diff --git a/seed/csharp-sdk/grpc-proto/.mock/fern.config.json b/seed/csharp-sdk/grpc-proto/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/csharp-sdk/grpc-proto/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/csharp-sdk/grpc-proto/.mock/generators.yml b/seed/csharp-sdk/grpc-proto/.mock/generators.yml new file mode 100644 index 00000000000..902c4f50168 --- /dev/null +++ b/seed/csharp-sdk/grpc-proto/.mock/generators.yml @@ -0,0 +1,5 @@ +api: + - proto: + root: proto + target: proto/user/v1/user.proto + local-generation: true \ No newline at end of file diff --git a/seed/csharp-sdk/grpc-proto/.mock/proto/google/api/annotations.proto b/seed/csharp-sdk/grpc-proto/.mock/proto/google/api/annotations.proto new file mode 100644 index 00000000000..8ff42098404 --- /dev/null +++ b/seed/csharp-sdk/grpc-proto/.mock/proto/google/api/annotations.proto @@ -0,0 +1,31 @@ +// Copyright 2015 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/api/http.proto"; +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "AnnotationsProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // See `HttpRule`. + HttpRule http = 72295728; +} \ No newline at end of file diff --git a/seed/csharp-sdk/grpc-proto/.mock/proto/google/api/field_behavior.proto b/seed/csharp-sdk/grpc-proto/.mock/proto/google/api/field_behavior.proto new file mode 100644 index 00000000000..128799c558d --- /dev/null +++ b/seed/csharp-sdk/grpc-proto/.mock/proto/google/api/field_behavior.proto @@ -0,0 +1,104 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "FieldBehaviorProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.FieldOptions { + // A designation of a specific field behavior (required, output only, etc.) + // in protobuf messages. + // + // Examples: + // + // string name = 1 [(google.api.field_behavior) = REQUIRED]; + // State state = 1 [(google.api.field_behavior) = OUTPUT_ONLY]; + // google.protobuf.Duration ttl = 1 + // [(google.api.field_behavior) = INPUT_ONLY]; + // google.protobuf.Timestamp expire_time = 1 + // [(google.api.field_behavior) = OUTPUT_ONLY, + // (google.api.field_behavior) = IMMUTABLE]; + repeated google.api.FieldBehavior field_behavior = 1052; +} + +// An indicator of the behavior of a given field (for example, that a field +// is required in requests, or given as output but ignored as input). +// This **does not** change the behavior in protocol buffers itself; it only +// denotes the behavior and may affect how API tooling handles the field. +// +// Note: This enum **may** receive new values in the future. +enum FieldBehavior { + // Conventional default for enums. Do not use this. + FIELD_BEHAVIOR_UNSPECIFIED = 0; + + // Specifically denotes a field as optional. + // While all fields in protocol buffers are optional, this may be specified + // for emphasis if appropriate. + OPTIONAL = 1; + + // Denotes a field as required. + // This indicates that the field **must** be provided as part of the request, + // and failure to do so will cause an error (usually `INVALID_ARGUMENT`). + REQUIRED = 2; + + // Denotes a field as output only. + // This indicates that the field is provided in responses, but including the + // field in a request does nothing (the server *must* ignore it and + // *must not* throw an error as a result of the field's presence). + OUTPUT_ONLY = 3; + + // Denotes a field as input only. + // This indicates that the field is provided in requests, and the + // corresponding field is not included in output. + INPUT_ONLY = 4; + + // Denotes a field as immutable. + // This indicates that the field may be set once in a request to create a + // resource, but may not be changed thereafter. + IMMUTABLE = 5; + + // Denotes that a (repeated) field is an unordered list. + // This indicates that the service may provide the elements of the list + // in any arbitrary order, rather than the order the user originally + // provided. Additionally, the list's order may or may not be stable. + UNORDERED_LIST = 6; + + // Denotes that this field returns a non-empty default value if not set. + // This indicates that if the user provides the empty value in a request, + // a non-empty value will be returned. The user will not be aware of what + // non-empty value to expect. + NON_EMPTY_DEFAULT = 7; + + // Denotes that the field in a resource (a message annotated with + // google.api.resource) is used in the resource name to uniquely identify the + // resource. For AIP-compliant APIs, this should only be applied to the + // `name` field on the resource. + // + // This behavior should not be applied to references to other resources within + // the message. + // + // The identifier field of resources often have different field behavior + // depending on the request it is embedded in (e.g. for Create methods name + // is optional and unused, while for Update methods it is required). Instead + // of method-specific annotations, only `IDENTIFIER` is required. + IDENTIFIER = 8; +} \ No newline at end of file diff --git a/seed/csharp-sdk/grpc-proto/.mock/proto/google/api/http.proto b/seed/csharp-sdk/grpc-proto/.mock/proto/google/api/http.proto new file mode 100644 index 00000000000..c8392381eb9 --- /dev/null +++ b/seed/csharp-sdk/grpc-proto/.mock/proto/google/api/http.proto @@ -0,0 +1,379 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "HttpProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Defines the HTTP configuration for an API service. It contains a list of +// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method +// to one or more HTTP REST API methods. +message Http { + // A list of HTTP configuration rules that apply to individual API methods. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated HttpRule rules = 1; + + // When set to true, URL path parameters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // + // The default behavior is to not decode RFC 6570 reserved characters in multi + // segment matches. + bool fully_decode_reserved_expansion = 2; +} + +// # gRPC Transcoding +// +// gRPC Transcoding is a feature for mapping between a gRPC method and one or +// more HTTP REST endpoints. It allows developers to build a single API service +// that supports both gRPC APIs and REST APIs. Many systems, including [Google +// APIs](https://github.com/googleapis/googleapis), +// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC +// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), +// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature +// and use it for large scale production services. +// +// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies +// how different portions of the gRPC request message are mapped to the URL +// path, URL query parameters, and HTTP request body. It also controls how the +// gRPC response message is mapped to the HTTP response body. `HttpRule` is +// typically specified as an `google.api.http` annotation on the gRPC method. +// +// Each mapping specifies a URL path template and an HTTP method. The path +// template may refer to one or more fields in the gRPC request message, as long +// as each field is a non-repeated field with a primitive (non-message) type. +// The path template controls how fields of the request message are mapped to +// the URL path. +// +// Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/{name=messages/*}" +// }; +// } +// } +// message GetMessageRequest { +// string name = 1; // Mapped to URL path. +// } +// message Message { +// string text = 1; // The resource content. +// } +// +// This enables an HTTP REST to gRPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` +// +// Any fields in the request message which are not bound by the path template +// automatically become HTTP query parameters if there is no HTTP request body. +// For example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get:"/v1/messages/{message_id}" +// }; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // Mapped to URL path. +// int64 revision = 2; // Mapped to URL query parameter `revision`. +// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. +// } +// +// This enables a HTTP JSON to RPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | +// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: +// "foo"))` +// +// Note that fields which are mapped to URL query parameters must have a +// primitive type or a repeated primitive type or a non-repeated message type. +// In the case of a repeated type, the parameter can be repeated in the URL +// as `...?param=A¶m=B`. In the case of a message type, each field of the +// message is mapped to a separate parameter, such as +// `...?foo.a=A&foo.b=B&foo.c=C`. +// +// For HTTP methods that allow a request body, the `body` field +// specifies the mapping. Consider a REST update method on the +// message resource collection: +// +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } +// +// The following HTTP JSON to RPC mapping is enabled, where the +// representation of the JSON in the request body is determined by +// protos JSON encoding: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" message { text: "Hi!" })` +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. This enables the following alternative definition of +// the update method: +// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } +// +// +// The following HTTP JSON to RPC mapping is enabled: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" text: "Hi!")` +// +// Note that when using `*` in the body mapping, it is not possible to +// have HTTP parameters, as all fields not bound by the path end in +// the body. This makes this option more rarely used in practice when +// defining REST APIs. The common usage of `*` is in custom methods +// which don't use the URL at all for transferring data. +// +// It is possible to define multiple HTTP methods for one RPC by using +// the `additional_bindings` option. Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } +// +// This enables the following two alternative HTTP JSON to RPC mappings: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` +// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: +// "123456")` +// +// ## Rules for HTTP mapping +// +// 1. Leaf request fields (recursive expansion nested messages in the request +// message) are classified into three categories: +// - Fields referred by the path template. They are passed via the URL path. +// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They +// are passed via the HTTP +// request body. +// - All other fields are passed via the URL query parameters, and the +// parameter name is the field path in the request message. A repeated +// field can be represented as multiple query parameters under the same +// name. +// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL +// query parameter, all fields +// are passed via URL path and HTTP request body. +// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP +// request body, all +// fields are passed via URL path and URL query parameters. +// +// ### Path template syntax +// +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +// +// The syntax `*` matches a single URL path segment. The syntax `**` matches +// zero or more URL path segments, which must be the last part of the URL path +// except the `Verb`. +// +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable +// matches a single path segment, its template may be omitted, e.g. `{var}` +// is equivalent to `{var=*}`. +// +// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` +// contains any reserved character, such characters should be percent-encoded +// before the matching. +// +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path on the client +// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The +// server side does the reverse decoding. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{var}`. +// +// If a variable contains multiple path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path on the +// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. +// The server side does the reverse decoding, except "%2F" and "%2f" are left +// unchanged. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{+var}`. +// +// ## Using gRPC API Service Configuration +// +// gRPC API Service Configuration (service config) is a configuration language +// for configuring a gRPC service to become a user-facing product. The +// service config is simply the YAML representation of the `google.api.Service` +// proto message. +// +// As an alternative to annotating your proto file, you can configure gRPC +// transcoding in your service config YAML files. You do this by specifying a +// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same +// effect as the proto annotation. This can be particularly useful if you +// have a proto that is reused in multiple services. Note that any transcoding +// specified in the service config will override any matching transcoding +// configuration in the proto. +// +// Example: +// +// http: +// rules: +// # Selects a gRPC method and applies HttpRule to it. +// - selector: example.v1.Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} +// +// ## Special notes +// +// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the +// proto to JSON conversion must follow the [proto3 +// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). +// +// While the single segment variable follows the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String +// Expansion, the multi segment variable **does not** follow RFC 6570 Section +// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding +// for multi segment variables. +// +// The path variables **must not** refer to any repeated or mapped field, +// because client libraries are not capable of handling such variable expansion. +// +// The path variables **must not** capture the leading "/" character. The reason +// is that the most common use case "{var}" does not capture the leading "/" +// character. For consistency, all path variables must share the same behavior. +// +// Repeated message fields must not be mapped to URL query parameters, because +// no client library can support such complicated mapping. +// +// If an API needs to use a JSON array for request or response body, it can map +// the request or response body to a repeated field. However, some gRPC +// Transcoding implementations may not support this feature. +message HttpRule { + // Selects a method to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax + // details. + string selector = 1; + + // Determines the URL pattern is matched by this rules. This pattern can be + // used with any of the {get|put|post|delete|patch} methods. A custom method + // can be defined using the 'custom' field. + oneof pattern { + // Maps to HTTP GET. Used for listing and getting information about + // resources. + string get = 2; + + // Maps to HTTP PUT. Used for replacing a resource. + string put = 3; + + // Maps to HTTP POST. Used for creating a resource or performing an action. + string post = 4; + + // Maps to HTTP DELETE. Used for deleting a resource. + string delete = 5; + + // Maps to HTTP PATCH. Used for updating a resource. + string patch = 6; + + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. + CustomHttpPattern custom = 8; + } + + // The name of the request field whose value is mapped to the HTTP request + // body, or `*` for mapping all request fields not captured by the path + // pattern to the HTTP body, or omitted for not having any HTTP request body. + // + // NOTE: the referred field must be present at the top-level of the request + // message type. + string body = 7; + + // Optional. The name of the response field whose value is mapped to the HTTP + // response body. When omitted, the entire response message will be used + // as the HTTP response body. + // + // NOTE: The referred field must be present at the top-level of the response + // message type. + string response_body = 12; + + // Additional HTTP bindings for the selector. Nested bindings must + // not contain an `additional_bindings` field themselves (that is, + // the nesting may only be one level deep). + repeated HttpRule additional_bindings = 11; +} + +// A custom pattern is used for defining custom HTTP verb. +message CustomHttpPattern { + // The name of this custom HTTP verb. + string kind = 1; + + // The path matched by this custom verb. + string path = 2; +} \ No newline at end of file diff --git a/seed/csharp-sdk/grpc-proto/.mock/proto/user/v1/user.proto b/seed/csharp-sdk/grpc-proto/.mock/proto/user/v1/user.proto new file mode 100644 index 00000000000..a3ad0195bd4 --- /dev/null +++ b/seed/csharp-sdk/grpc-proto/.mock/proto/user/v1/user.proto @@ -0,0 +1,39 @@ +syntax = "proto3"; + +package user.v1; + +import "google/api/annotations.proto"; +import "google/api/field_behavior.proto"; +import "google/protobuf/struct.proto"; + +option go_package = "user/v1"; +option csharp_namespace = "User.V1"; + +message UserModel { + string username = 1; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateRequest { + string username = 1; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateResponse { + UserModel user = 1; +} + +service User { + rpc Create(CreateRequest) returns (CreateResponse) { + option (google.api.http) = { + post: "/users" + body: "*" + }; + } +} \ No newline at end of file diff --git a/seed/csharp-sdk/grpc-proto/snippet-templates.json b/seed/csharp-sdk/grpc-proto/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/csharp-sdk/grpc-proto/snippet.json b/seed/csharp-sdk/grpc-proto/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/csharp-sdk/grpc-proto/src/SeedApi.Test/SeedApi.Test.csproj b/seed/csharp-sdk/grpc-proto/src/SeedApi.Test/SeedApi.Test.csproj new file mode 100644 index 00000000000..fa0676afacd --- /dev/null +++ b/seed/csharp-sdk/grpc-proto/src/SeedApi.Test/SeedApi.Test.csproj @@ -0,0 +1,24 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/seed/csharp-sdk/grpc-proto/src/SeedApi.Test/TestClient.cs b/seed/csharp-sdk/grpc-proto/src/SeedApi.Test/TestClient.cs new file mode 100644 index 00000000000..f1550b51fff --- /dev/null +++ b/seed/csharp-sdk/grpc-proto/src/SeedApi.Test/TestClient.cs @@ -0,0 +1,8 @@ +using NUnit.Framework; + +#nullable enable + +namespace SeedApi.Test; + +[TestFixture] +public class TestClient { } diff --git a/seed/csharp-sdk/grpc-proto/src/SeedApi/Core/ClientOptions.cs b/seed/csharp-sdk/grpc-proto/src/SeedApi/Core/ClientOptions.cs new file mode 100644 index 00000000000..10bdd2e095f --- /dev/null +++ b/seed/csharp-sdk/grpc-proto/src/SeedApi/Core/ClientOptions.cs @@ -0,0 +1,29 @@ +using System; +using System.Net.Http; + +#nullable enable + +namespace SeedApi.Core; + +public partial class ClientOptions +{ + /// + /// The Base URL for the API. + /// + public string BaseUrl { get; init; } = ""; + + /// + /// The http client used to make requests. + /// + public HttpClient HttpClient { get; init; } = new HttpClient(); + + /// + /// The http client used to make requests. + /// + public int MaxRetries { get; init; } = 2; + + /// + /// The timeout for the request. + /// + public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(30); +} diff --git a/seed/csharp-sdk/grpc-proto/src/SeedApi/Core/CollectionItemSerializer.cs b/seed/csharp-sdk/grpc-proto/src/SeedApi/Core/CollectionItemSerializer.cs new file mode 100644 index 00000000000..2e1e0f550be --- /dev/null +++ b/seed/csharp-sdk/grpc-proto/src/SeedApi/Core/CollectionItemSerializer.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace SeedApi.Core; + +/// +/// Json collection converter. +/// +/// Type of item to convert. +/// Converter to use for individual items. +public class CollectionItemSerializer + : JsonConverter> + where TConverterType : JsonConverter +{ + /// + /// Reads a json string and deserializes it into an object. + /// + /// Json reader. + /// Type to convert. + /// Serializer options. + /// Created object. + public override IEnumerable? Read( + ref Utf8JsonReader reader, + System.Type typeToConvert, + JsonSerializerOptions options + ) + { + if (reader.TokenType == JsonTokenType.Null) + { + return default; + } + + var jsonSerializerOptions = new JsonSerializerOptions(options); + jsonSerializerOptions.Converters.Clear(); + jsonSerializerOptions.Converters.Add(Activator.CreateInstance()); + + var returnValue = new List(); + + while (reader.TokenType != JsonTokenType.EndArray) + { + if (reader.TokenType != JsonTokenType.StartArray) + { + var item = (TDatatype)( + JsonSerializer.Deserialize(ref reader, typeof(TDatatype), jsonSerializerOptions) + ?? throw new Exception( + $"Failed to deserialize collection item of type {typeof(TDatatype)}" + ) + ); + returnValue.Add(item); + } + + reader.Read(); + } + + return returnValue; + } + + /// + /// Writes a json string. + /// + /// Json writer. + /// Value to write. + /// Serializer options. + public override void Write( + Utf8JsonWriter writer, + IEnumerable? value, + JsonSerializerOptions options + ) + { + if (value == null) + { + writer.WriteNullValue(); + return; + } + + JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions(options); + jsonSerializerOptions.Converters.Clear(); + jsonSerializerOptions.Converters.Add(Activator.CreateInstance()); + + writer.WriteStartArray(); + + foreach (var data in value) + { + JsonSerializer.Serialize(writer, data, jsonSerializerOptions); + } + + writer.WriteEndArray(); + } +} diff --git a/seed/csharp-sdk/grpc-proto/src/SeedApi/Core/Constants.cs b/seed/csharp-sdk/grpc-proto/src/SeedApi/Core/Constants.cs new file mode 100644 index 00000000000..676d472ce33 --- /dev/null +++ b/seed/csharp-sdk/grpc-proto/src/SeedApi/Core/Constants.cs @@ -0,0 +1,6 @@ +namespace SeedApi.Core; + +public static class Constants +{ + public const string DateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fffK"; +} diff --git a/seed/csharp-sdk/grpc-proto/src/SeedApi/Core/DateTimeSerializer.cs b/seed/csharp-sdk/grpc-proto/src/SeedApi/Core/DateTimeSerializer.cs new file mode 100644 index 00000000000..7ca41fcd279 --- /dev/null +++ b/seed/csharp-sdk/grpc-proto/src/SeedApi/Core/DateTimeSerializer.cs @@ -0,0 +1,22 @@ +using System.Globalization; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace SeedApi.Core; + +public class DateTimeSerializer : JsonConverter +{ + public override DateTime Read( + ref Utf8JsonReader reader, + System.Type typeToConvert, + JsonSerializerOptions options + ) + { + return DateTime.Parse(reader.GetString()!, null, DateTimeStyles.RoundtripKind); + } + + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString(Constants.DateTimeFormat)); + } +} diff --git a/seed/csharp-sdk/grpc-proto/src/SeedApi/Core/HttpMethodExtensions.cs b/seed/csharp-sdk/grpc-proto/src/SeedApi/Core/HttpMethodExtensions.cs new file mode 100644 index 00000000000..66ee2e9d252 --- /dev/null +++ b/seed/csharp-sdk/grpc-proto/src/SeedApi/Core/HttpMethodExtensions.cs @@ -0,0 +1,8 @@ +using System.Net.Http; + +namespace SeedApi.Core; + +public static class HttpMethodExtensions +{ + public static readonly HttpMethod Patch = new("PATCH"); +} diff --git a/seed/csharp-sdk/grpc-proto/src/SeedApi/Core/JsonConfiguration.cs b/seed/csharp-sdk/grpc-proto/src/SeedApi/Core/JsonConfiguration.cs new file mode 100644 index 00000000000..0c7db293eb9 --- /dev/null +++ b/seed/csharp-sdk/grpc-proto/src/SeedApi/Core/JsonConfiguration.cs @@ -0,0 +1,30 @@ +using System.Text.Json; + +namespace SeedApi.Core; + +public static class JsonOptions +{ + public static readonly JsonSerializerOptions JsonSerializerOptions; + + static JsonOptions() + { + JsonSerializerOptions = new JsonSerializerOptions + { + Converters = { new DateTimeSerializer() }, + WriteIndented = true + }; + } +} + +public static class JsonUtils +{ + public static string Serialize(T obj) + { + return JsonSerializer.Serialize(obj, JsonOptions.JsonSerializerOptions); + } + + public static T Deserialize(string json) + { + return JsonSerializer.Deserialize(json, JsonOptions.JsonSerializerOptions)!; + } +} diff --git a/seed/csharp-sdk/grpc-proto/src/SeedApi/Core/OneOfSerializer.cs b/seed/csharp-sdk/grpc-proto/src/SeedApi/Core/OneOfSerializer.cs new file mode 100644 index 00000000000..d86f8e24e70 --- /dev/null +++ b/seed/csharp-sdk/grpc-proto/src/SeedApi/Core/OneOfSerializer.cs @@ -0,0 +1,67 @@ +using System.Reflection; +using System.Text.Json; +using System.Text.Json.Serialization; +using OneOf; + +namespace SeedApi.Core; + +public class OneOfSerializer : JsonConverter + where TOneOf : IOneOf +{ + public override TOneOf? Read( + ref Utf8JsonReader reader, + System.Type typeToConvert, + JsonSerializerOptions options + ) + { + if (reader.TokenType is JsonTokenType.Null) + return default; + + foreach (var (type, cast) in s_types) + { + try + { + var readerCopy = reader; + var result = JsonSerializer.Deserialize(ref readerCopy, type, options); + reader.Skip(); + return (TOneOf)cast.Invoke(null, [result])!; + } + catch (JsonException) { } + } + + throw new JsonException( + $"Cannot deserialize into one of the supported types for {typeToConvert}" + ); + } + + private static readonly (System.Type type, MethodInfo cast)[] s_types = GetOneOfTypes(); + + public override void Write(Utf8JsonWriter writer, TOneOf value, JsonSerializerOptions options) + { + JsonSerializer.Serialize(writer, value.Value, options); + } + + private static (System.Type type, MethodInfo cast)[] GetOneOfTypes() + { + var casts = typeof(TOneOf) + .GetRuntimeMethods() + .Where(m => m.IsSpecialName && m.Name == "op_Implicit") + .ToArray(); + var type = typeof(TOneOf); + while (type != null) + { + if ( + type.IsGenericType + && (type.Name.StartsWith("OneOf`") || type.Name.StartsWith("OneOfBase`")) + ) + { + return type.GetGenericArguments() + .Select(t => (t, casts.First(c => c.GetParameters()[0].ParameterType == t))) + .ToArray(); + } + + type = type.BaseType; + } + throw new InvalidOperationException($"{typeof(TOneOf)} isn't OneOf or OneOfBase"); + } +} diff --git a/seed/csharp-sdk/grpc-proto/src/SeedApi/Core/RawClient.cs b/seed/csharp-sdk/grpc-proto/src/SeedApi/Core/RawClient.cs new file mode 100644 index 00000000000..721c37a39c2 --- /dev/null +++ b/seed/csharp-sdk/grpc-proto/src/SeedApi/Core/RawClient.cs @@ -0,0 +1,149 @@ +using System.Net.Http; +using System.Text; + +namespace SeedApi.Core; + +#nullable enable + +/// +/// Utility class for making raw HTTP requests to the API. +/// +public class RawClient( + Dictionary headers, + Dictionary> headerSuppliers, + ClientOptions clientOptions +) +{ + /// + /// The http client used to make requests. + /// + public readonly ClientOptions Options = clientOptions; + + /// + /// Global headers to be sent with every request. + /// + private readonly Dictionary _headers = headers; + + public async Task MakeRequestAsync(BaseApiRequest request) + { + var url = BuildUrl(request); + var httpRequest = new HttpRequestMessage(request.Method, url); + if (request.ContentType != null) + { + request.Headers.Add("Content-Type", request.ContentType); + } + // Add global headers to the request + foreach (var header in _headers) + { + httpRequest.Headers.Add(header.Key, header.Value); + } + // Add global headers to the request from supplier + foreach (var header in headerSuppliers) + { + httpRequest.Headers.Add(header.Key, header.Value.Invoke()); + } + // Add request headers to the request + foreach (var header in request.Headers) + { + httpRequest.Headers.Add(header.Key, header.Value); + } + // Add the request body to the request + if (request is JsonApiRequest jsonRequest) + { + if (jsonRequest.Body != null) + { + httpRequest.Content = new StringContent( + JsonUtils.Serialize(jsonRequest.Body), + Encoding.UTF8, + "application/json" + ); + } + } + else if (request is StreamApiRequest { Body: not null } streamRequest) + { + httpRequest.Content = new StreamContent(streamRequest.Body); + } + // Send the request + var httpClient = request.Options?.HttpClient ?? Options.HttpClient; + var response = await httpClient.SendAsync(httpRequest); + return new ApiResponse { StatusCode = (int)response.StatusCode, Raw = response }; + } + + public record BaseApiRequest + { + public required string BaseUrl { get; init; } + + public required HttpMethod Method { get; init; } + + public required string Path { get; init; } + + public string? ContentType { get; init; } + + public Dictionary Query { get; init; } = new(); + + public Dictionary Headers { get; init; } = new(); + + public RequestOptions? Options { get; init; } + } + + /// + /// The request object to be sent for streaming uploads. + /// + public record StreamApiRequest : BaseApiRequest + { + public Stream? Body { get; init; } + } + + /// + /// The request object to be sent for JSON APIs. + /// + public record JsonApiRequest : BaseApiRequest + { + public object? Body { get; init; } + } + + /// + /// The response object returned from the API. + /// + public record ApiResponse + { + public required int StatusCode { get; init; } + + public required HttpResponseMessage Raw { get; init; } + } + + private string BuildUrl(BaseApiRequest request) + { + var baseUrl = request.Options?.BaseUrl ?? request.BaseUrl; + var trimmedBaseUrl = baseUrl.TrimEnd('/'); + var trimmedBasePath = request.Path.TrimStart('/'); + var url = $"{trimmedBaseUrl}/{trimmedBasePath}"; + if (request.Query.Count <= 0) + return url; + url += "?"; + url = request.Query.Aggregate( + url, + (current, queryItem) => + { + if (queryItem.Value is System.Collections.IEnumerable collection and not string) + { + var items = collection + .Cast() + .Select(value => $"{queryItem.Key}={value}") + .ToList(); + if (items.Any()) + { + current += string.Join("&", items) + "&"; + } + } + else + { + current += $"{queryItem.Key}={queryItem.Value}&"; + } + return current; + } + ); + url = url.Substring(0, url.Length - 1); + return url; + } +} diff --git a/seed/csharp-sdk/grpc-proto/src/SeedApi/Core/RequestOptions.cs b/seed/csharp-sdk/grpc-proto/src/SeedApi/Core/RequestOptions.cs new file mode 100644 index 00000000000..e757fd480b4 --- /dev/null +++ b/seed/csharp-sdk/grpc-proto/src/SeedApi/Core/RequestOptions.cs @@ -0,0 +1,29 @@ +using System; +using System.Net.Http; + +#nullable enable + +namespace SeedApi.Core; + +public partial class RequestOptions +{ + /// + /// The Base URL for the API. + /// + public string? BaseUrl { get; init; } + + /// + /// The http client used to make requests. + /// + public HttpClient? HttpClient { get; init; } + + /// + /// The http client used to make requests. + /// + public int? MaxRetries { get; init; } + + /// + /// The timeout for the request. + /// + public TimeSpan? Timeout { get; init; } +} diff --git a/seed/csharp-sdk/grpc-proto/src/SeedApi/Core/SeedApiApiException.cs b/seed/csharp-sdk/grpc-proto/src/SeedApi/Core/SeedApiApiException.cs new file mode 100644 index 00000000000..7aca6541a23 --- /dev/null +++ b/seed/csharp-sdk/grpc-proto/src/SeedApi/Core/SeedApiApiException.cs @@ -0,0 +1,27 @@ +using SeedApi.Core; + +#nullable enable + +namespace SeedApi.Core; + +/// +/// This exception type will be thrown for any non-2XX API responses. +/// +public class SeedApiApiException(string message, int statusCode, object body) + : SeedApiException(message) +{ + /// + /// The error code of the response that triggered the exception. + /// + public int StatusCode { get; } = statusCode; + + /// + /// The body of the response that triggered the exception. + /// + public object Body { get; } = body; + + public override string ToString() + { + return $"SeedApiApiException {{ message: {Message}, statusCode: {StatusCode}, body: {Body} }}"; + } +} diff --git a/seed/csharp-sdk/grpc-proto/src/SeedApi/Core/SeedApiException.cs b/seed/csharp-sdk/grpc-proto/src/SeedApi/Core/SeedApiException.cs new file mode 100644 index 00000000000..ffbaa1c0718 --- /dev/null +++ b/seed/csharp-sdk/grpc-proto/src/SeedApi/Core/SeedApiException.cs @@ -0,0 +1,11 @@ +using System; + +#nullable enable + +namespace SeedApi.Core; + +/// +/// Base exception class for all exceptions thrown by the SDK. +/// +public class SeedApiException(string message, Exception? innerException = null) + : Exception(message, innerException) { } diff --git a/seed/csharp-sdk/grpc-proto/src/SeedApi/Core/StringEnumSerializer.cs b/seed/csharp-sdk/grpc-proto/src/SeedApi/Core/StringEnumSerializer.cs new file mode 100644 index 00000000000..fbaf79b865e --- /dev/null +++ b/seed/csharp-sdk/grpc-proto/src/SeedApi/Core/StringEnumSerializer.cs @@ -0,0 +1,53 @@ +using System.Runtime.Serialization; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace SeedApi.Core; + +public class StringEnumSerializer : JsonConverter + where TEnum : struct, System.Enum +{ + private readonly Dictionary _enumToString = new(); + private readonly Dictionary _stringToEnum = new(); + + public StringEnumSerializer() + { + var type = typeof(TEnum); + var values = Enum.GetValues(type); + + foreach (var value in values) + { + var enumValue = (TEnum)value; + var enumMember = type.GetMember(enumValue.ToString())[0]; + var attr = enumMember + .GetCustomAttributes(typeof(EnumMemberAttribute), false) + .Cast() + .FirstOrDefault(); + + var stringValue = + attr?.Value + ?? value.ToString() + ?? throw new Exception("Unexpected null enum toString value"); + + _enumToString.Add(enumValue, stringValue); + _stringToEnum.Add(stringValue, enumValue); + } + } + + public override TEnum Read( + ref Utf8JsonReader reader, + System.Type typeToConvert, + JsonSerializerOptions options + ) + { + var stringValue = + reader.GetString() + ?? throw new Exception("The JSON value could not be read as a string."); + return _stringToEnum.TryGetValue(stringValue, out var enumValue) ? enumValue : default; + } + + public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options) + { + writer.WriteStringValue(_enumToString[value]); + } +} diff --git a/seed/csharp-sdk/grpc-proto/src/SeedApi/SeedApi.csproj b/seed/csharp-sdk/grpc-proto/src/SeedApi/SeedApi.csproj new file mode 100644 index 00000000000..bfc69cc48f9 --- /dev/null +++ b/seed/csharp-sdk/grpc-proto/src/SeedApi/SeedApi.csproj @@ -0,0 +1,45 @@ + + + + + net462;net8.0;net7.0;net6.0;netstandard2.0 + enable + false + 12 + enable + 0.0.1 + README.md + https://github.com/grpc-proto/fern + + + + true + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + + + + diff --git a/seed/csharp-sdk/grpc-proto/src/SeedApi/SeedApiClient.cs b/seed/csharp-sdk/grpc-proto/src/SeedApi/SeedApiClient.cs new file mode 100644 index 00000000000..ff1ce9dc7d6 --- /dev/null +++ b/seed/csharp-sdk/grpc-proto/src/SeedApi/SeedApiClient.cs @@ -0,0 +1,24 @@ +using System; +using SeedApi; +using SeedApi.Core; + +#nullable enable + +namespace SeedApi; + +public partial class SeedApiClient +{ + private RawClient _client; + + public SeedApiClient(ClientOptions? clientOptions = null) + { + _client = new RawClient( + new Dictionary() { { "X-Fern-Language", "C#" }, }, + new Dictionary>() { }, + clientOptions ?? new ClientOptions() + ); + User = new UserClient(_client); + } + + public UserClient User { get; init; } +} diff --git a/seed/csharp-sdk/grpc-proto/src/SeedApi/Types/CreateResponse.cs b/seed/csharp-sdk/grpc-proto/src/SeedApi/Types/CreateResponse.cs new file mode 100644 index 00000000000..0374ef2a9b7 --- /dev/null +++ b/seed/csharp-sdk/grpc-proto/src/SeedApi/Types/CreateResponse.cs @@ -0,0 +1,12 @@ +using System.Text.Json.Serialization; +using SeedApi; + +#nullable enable + +namespace SeedApi; + +public record CreateResponse +{ + [JsonPropertyName("user")] + public UserModel? User { get; set; } +} diff --git a/seed/csharp-sdk/grpc-proto/src/SeedApi/Types/UserModel.cs b/seed/csharp-sdk/grpc-proto/src/SeedApi/Types/UserModel.cs new file mode 100644 index 00000000000..11f6420ce23 --- /dev/null +++ b/seed/csharp-sdk/grpc-proto/src/SeedApi/Types/UserModel.cs @@ -0,0 +1,23 @@ +using System.Text.Json.Serialization; + +#nullable enable + +namespace SeedApi; + +public record UserModel +{ + [JsonPropertyName("username")] + public string? Username { get; set; } + + [JsonPropertyName("email")] + public string? Email { get; set; } + + [JsonPropertyName("age")] + public uint? Age { get; set; } + + [JsonPropertyName("weight")] + public float? Weight { get; set; } + + [JsonPropertyName("metadata")] + public Dictionary? Metadata { get; set; } +} diff --git a/seed/csharp-sdk/grpc-proto/src/SeedApi/User/Requests/CreateRequest.cs b/seed/csharp-sdk/grpc-proto/src/SeedApi/User/Requests/CreateRequest.cs new file mode 100644 index 00000000000..2b096055e3f --- /dev/null +++ b/seed/csharp-sdk/grpc-proto/src/SeedApi/User/Requests/CreateRequest.cs @@ -0,0 +1,23 @@ +using System.Text.Json.Serialization; + +#nullable enable + +namespace SeedApi; + +public record CreateRequest +{ + [JsonPropertyName("username")] + public string? Username { get; set; } + + [JsonPropertyName("email")] + public string? Email { get; set; } + + [JsonPropertyName("age")] + public uint? Age { get; set; } + + [JsonPropertyName("weight")] + public float? Weight { get; set; } + + [JsonPropertyName("metadata")] + public Dictionary? Metadata { get; set; } +} diff --git a/seed/csharp-sdk/grpc-proto/src/SeedApi/User/UserClient.cs b/seed/csharp-sdk/grpc-proto/src/SeedApi/User/UserClient.cs new file mode 100644 index 00000000000..60de74413ef --- /dev/null +++ b/seed/csharp-sdk/grpc-proto/src/SeedApi/User/UserClient.cs @@ -0,0 +1,53 @@ +using System.Net.Http; +using System.Text.Json; +using SeedApi; +using SeedApi.Core; + +#nullable enable + +namespace SeedApi; + +public class UserClient +{ + private RawClient _client; + + public UserClient(RawClient client) + { + _client = client; + } + + public async Task CreateAsync( + CreateRequest request, + RequestOptions? options = null + ) + { + var response = await _client.MakeRequestAsync( + new RawClient.JsonApiRequest + { + BaseUrl = _client.Options.BaseUrl, + Method = HttpMethod.Post, + Path = "users", + Body = request, + Options = options + } + ); + var responseBody = await response.Raw.Content.ReadAsStringAsync(); + if (response.StatusCode is >= 200 and < 400) + { + try + { + return JsonUtils.Deserialize(responseBody)!; + } + catch (JsonException e) + { + throw new SeedApiException("Failed to deserialize response", e); + } + } + + throw new SeedApiApiException( + $"Error with status code {response.StatusCode}", + response.StatusCode, + JsonUtils.Deserialize(responseBody) + ); + } +} diff --git a/seed/csharp-sdk/seed.yml b/seed/csharp-sdk/seed.yml index 41d18485ad4..487a13976df 100644 --- a/seed/csharp-sdk/seed.yml +++ b/seed/csharp-sdk/seed.yml @@ -34,4 +34,5 @@ fixtures: allowedFailures: - objects-with-imports - examples - + # TODO: Add support for recursive undiscriminated unions. + - grpc diff --git a/seed/fastapi/grpc-proto/.mock/fern.config.json b/seed/fastapi/grpc-proto/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/fastapi/grpc-proto/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/fastapi/grpc-proto/.mock/generators.yml b/seed/fastapi/grpc-proto/.mock/generators.yml new file mode 100644 index 00000000000..902c4f50168 --- /dev/null +++ b/seed/fastapi/grpc-proto/.mock/generators.yml @@ -0,0 +1,5 @@ +api: + - proto: + root: proto + target: proto/user/v1/user.proto + local-generation: true \ No newline at end of file diff --git a/seed/fastapi/grpc-proto/.mock/proto/google/api/annotations.proto b/seed/fastapi/grpc-proto/.mock/proto/google/api/annotations.proto new file mode 100644 index 00000000000..8ff42098404 --- /dev/null +++ b/seed/fastapi/grpc-proto/.mock/proto/google/api/annotations.proto @@ -0,0 +1,31 @@ +// Copyright 2015 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/api/http.proto"; +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "AnnotationsProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // See `HttpRule`. + HttpRule http = 72295728; +} \ No newline at end of file diff --git a/seed/fastapi/grpc-proto/.mock/proto/google/api/field_behavior.proto b/seed/fastapi/grpc-proto/.mock/proto/google/api/field_behavior.proto new file mode 100644 index 00000000000..128799c558d --- /dev/null +++ b/seed/fastapi/grpc-proto/.mock/proto/google/api/field_behavior.proto @@ -0,0 +1,104 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "FieldBehaviorProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.FieldOptions { + // A designation of a specific field behavior (required, output only, etc.) + // in protobuf messages. + // + // Examples: + // + // string name = 1 [(google.api.field_behavior) = REQUIRED]; + // State state = 1 [(google.api.field_behavior) = OUTPUT_ONLY]; + // google.protobuf.Duration ttl = 1 + // [(google.api.field_behavior) = INPUT_ONLY]; + // google.protobuf.Timestamp expire_time = 1 + // [(google.api.field_behavior) = OUTPUT_ONLY, + // (google.api.field_behavior) = IMMUTABLE]; + repeated google.api.FieldBehavior field_behavior = 1052; +} + +// An indicator of the behavior of a given field (for example, that a field +// is required in requests, or given as output but ignored as input). +// This **does not** change the behavior in protocol buffers itself; it only +// denotes the behavior and may affect how API tooling handles the field. +// +// Note: This enum **may** receive new values in the future. +enum FieldBehavior { + // Conventional default for enums. Do not use this. + FIELD_BEHAVIOR_UNSPECIFIED = 0; + + // Specifically denotes a field as optional. + // While all fields in protocol buffers are optional, this may be specified + // for emphasis if appropriate. + OPTIONAL = 1; + + // Denotes a field as required. + // This indicates that the field **must** be provided as part of the request, + // and failure to do so will cause an error (usually `INVALID_ARGUMENT`). + REQUIRED = 2; + + // Denotes a field as output only. + // This indicates that the field is provided in responses, but including the + // field in a request does nothing (the server *must* ignore it and + // *must not* throw an error as a result of the field's presence). + OUTPUT_ONLY = 3; + + // Denotes a field as input only. + // This indicates that the field is provided in requests, and the + // corresponding field is not included in output. + INPUT_ONLY = 4; + + // Denotes a field as immutable. + // This indicates that the field may be set once in a request to create a + // resource, but may not be changed thereafter. + IMMUTABLE = 5; + + // Denotes that a (repeated) field is an unordered list. + // This indicates that the service may provide the elements of the list + // in any arbitrary order, rather than the order the user originally + // provided. Additionally, the list's order may or may not be stable. + UNORDERED_LIST = 6; + + // Denotes that this field returns a non-empty default value if not set. + // This indicates that if the user provides the empty value in a request, + // a non-empty value will be returned. The user will not be aware of what + // non-empty value to expect. + NON_EMPTY_DEFAULT = 7; + + // Denotes that the field in a resource (a message annotated with + // google.api.resource) is used in the resource name to uniquely identify the + // resource. For AIP-compliant APIs, this should only be applied to the + // `name` field on the resource. + // + // This behavior should not be applied to references to other resources within + // the message. + // + // The identifier field of resources often have different field behavior + // depending on the request it is embedded in (e.g. for Create methods name + // is optional and unused, while for Update methods it is required). Instead + // of method-specific annotations, only `IDENTIFIER` is required. + IDENTIFIER = 8; +} \ No newline at end of file diff --git a/seed/fastapi/grpc-proto/.mock/proto/google/api/http.proto b/seed/fastapi/grpc-proto/.mock/proto/google/api/http.proto new file mode 100644 index 00000000000..c8392381eb9 --- /dev/null +++ b/seed/fastapi/grpc-proto/.mock/proto/google/api/http.proto @@ -0,0 +1,379 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "HttpProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Defines the HTTP configuration for an API service. It contains a list of +// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method +// to one or more HTTP REST API methods. +message Http { + // A list of HTTP configuration rules that apply to individual API methods. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated HttpRule rules = 1; + + // When set to true, URL path parameters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // + // The default behavior is to not decode RFC 6570 reserved characters in multi + // segment matches. + bool fully_decode_reserved_expansion = 2; +} + +// # gRPC Transcoding +// +// gRPC Transcoding is a feature for mapping between a gRPC method and one or +// more HTTP REST endpoints. It allows developers to build a single API service +// that supports both gRPC APIs and REST APIs. Many systems, including [Google +// APIs](https://github.com/googleapis/googleapis), +// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC +// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), +// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature +// and use it for large scale production services. +// +// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies +// how different portions of the gRPC request message are mapped to the URL +// path, URL query parameters, and HTTP request body. It also controls how the +// gRPC response message is mapped to the HTTP response body. `HttpRule` is +// typically specified as an `google.api.http` annotation on the gRPC method. +// +// Each mapping specifies a URL path template and an HTTP method. The path +// template may refer to one or more fields in the gRPC request message, as long +// as each field is a non-repeated field with a primitive (non-message) type. +// The path template controls how fields of the request message are mapped to +// the URL path. +// +// Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/{name=messages/*}" +// }; +// } +// } +// message GetMessageRequest { +// string name = 1; // Mapped to URL path. +// } +// message Message { +// string text = 1; // The resource content. +// } +// +// This enables an HTTP REST to gRPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` +// +// Any fields in the request message which are not bound by the path template +// automatically become HTTP query parameters if there is no HTTP request body. +// For example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get:"/v1/messages/{message_id}" +// }; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // Mapped to URL path. +// int64 revision = 2; // Mapped to URL query parameter `revision`. +// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. +// } +// +// This enables a HTTP JSON to RPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | +// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: +// "foo"))` +// +// Note that fields which are mapped to URL query parameters must have a +// primitive type or a repeated primitive type or a non-repeated message type. +// In the case of a repeated type, the parameter can be repeated in the URL +// as `...?param=A¶m=B`. In the case of a message type, each field of the +// message is mapped to a separate parameter, such as +// `...?foo.a=A&foo.b=B&foo.c=C`. +// +// For HTTP methods that allow a request body, the `body` field +// specifies the mapping. Consider a REST update method on the +// message resource collection: +// +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } +// +// The following HTTP JSON to RPC mapping is enabled, where the +// representation of the JSON in the request body is determined by +// protos JSON encoding: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" message { text: "Hi!" })` +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. This enables the following alternative definition of +// the update method: +// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } +// +// +// The following HTTP JSON to RPC mapping is enabled: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" text: "Hi!")` +// +// Note that when using `*` in the body mapping, it is not possible to +// have HTTP parameters, as all fields not bound by the path end in +// the body. This makes this option more rarely used in practice when +// defining REST APIs. The common usage of `*` is in custom methods +// which don't use the URL at all for transferring data. +// +// It is possible to define multiple HTTP methods for one RPC by using +// the `additional_bindings` option. Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } +// +// This enables the following two alternative HTTP JSON to RPC mappings: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` +// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: +// "123456")` +// +// ## Rules for HTTP mapping +// +// 1. Leaf request fields (recursive expansion nested messages in the request +// message) are classified into three categories: +// - Fields referred by the path template. They are passed via the URL path. +// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They +// are passed via the HTTP +// request body. +// - All other fields are passed via the URL query parameters, and the +// parameter name is the field path in the request message. A repeated +// field can be represented as multiple query parameters under the same +// name. +// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL +// query parameter, all fields +// are passed via URL path and HTTP request body. +// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP +// request body, all +// fields are passed via URL path and URL query parameters. +// +// ### Path template syntax +// +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +// +// The syntax `*` matches a single URL path segment. The syntax `**` matches +// zero or more URL path segments, which must be the last part of the URL path +// except the `Verb`. +// +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable +// matches a single path segment, its template may be omitted, e.g. `{var}` +// is equivalent to `{var=*}`. +// +// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` +// contains any reserved character, such characters should be percent-encoded +// before the matching. +// +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path on the client +// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The +// server side does the reverse decoding. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{var}`. +// +// If a variable contains multiple path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path on the +// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. +// The server side does the reverse decoding, except "%2F" and "%2f" are left +// unchanged. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{+var}`. +// +// ## Using gRPC API Service Configuration +// +// gRPC API Service Configuration (service config) is a configuration language +// for configuring a gRPC service to become a user-facing product. The +// service config is simply the YAML representation of the `google.api.Service` +// proto message. +// +// As an alternative to annotating your proto file, you can configure gRPC +// transcoding in your service config YAML files. You do this by specifying a +// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same +// effect as the proto annotation. This can be particularly useful if you +// have a proto that is reused in multiple services. Note that any transcoding +// specified in the service config will override any matching transcoding +// configuration in the proto. +// +// Example: +// +// http: +// rules: +// # Selects a gRPC method and applies HttpRule to it. +// - selector: example.v1.Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} +// +// ## Special notes +// +// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the +// proto to JSON conversion must follow the [proto3 +// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). +// +// While the single segment variable follows the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String +// Expansion, the multi segment variable **does not** follow RFC 6570 Section +// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding +// for multi segment variables. +// +// The path variables **must not** refer to any repeated or mapped field, +// because client libraries are not capable of handling such variable expansion. +// +// The path variables **must not** capture the leading "/" character. The reason +// is that the most common use case "{var}" does not capture the leading "/" +// character. For consistency, all path variables must share the same behavior. +// +// Repeated message fields must not be mapped to URL query parameters, because +// no client library can support such complicated mapping. +// +// If an API needs to use a JSON array for request or response body, it can map +// the request or response body to a repeated field. However, some gRPC +// Transcoding implementations may not support this feature. +message HttpRule { + // Selects a method to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax + // details. + string selector = 1; + + // Determines the URL pattern is matched by this rules. This pattern can be + // used with any of the {get|put|post|delete|patch} methods. A custom method + // can be defined using the 'custom' field. + oneof pattern { + // Maps to HTTP GET. Used for listing and getting information about + // resources. + string get = 2; + + // Maps to HTTP PUT. Used for replacing a resource. + string put = 3; + + // Maps to HTTP POST. Used for creating a resource or performing an action. + string post = 4; + + // Maps to HTTP DELETE. Used for deleting a resource. + string delete = 5; + + // Maps to HTTP PATCH. Used for updating a resource. + string patch = 6; + + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. + CustomHttpPattern custom = 8; + } + + // The name of the request field whose value is mapped to the HTTP request + // body, or `*` for mapping all request fields not captured by the path + // pattern to the HTTP body, or omitted for not having any HTTP request body. + // + // NOTE: the referred field must be present at the top-level of the request + // message type. + string body = 7; + + // Optional. The name of the response field whose value is mapped to the HTTP + // response body. When omitted, the entire response message will be used + // as the HTTP response body. + // + // NOTE: The referred field must be present at the top-level of the response + // message type. + string response_body = 12; + + // Additional HTTP bindings for the selector. Nested bindings must + // not contain an `additional_bindings` field themselves (that is, + // the nesting may only be one level deep). + repeated HttpRule additional_bindings = 11; +} + +// A custom pattern is used for defining custom HTTP verb. +message CustomHttpPattern { + // The name of this custom HTTP verb. + string kind = 1; + + // The path matched by this custom verb. + string path = 2; +} \ No newline at end of file diff --git a/seed/fastapi/grpc-proto/.mock/proto/user/v1/user.proto b/seed/fastapi/grpc-proto/.mock/proto/user/v1/user.proto new file mode 100644 index 00000000000..a3ad0195bd4 --- /dev/null +++ b/seed/fastapi/grpc-proto/.mock/proto/user/v1/user.proto @@ -0,0 +1,39 @@ +syntax = "proto3"; + +package user.v1; + +import "google/api/annotations.proto"; +import "google/api/field_behavior.proto"; +import "google/protobuf/struct.proto"; + +option go_package = "user/v1"; +option csharp_namespace = "User.V1"; + +message UserModel { + string username = 1; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateRequest { + string username = 1; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateResponse { + UserModel user = 1; +} + +service User { + rpc Create(CreateRequest) returns (CreateResponse) { + option (google.api.http) = { + post: "/users" + body: "*" + }; + } +} \ No newline at end of file diff --git a/seed/fastapi/grpc-proto/__init__.py b/seed/fastapi/grpc-proto/__init__.py new file mode 100644 index 00000000000..fcc1f8010b0 --- /dev/null +++ b/seed/fastapi/grpc-proto/__init__.py @@ -0,0 +1,6 @@ +# This file was auto-generated by Fern from our API Definition. + +from .resources import CreateRequest, user +from .types import CreateResponse, UserModel + +__all__ = ["CreateRequest", "CreateResponse", "UserModel", "user"] diff --git a/seed/fastapi/grpc-proto/core/__init__.py b/seed/fastapi/grpc-proto/core/__init__.py new file mode 100644 index 00000000000..f375cd27ae8 --- /dev/null +++ b/seed/fastapi/grpc-proto/core/__init__.py @@ -0,0 +1,41 @@ +# This file was auto-generated by Fern from our API Definition. + +from .datetime_utils import serialize_datetime +from .exceptions import ( + FernHTTPException, + UnauthorizedException, + default_exception_handler, + fern_http_exception_handler, + http_exception_handler, +) +from .pydantic_utilities import ( + IS_PYDANTIC_V2, + UniversalBaseModel, + UniversalRootModel, + deep_union_pydantic_dicts, + parse_obj_as, + universal_field_validator, + universal_root_validator, + update_forward_refs, +) +from .route_args import route_args +from .security import BearerToken + +__all__ = [ + "BearerToken", + "FernHTTPException", + "IS_PYDANTIC_V2", + "UnauthorizedException", + "UniversalBaseModel", + "UniversalRootModel", + "deep_union_pydantic_dicts", + "default_exception_handler", + "fern_http_exception_handler", + "http_exception_handler", + "parse_obj_as", + "route_args", + "serialize_datetime", + "universal_field_validator", + "universal_root_validator", + "update_forward_refs", +] diff --git a/seed/fastapi/grpc-proto/core/abstract_fern_service.py b/seed/fastapi/grpc-proto/core/abstract_fern_service.py new file mode 100644 index 00000000000..da7c8dc49c4 --- /dev/null +++ b/seed/fastapi/grpc-proto/core/abstract_fern_service.py @@ -0,0 +1,11 @@ +# This file was auto-generated by Fern from our API Definition. + +import abc + +import fastapi + + +class AbstractFernService(abc.ABC): + @classmethod + def _init_fern(cls, router: fastapi.APIRouter) -> None: + ... diff --git a/seed/fastapi/grpc-proto/core/datetime_utils.py b/seed/fastapi/grpc-proto/core/datetime_utils.py new file mode 100644 index 00000000000..7c9864a944c --- /dev/null +++ b/seed/fastapi/grpc-proto/core/datetime_utils.py @@ -0,0 +1,28 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt + + +def serialize_datetime(v: dt.datetime) -> str: + """ + Serialize a datetime including timezone info. + + Uses the timezone info provided if present, otherwise uses the current runtime's timezone info. + + UTC datetimes end in "Z" while all other timezones are represented as offset from UTC, e.g. +05:00. + """ + + def _serialize_zoned_datetime(v: dt.datetime) -> str: + if v.tzinfo is not None and v.tzinfo.tzname(None) == dt.timezone.utc.tzname(None): + # UTC is a special case where we use "Z" at the end instead of "+00:00" + return v.isoformat().replace("+00:00", "Z") + else: + # Delegate to the typical +/- offset format + return v.isoformat() + + if v.tzinfo is not None: + return _serialize_zoned_datetime(v) + else: + local_tz = dt.datetime.now().astimezone().tzinfo + localized_dt = v.replace(tzinfo=local_tz) + return _serialize_zoned_datetime(localized_dt) diff --git a/seed/fastapi/grpc-proto/core/exceptions/__init__.py b/seed/fastapi/grpc-proto/core/exceptions/__init__.py new file mode 100644 index 00000000000..297d6e06f5f --- /dev/null +++ b/seed/fastapi/grpc-proto/core/exceptions/__init__.py @@ -0,0 +1,13 @@ +# This file was auto-generated by Fern from our API Definition. + +from .fern_http_exception import FernHTTPException +from .handlers import default_exception_handler, fern_http_exception_handler, http_exception_handler +from .unauthorized import UnauthorizedException + +__all__ = [ + "FernHTTPException", + "UnauthorizedException", + "default_exception_handler", + "fern_http_exception_handler", + "http_exception_handler", +] diff --git a/seed/fastapi/grpc-proto/core/exceptions/fern_http_exception.py b/seed/fastapi/grpc-proto/core/exceptions/fern_http_exception.py new file mode 100644 index 00000000000..bdf03862487 --- /dev/null +++ b/seed/fastapi/grpc-proto/core/exceptions/fern_http_exception.py @@ -0,0 +1,20 @@ +# This file was auto-generated by Fern from our API Definition. + +import abc +import typing + +import fastapi + + +class FernHTTPException(abc.ABC, fastapi.HTTPException): + def __init__( + self, status_code: int, name: typing.Optional[str] = None, content: typing.Optional[typing.Any] = None + ): + super().__init__(status_code=status_code) + self.name = name + self.status_code = status_code + self.content = content + + def to_json_response(self) -> fastapi.responses.JSONResponse: + content = fastapi.encoders.jsonable_encoder(self.content, exclude_none=True) + return fastapi.responses.JSONResponse(content=content, status_code=self.status_code) diff --git a/seed/fastapi/grpc-proto/core/exceptions/handlers.py b/seed/fastapi/grpc-proto/core/exceptions/handlers.py new file mode 100644 index 00000000000..6d8c4155c5a --- /dev/null +++ b/seed/fastapi/grpc-proto/core/exceptions/handlers.py @@ -0,0 +1,33 @@ +# This file was auto-generated by Fern from our API Definition. + +import logging + +import fastapi +import starlette +import starlette.exceptions + +from .fern_http_exception import FernHTTPException + + +def fern_http_exception_handler( + request: fastapi.requests.Request, exc: FernHTTPException, skip_log: bool = False +) -> fastapi.responses.JSONResponse: + if not skip_log: + logging.getLogger(__name__).error(f"{exc.__class__.__name__} in {request.url.path}", exc_info=exc) + return exc.to_json_response() + + +def http_exception_handler( + request: fastapi.requests.Request, exc: starlette.exceptions.HTTPException, skip_log: bool = False +) -> fastapi.responses.JSONResponse: + if not skip_log: + logging.getLogger(__name__).error(f"{exc.__class__.__name__} in {request.url.path}", exc_info=exc) + return FernHTTPException(status_code=exc.status_code, content=exc.detail).to_json_response() + + +def default_exception_handler( + request: fastapi.requests.Request, exc: Exception, skip_log: bool = False +) -> fastapi.responses.JSONResponse: + if not skip_log: + logging.getLogger(__name__).error(f"{exc.__class__.__name__} in {request.url.path}", exc_info=exc) + return FernHTTPException(status_code=500, content="Internal Server Error").to_json_response() diff --git a/seed/fastapi/grpc-proto/core/exceptions/unauthorized.py b/seed/fastapi/grpc-proto/core/exceptions/unauthorized.py new file mode 100644 index 00000000000..32d532e5ef2 --- /dev/null +++ b/seed/fastapi/grpc-proto/core/exceptions/unauthorized.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from .fern_http_exception import FernHTTPException + + +class UnauthorizedException(FernHTTPException): + """ + This is the exception that is thrown by Fern when auth is not present on a + request. + """ + + def __init__(self, content: typing.Optional[str] = None) -> None: + super().__init__(status_code=401, content=content) diff --git a/seed/fastapi/grpc-proto/core/pydantic_utilities.py b/seed/fastapi/grpc-proto/core/pydantic_utilities.py new file mode 100644 index 00000000000..f95015f89bd --- /dev/null +++ b/seed/fastapi/grpc-proto/core/pydantic_utilities.py @@ -0,0 +1,179 @@ +# This file was auto-generated by Fern from our API Definition. + +# nopycln: file +import datetime as dt +import typing +from collections import defaultdict +from functools import wraps + +import pydantic + +from .datetime_utils import serialize_datetime + +IS_PYDANTIC_V2 = pydantic.VERSION.startswith("2.") + +if IS_PYDANTIC_V2: + # isort will try to reformat the comments on these imports, which breaks mypy + # isort: off + from pydantic.v1.datetime_parse import ( # type: ignore # pyright: ignore[reportMissingImports] # Pydantic v2 + parse_date as parse_date, + ) + from pydantic.v1.datetime_parse import ( # pyright: ignore[reportMissingImports] # Pydantic v2 + parse_datetime as parse_datetime, + ) + from pydantic.v1.json import ( # type: ignore # pyright: ignore[reportMissingImports] # Pydantic v2 + ENCODERS_BY_TYPE as encoders_by_type, + ) + from pydantic.v1.typing import ( # type: ignore # pyright: ignore[reportMissingImports] # Pydantic v2 + get_args as get_args, + ) + from pydantic.v1.typing import get_origin as get_origin # pyright: ignore[reportMissingImports] # Pydantic v2 + from pydantic.v1.typing import ( # pyright: ignore[reportMissingImports] # Pydantic v2 + is_literal_type as is_literal_type, + ) + from pydantic.v1.typing import is_union as is_union # pyright: ignore[reportMissingImports] # Pydantic v2 + from pydantic.v1.fields import ModelField as ModelField # type: ignore # pyright: ignore[reportMissingImports] # Pydantic v2 +else: + from pydantic.datetime_parse import parse_date as parse_date # type: ignore # Pydantic v1 + from pydantic.datetime_parse import parse_datetime as parse_datetime # type: ignore # Pydantic v1 + from pydantic.fields import ModelField as ModelField # type: ignore # Pydantic v1 + from pydantic.json import ENCODERS_BY_TYPE as encoders_by_type # type: ignore # Pydantic v1 + from pydantic.typing import get_args as get_args # type: ignore # Pydantic v1 + from pydantic.typing import get_origin as get_origin # type: ignore # Pydantic v1 + from pydantic.typing import is_literal_type as is_literal_type # type: ignore # Pydantic v1 + from pydantic.typing import is_union as is_union # type: ignore # Pydantic v1 + + # isort: on + + +T = typing.TypeVar("T") +Model = typing.TypeVar("Model", bound=pydantic.BaseModel) + + +def deep_union_pydantic_dicts( + source: typing.Dict[str, typing.Any], destination: typing.Dict[str, typing.Any] +) -> typing.Dict[str, typing.Any]: + for key, value in source.items(): + if isinstance(value, dict): + node = destination.setdefault(key, {}) + deep_union_pydantic_dicts(value, node) + else: + destination[key] = value + + return destination + + +def parse_obj_as(type_: typing.Type[T], object_: typing.Any) -> T: + if IS_PYDANTIC_V2: + adapter = pydantic.TypeAdapter(type_) # type: ignore # Pydantic v2 + return adapter.validate_python(object_) + else: + return pydantic.parse_obj_as(type_, object_) + + +def to_jsonable_with_fallback( + obj: typing.Any, fallback_serializer: typing.Callable[[typing.Any], typing.Any] +) -> typing.Any: + if IS_PYDANTIC_V2: + from pydantic_core import to_jsonable_python + + return to_jsonable_python(obj, fallback=fallback_serializer) + else: + return fallback_serializer(obj) + + +class UniversalBaseModel(pydantic.BaseModel): + class Config: + populate_by_name = True + smart_union = True + allow_population_by_field_name = True + json_encoders = {dt.datetime: serialize_datetime} + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = {"by_alias": True, "exclude_unset": True, **kwargs} + if IS_PYDANTIC_V2: + return super().model_dump_json(**kwargs_with_defaults) # type: ignore # Pydantic v2 + else: + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + kwargs_with_defaults_exclude_unset: typing.Any = {"by_alias": True, "exclude_unset": True, **kwargs} + kwargs_with_defaults_exclude_none: typing.Any = {"by_alias": True, "exclude_none": True, **kwargs} + + if IS_PYDANTIC_V2: + return deep_union_pydantic_dicts( + super().model_dump(**kwargs_with_defaults_exclude_unset), # type: ignore # Pydantic v2 + super().model_dump(**kwargs_with_defaults_exclude_none), # type: ignore # Pydantic v2 + ) + else: + return deep_union_pydantic_dicts( + super().dict(**kwargs_with_defaults_exclude_unset), super().dict(**kwargs_with_defaults_exclude_none) + ) + + +UniversalRootModel: typing.Type[pydantic.BaseModel] +if IS_PYDANTIC_V2: + + class V2RootModel(UniversalBaseModel, pydantic.RootModel): # type: ignore # Pydantic v2 + pass + + UniversalRootModel = V2RootModel +else: + UniversalRootModel = UniversalBaseModel + + +def encode_by_type(o: typing.Any) -> typing.Any: + encoders_by_class_tuples: typing.Dict[ + typing.Callable[[typing.Any], typing.Any], typing.Tuple[typing.Any, ...] + ] = defaultdict(tuple) + for type_, encoder in encoders_by_type.items(): + encoders_by_class_tuples[encoder] += (type_,) + + if type(o) in encoders_by_type: + return encoders_by_type[type(o)](o) + for encoder, classes_tuple in encoders_by_class_tuples.items(): + if isinstance(o, classes_tuple): + return encoder(o) + + +def update_forward_refs(model: typing.Type["Model"], **localns: typing.Any) -> None: + if IS_PYDANTIC_V2: + model.model_rebuild(force=True, raise_errors=False) # type: ignore # Pydantic v2 + else: + model.update_forward_refs(**localns) + + +# Mirrors Pydantic's internal typing +AnyCallable = typing.Callable[..., typing.Any] + + +def universal_root_validator(pre: bool = False) -> typing.Callable[[AnyCallable], AnyCallable]: + def decorator(func: AnyCallable) -> AnyCallable: + @wraps(func) + def validate(*args: typing.Any, **kwargs: typing.Any) -> AnyCallable: + if IS_PYDANTIC_V2: + wrapped_func = pydantic.model_validator("before" if pre else "after")(func) # type: ignore # Pydantic v2 + else: + wrapped_func = pydantic.root_validator(pre=pre)(func) # type: ignore # Pydantic v1 + + return wrapped_func(*args, **kwargs) + + return validate + + return decorator + + +def universal_field_validator(field_name: str, pre: bool = False) -> typing.Callable[[AnyCallable], AnyCallable]: + def decorator(func: AnyCallable) -> AnyCallable: + @wraps(func) + def validate(*args: typing.Any, **kwargs: typing.Any) -> AnyCallable: + if IS_PYDANTIC_V2: + wrapped_func = pydantic.field_validator(field_name, mode="before" if pre else "after")(func) # type: ignore # Pydantic v2 + else: + wrapped_func = pydantic.validator(field_name, pre=pre)(func) + + return wrapped_func(*args, **kwargs) + + return validate + + return decorator diff --git a/seed/fastapi/grpc-proto/core/route_args.py b/seed/fastapi/grpc-proto/core/route_args.py new file mode 100644 index 00000000000..4228e2fe05d --- /dev/null +++ b/seed/fastapi/grpc-proto/core/route_args.py @@ -0,0 +1,63 @@ +# This file was auto-generated by Fern from our API Definition. + +import enum +import inspect +import typing + +import typing_extensions + +T = typing.TypeVar("T", bound=typing.Callable[..., typing.Any]) + +FERN_CONFIG_KEY = "__fern" + + +class RouteArgs(typing_extensions.TypedDict): + openapi_extra: typing.Optional[typing.Dict[str, typing.Any]] + tags: typing.Optional[typing.List[typing.Union[str, enum.Enum]]] + include_in_schema: bool + + +DEFAULT_ROUTE_ARGS = RouteArgs(openapi_extra=None, tags=None, include_in_schema=True) + + +def get_route_args(endpoint_function: typing.Callable[..., typing.Any], *, default_tag: str) -> RouteArgs: + unwrapped = inspect.unwrap(endpoint_function, stop=(lambda f: hasattr(f, FERN_CONFIG_KEY))) + route_args = typing.cast(RouteArgs, getattr(unwrapped, FERN_CONFIG_KEY, DEFAULT_ROUTE_ARGS)) + if route_args["tags"] is None: + return RouteArgs( + openapi_extra=route_args["openapi_extra"], + tags=[default_tag], + include_in_schema=route_args["include_in_schema"], + ) + return route_args + + +def route_args( + openapi_extra: typing.Optional[typing.Dict[str, typing.Any]] = None, + tags: typing.Optional[typing.List[typing.Union[str, enum.Enum]]] = None, + include_in_schema: bool = True, +) -> typing.Callable[[T], T]: + """ + this decorator allows you to forward certain args to the FastAPI route decorator. + + usage: + @route_args(openapi_extra=...) + def your_endpoint_method(... + + currently supported args: + - openapi_extra + - tags + + if there's another FastAPI route arg you need to pass through, please + contact the Fern team! + """ + + def decorator(endpoint_function: T) -> T: + setattr( + endpoint_function, + FERN_CONFIG_KEY, + RouteArgs(openapi_extra=openapi_extra, tags=tags, include_in_schema=include_in_schema), + ) + return endpoint_function + + return decorator diff --git a/seed/fastapi/grpc-proto/core/security/__init__.py b/seed/fastapi/grpc-proto/core/security/__init__.py new file mode 100644 index 00000000000..e69ee6d9c5a --- /dev/null +++ b/seed/fastapi/grpc-proto/core/security/__init__.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +from .bearer import BearerToken + +__all__ = ["BearerToken"] diff --git a/seed/fastapi/grpc-proto/core/security/bearer.py b/seed/fastapi/grpc-proto/core/security/bearer.py new file mode 100644 index 00000000000..023342b668d --- /dev/null +++ b/seed/fastapi/grpc-proto/core/security/bearer.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import fastapi + +from ..exceptions import UnauthorizedException + + +class BearerToken: + def __init__(self, token: str): + self.token = token + + +def HTTPBearer(request: fastapi.requests.Request) -> BearerToken: + authorization_header_value = request.headers.get("Authorization") + if not authorization_header_value: + raise UnauthorizedException("Missing Authorization header") + scheme, _, token = authorization_header_value.partition(" ") + if scheme.lower() != "bearer": + raise UnauthorizedException("Authorization header scheme is not bearer") + if not token: + raise UnauthorizedException("Authorization header is missing a token") + return BearerToken(token) diff --git a/seed/fastapi/grpc-proto/register.py b/seed/fastapi/grpc-proto/register.py new file mode 100644 index 00000000000..cad83228e21 --- /dev/null +++ b/seed/fastapi/grpc-proto/register.py @@ -0,0 +1,44 @@ +# This file was auto-generated by Fern from our API Definition. + +import glob +import importlib +import os +import types +import typing + +import fastapi +import starlette.exceptions +from fastapi import params + +from .core.abstract_fern_service import AbstractFernService +from .core.exceptions import default_exception_handler, fern_http_exception_handler, http_exception_handler +from .core.exceptions.fern_http_exception import FernHTTPException +from .resources.user.service.service import AbstractUserService + + +def register( + _app: fastapi.FastAPI, + *, + user: AbstractUserService, + dependencies: typing.Optional[typing.Sequence[params.Depends]] = None +) -> None: + _app.include_router(__register_service(user), dependencies=dependencies) + + _app.add_exception_handler(FernHTTPException, fern_http_exception_handler) # type: ignore + _app.add_exception_handler(starlette.exceptions.HTTPException, http_exception_handler) # type: ignore + _app.add_exception_handler(Exception, default_exception_handler) # type: ignore + + +def __register_service(service: AbstractFernService) -> fastapi.APIRouter: + router = fastapi.APIRouter() + type(service)._init_fern(router) + return router + + +def register_validators(module: types.ModuleType) -> None: + validators_directory: str = os.path.dirname(module.__file__) # type: ignore + for path in glob.glob(os.path.join(validators_directory, "**/*.py"), recursive=True): + if os.path.isfile(path): + relative_path = os.path.relpath(path, start=validators_directory) + module_path = ".".join([module.__name__] + relative_path[:-3].split("/")) + importlib.import_module(module_path) diff --git a/seed/fastapi/grpc-proto/resources/__init__.py b/seed/fastapi/grpc-proto/resources/__init__.py new file mode 100644 index 00000000000..25bc8eee205 --- /dev/null +++ b/seed/fastapi/grpc-proto/resources/__init__.py @@ -0,0 +1,6 @@ +# This file was auto-generated by Fern from our API Definition. + +from . import user +from .user import CreateRequest + +__all__ = ["CreateRequest", "user"] diff --git a/seed/fastapi/grpc-proto/resources/user/__init__.py b/seed/fastapi/grpc-proto/resources/user/__init__.py new file mode 100644 index 00000000000..331bfcb6aa4 --- /dev/null +++ b/seed/fastapi/grpc-proto/resources/user/__init__.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +from .service import CreateRequest + +__all__ = ["CreateRequest"] diff --git a/seed/fastapi/grpc-proto/resources/user/service/__init__.py b/seed/fastapi/grpc-proto/resources/user/service/__init__.py new file mode 100644 index 00000000000..db6f501c61b --- /dev/null +++ b/seed/fastapi/grpc-proto/resources/user/service/__init__.py @@ -0,0 +1,6 @@ +# This file was auto-generated by Fern from our API Definition. + +from .create_request import CreateRequest +from .service import AbstractUserService + +__all__ = ["AbstractUserService", "CreateRequest"] diff --git a/seed/fastapi/grpc-proto/resources/user/service/create_request.py b/seed/fastapi/grpc-proto/resources/user/service/create_request.py new file mode 100644 index 00000000000..d2d7b0f383d --- /dev/null +++ b/seed/fastapi/grpc-proto/resources/user/service/create_request.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic + +from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class CreateRequest(UniversalBaseModel): + username: typing.Optional[str] = None + email: typing.Optional[str] = None + age: typing.Optional[int] = None + weight: typing.Optional[float] = None + metadata: typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="forbid") # type: ignore # Pydantic v2 + else: + + class Config: + extra = pydantic.Extra.forbid diff --git a/seed/fastapi/grpc-proto/resources/user/service/service.py b/seed/fastapi/grpc-proto/resources/user/service/service.py new file mode 100644 index 00000000000..97d12029f11 --- /dev/null +++ b/seed/fastapi/grpc-proto/resources/user/service/service.py @@ -0,0 +1,74 @@ +# This file was auto-generated by Fern from our API Definition. + +import abc +import functools +import inspect +import logging +import typing + +import fastapi + +from ....core.abstract_fern_service import AbstractFernService +from ....core.exceptions.fern_http_exception import FernHTTPException +from ....core.route_args import get_route_args +from ....types.create_response import CreateResponse +from .create_request import CreateRequest + + +class AbstractUserService(AbstractFernService): + """ + AbstractUserService is an abstract class containing the methods that you should implement. + + Each method is associated with an API route, which will be registered + with FastAPI when you register your implementation using Fern's register() + function. + """ + + @abc.abstractmethod + def create(self, *, body: CreateRequest) -> CreateResponse: + ... + + """ + Below are internal methods used by Fern to register your implementation. + You can ignore them. + """ + + @classmethod + def _init_fern(cls, router: fastapi.APIRouter) -> None: + cls.__init_create(router=router) + + @classmethod + def __init_create(cls, router: fastapi.APIRouter) -> None: + endpoint_function = inspect.signature(cls.create) + new_parameters: typing.List[inspect.Parameter] = [] + for index, (parameter_name, parameter) in enumerate(endpoint_function.parameters.items()): + if index == 0: + new_parameters.append(parameter.replace(default=fastapi.Depends(cls))) + elif parameter_name == "body": + new_parameters.append(parameter.replace(default=fastapi.Body(...))) + else: + new_parameters.append(parameter) + setattr(cls.create, "__signature__", endpoint_function.replace(parameters=new_parameters)) + + @functools.wraps(cls.create) + def wrapper(*args: typing.Any, **kwargs: typing.Any) -> CreateResponse: + try: + return cls.create(*args, **kwargs) + except FernHTTPException as e: + logging.getLogger(f"{cls.__module__}.{cls.__name__}").warn( + f"Endpoint 'create' unexpectedly threw {e.__class__.__name__}. " + + f"If this was intentional, please add {e.__class__.__name__} to " + + "the endpoint's errors list in your Fern Definition." + ) + raise e + + # this is necessary for FastAPI to find forward-ref'ed type hints. + # https://github.com/tiangolo/fastapi/pull/5077 + wrapper.__globals__.update(cls.create.__globals__) + + router.post( + path="/users", + response_model=CreateResponse, + description=AbstractUserService.create.__doc__, + **get_route_args(cls.create, default_tag="user"), + )(wrapper) diff --git a/seed/fastapi/grpc-proto/snippet-templates.json b/seed/fastapi/grpc-proto/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/fastapi/grpc-proto/snippet.json b/seed/fastapi/grpc-proto/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/fastapi/grpc-proto/types/__init__.py b/seed/fastapi/grpc-proto/types/__init__.py new file mode 100644 index 00000000000..243a9961e67 --- /dev/null +++ b/seed/fastapi/grpc-proto/types/__init__.py @@ -0,0 +1,6 @@ +# This file was auto-generated by Fern from our API Definition. + +from .create_response import CreateResponse +from .user_model import UserModel + +__all__ = ["CreateResponse", "UserModel"] diff --git a/seed/fastapi/grpc-proto/types/create_response.py b/seed/fastapi/grpc-proto/types/create_response.py new file mode 100644 index 00000000000..4f35d4c415f --- /dev/null +++ b/seed/fastapi/grpc-proto/types/create_response.py @@ -0,0 +1,19 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic + +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .user_model import UserModel + + +class CreateResponse(UniversalBaseModel): + user: typing.Optional[UserModel] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="forbid") # type: ignore # Pydantic v2 + else: + + class Config: + extra = pydantic.Extra.forbid diff --git a/seed/fastapi/grpc-proto/types/user_model.py b/seed/fastapi/grpc-proto/types/user_model.py new file mode 100644 index 00000000000..0003c2a320e --- /dev/null +++ b/seed/fastapi/grpc-proto/types/user_model.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic + +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class UserModel(UniversalBaseModel): + username: typing.Optional[str] = None + email: typing.Optional[str] = None + age: typing.Optional[int] = None + weight: typing.Optional[float] = None + metadata: typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="forbid") # type: ignore # Pydantic v2 + else: + + class Config: + extra = pydantic.Extra.forbid diff --git a/seed/fastapi/grpc/.mock/definition/api.yml b/seed/fastapi/grpc/.mock/definition/api.yml new file mode 100644 index 00000000000..15a9f823724 --- /dev/null +++ b/seed/fastapi/grpc/.mock/definition/api.yml @@ -0,0 +1,4 @@ +name: api + +error-discrimination: + strategy: status-code \ No newline at end of file diff --git a/seed/fastapi/grpc/.mock/definition/user.yml b/seed/fastapi/grpc/.mock/definition/user.yml new file mode 100644 index 00000000000..80223300afc --- /dev/null +++ b/seed/fastapi/grpc/.mock/definition/user.yml @@ -0,0 +1,61 @@ +types: + Metadata: + type: map> + encoding: + proto: + type: google.protobuf.Struct + + MetadataValue: + discriminated: false + union: + - double + - string + - boolean + - list + encoding: + proto: + type: google.protobuf.Value + + User: + properties: + id: string + username: string + email: optional + age: optional + weight: optional + metadata: optional + + CreateUserResponse: + properties: + user: User + +service: + auth: false + base-path: / + transport: + grpc: + service-name: UserService + endpoints: + createUser: + method: POST + path: /users + request: + name: CreateUserRequest + body: + properties: + username: string + email: optional + age: optional + weight: optional + response: CreateUserResponse + + getUser: + method: GET + path: /users + request: + name: GetUserRequest + query-parameters: + username: optional + age: optional + weight: optional + response: User diff --git a/seed/fastapi/grpc/.mock/fern.config.json b/seed/fastapi/grpc/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/fastapi/grpc/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/fastapi/grpc/.mock/generators.yml b/seed/fastapi/grpc/.mock/generators.yml new file mode 100644 index 00000000000..f62dfba5843 --- /dev/null +++ b/seed/fastapi/grpc/.mock/generators.yml @@ -0,0 +1,4 @@ +api: + - proto: + root: proto + target: proto/user/v1/user.proto \ No newline at end of file diff --git a/seed/fastapi/grpc/.mock/proto/google/api/annotations.proto b/seed/fastapi/grpc/.mock/proto/google/api/annotations.proto new file mode 100644 index 00000000000..efdab3db6ca --- /dev/null +++ b/seed/fastapi/grpc/.mock/proto/google/api/annotations.proto @@ -0,0 +1,31 @@ +// Copyright 2015 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/api/http.proto"; +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "AnnotationsProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // See `HttpRule`. + HttpRule http = 72295728; +} diff --git a/seed/fastapi/grpc/.mock/proto/google/api/field_behavior.proto b/seed/fastapi/grpc/.mock/proto/google/api/field_behavior.proto new file mode 100644 index 00000000000..344cb0b1fc2 --- /dev/null +++ b/seed/fastapi/grpc/.mock/proto/google/api/field_behavior.proto @@ -0,0 +1,104 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "FieldBehaviorProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.FieldOptions { + // A designation of a specific field behavior (required, output only, etc.) + // in protobuf messages. + // + // Examples: + // + // string name = 1 [(google.api.field_behavior) = REQUIRED]; + // State state = 1 [(google.api.field_behavior) = OUTPUT_ONLY]; + // google.protobuf.Duration ttl = 1 + // [(google.api.field_behavior) = INPUT_ONLY]; + // google.protobuf.Timestamp expire_time = 1 + // [(google.api.field_behavior) = OUTPUT_ONLY, + // (google.api.field_behavior) = IMMUTABLE]; + repeated google.api.FieldBehavior field_behavior = 1052; +} + +// An indicator of the behavior of a given field (for example, that a field +// is required in requests, or given as output but ignored as input). +// This **does not** change the behavior in protocol buffers itself; it only +// denotes the behavior and may affect how API tooling handles the field. +// +// Note: This enum **may** receive new values in the future. +enum FieldBehavior { + // Conventional default for enums. Do not use this. + FIELD_BEHAVIOR_UNSPECIFIED = 0; + + // Specifically denotes a field as optional. + // While all fields in protocol buffers are optional, this may be specified + // for emphasis if appropriate. + OPTIONAL = 1; + + // Denotes a field as required. + // This indicates that the field **must** be provided as part of the request, + // and failure to do so will cause an error (usually `INVALID_ARGUMENT`). + REQUIRED = 2; + + // Denotes a field as output only. + // This indicates that the field is provided in responses, but including the + // field in a request does nothing (the server *must* ignore it and + // *must not* throw an error as a result of the field's presence). + OUTPUT_ONLY = 3; + + // Denotes a field as input only. + // This indicates that the field is provided in requests, and the + // corresponding field is not included in output. + INPUT_ONLY = 4; + + // Denotes a field as immutable. + // This indicates that the field may be set once in a request to create a + // resource, but may not be changed thereafter. + IMMUTABLE = 5; + + // Denotes that a (repeated) field is an unordered list. + // This indicates that the service may provide the elements of the list + // in any arbitrary order, rather than the order the user originally + // provided. Additionally, the list's order may or may not be stable. + UNORDERED_LIST = 6; + + // Denotes that this field returns a non-empty default value if not set. + // This indicates that if the user provides the empty value in a request, + // a non-empty value will be returned. The user will not be aware of what + // non-empty value to expect. + NON_EMPTY_DEFAULT = 7; + + // Denotes that the field in a resource (a message annotated with + // google.api.resource) is used in the resource name to uniquely identify the + // resource. For AIP-compliant APIs, this should only be applied to the + // `name` field on the resource. + // + // This behavior should not be applied to references to other resources within + // the message. + // + // The identifier field of resources often have different field behavior + // depending on the request it is embedded in (e.g. for Create methods name + // is optional and unused, while for Update methods it is required). Instead + // of method-specific annotations, only `IDENTIFIER` is required. + IDENTIFIER = 8; +} diff --git a/seed/fastapi/grpc/.mock/proto/google/api/http.proto b/seed/fastapi/grpc/.mock/proto/google/api/http.proto new file mode 100644 index 00000000000..31d867a27d5 --- /dev/null +++ b/seed/fastapi/grpc/.mock/proto/google/api/http.proto @@ -0,0 +1,379 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "HttpProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Defines the HTTP configuration for an API service. It contains a list of +// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method +// to one or more HTTP REST API methods. +message Http { + // A list of HTTP configuration rules that apply to individual API methods. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated HttpRule rules = 1; + + // When set to true, URL path parameters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // + // The default behavior is to not decode RFC 6570 reserved characters in multi + // segment matches. + bool fully_decode_reserved_expansion = 2; +} + +// # gRPC Transcoding +// +// gRPC Transcoding is a feature for mapping between a gRPC method and one or +// more HTTP REST endpoints. It allows developers to build a single API service +// that supports both gRPC APIs and REST APIs. Many systems, including [Google +// APIs](https://github.com/googleapis/googleapis), +// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC +// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), +// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature +// and use it for large scale production services. +// +// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies +// how different portions of the gRPC request message are mapped to the URL +// path, URL query parameters, and HTTP request body. It also controls how the +// gRPC response message is mapped to the HTTP response body. `HttpRule` is +// typically specified as an `google.api.http` annotation on the gRPC method. +// +// Each mapping specifies a URL path template and an HTTP method. The path +// template may refer to one or more fields in the gRPC request message, as long +// as each field is a non-repeated field with a primitive (non-message) type. +// The path template controls how fields of the request message are mapped to +// the URL path. +// +// Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/{name=messages/*}" +// }; +// } +// } +// message GetMessageRequest { +// string name = 1; // Mapped to URL path. +// } +// message Message { +// string text = 1; // The resource content. +// } +// +// This enables an HTTP REST to gRPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` +// +// Any fields in the request message which are not bound by the path template +// automatically become HTTP query parameters if there is no HTTP request body. +// For example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get:"/v1/messages/{message_id}" +// }; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // Mapped to URL path. +// int64 revision = 2; // Mapped to URL query parameter `revision`. +// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. +// } +// +// This enables a HTTP JSON to RPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | +// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: +// "foo"))` +// +// Note that fields which are mapped to URL query parameters must have a +// primitive type or a repeated primitive type or a non-repeated message type. +// In the case of a repeated type, the parameter can be repeated in the URL +// as `...?param=A¶m=B`. In the case of a message type, each field of the +// message is mapped to a separate parameter, such as +// `...?foo.a=A&foo.b=B&foo.c=C`. +// +// For HTTP methods that allow a request body, the `body` field +// specifies the mapping. Consider a REST update method on the +// message resource collection: +// +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } +// +// The following HTTP JSON to RPC mapping is enabled, where the +// representation of the JSON in the request body is determined by +// protos JSON encoding: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" message { text: "Hi!" })` +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. This enables the following alternative definition of +// the update method: +// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } +// +// +// The following HTTP JSON to RPC mapping is enabled: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" text: "Hi!")` +// +// Note that when using `*` in the body mapping, it is not possible to +// have HTTP parameters, as all fields not bound by the path end in +// the body. This makes this option more rarely used in practice when +// defining REST APIs. The common usage of `*` is in custom methods +// which don't use the URL at all for transferring data. +// +// It is possible to define multiple HTTP methods for one RPC by using +// the `additional_bindings` option. Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } +// +// This enables the following two alternative HTTP JSON to RPC mappings: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` +// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: +// "123456")` +// +// ## Rules for HTTP mapping +// +// 1. Leaf request fields (recursive expansion nested messages in the request +// message) are classified into three categories: +// - Fields referred by the path template. They are passed via the URL path. +// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They +// are passed via the HTTP +// request body. +// - All other fields are passed via the URL query parameters, and the +// parameter name is the field path in the request message. A repeated +// field can be represented as multiple query parameters under the same +// name. +// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL +// query parameter, all fields +// are passed via URL path and HTTP request body. +// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP +// request body, all +// fields are passed via URL path and URL query parameters. +// +// ### Path template syntax +// +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +// +// The syntax `*` matches a single URL path segment. The syntax `**` matches +// zero or more URL path segments, which must be the last part of the URL path +// except the `Verb`. +// +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable +// matches a single path segment, its template may be omitted, e.g. `{var}` +// is equivalent to `{var=*}`. +// +// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` +// contains any reserved character, such characters should be percent-encoded +// before the matching. +// +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path on the client +// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The +// server side does the reverse decoding. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{var}`. +// +// If a variable contains multiple path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path on the +// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. +// The server side does the reverse decoding, except "%2F" and "%2f" are left +// unchanged. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{+var}`. +// +// ## Using gRPC API Service Configuration +// +// gRPC API Service Configuration (service config) is a configuration language +// for configuring a gRPC service to become a user-facing product. The +// service config is simply the YAML representation of the `google.api.Service` +// proto message. +// +// As an alternative to annotating your proto file, you can configure gRPC +// transcoding in your service config YAML files. You do this by specifying a +// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same +// effect as the proto annotation. This can be particularly useful if you +// have a proto that is reused in multiple services. Note that any transcoding +// specified in the service config will override any matching transcoding +// configuration in the proto. +// +// Example: +// +// http: +// rules: +// # Selects a gRPC method and applies HttpRule to it. +// - selector: example.v1.Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} +// +// ## Special notes +// +// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the +// proto to JSON conversion must follow the [proto3 +// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). +// +// While the single segment variable follows the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String +// Expansion, the multi segment variable **does not** follow RFC 6570 Section +// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding +// for multi segment variables. +// +// The path variables **must not** refer to any repeated or mapped field, +// because client libraries are not capable of handling such variable expansion. +// +// The path variables **must not** capture the leading "/" character. The reason +// is that the most common use case "{var}" does not capture the leading "/" +// character. For consistency, all path variables must share the same behavior. +// +// Repeated message fields must not be mapped to URL query parameters, because +// no client library can support such complicated mapping. +// +// If an API needs to use a JSON array for request or response body, it can map +// the request or response body to a repeated field. However, some gRPC +// Transcoding implementations may not support this feature. +message HttpRule { + // Selects a method to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax + // details. + string selector = 1; + + // Determines the URL pattern is matched by this rules. This pattern can be + // used with any of the {get|put|post|delete|patch} methods. A custom method + // can be defined using the 'custom' field. + oneof pattern { + // Maps to HTTP GET. Used for listing and getting information about + // resources. + string get = 2; + + // Maps to HTTP PUT. Used for replacing a resource. + string put = 3; + + // Maps to HTTP POST. Used for creating a resource or performing an action. + string post = 4; + + // Maps to HTTP DELETE. Used for deleting a resource. + string delete = 5; + + // Maps to HTTP PATCH. Used for updating a resource. + string patch = 6; + + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. + CustomHttpPattern custom = 8; + } + + // The name of the request field whose value is mapped to the HTTP request + // body, or `*` for mapping all request fields not captured by the path + // pattern to the HTTP body, or omitted for not having any HTTP request body. + // + // NOTE: the referred field must be present at the top-level of the request + // message type. + string body = 7; + + // Optional. The name of the response field whose value is mapped to the HTTP + // response body. When omitted, the entire response message will be used + // as the HTTP response body. + // + // NOTE: The referred field must be present at the top-level of the response + // message type. + string response_body = 12; + + // Additional HTTP bindings for the selector. Nested bindings must + // not contain an `additional_bindings` field themselves (that is, + // the nesting may only be one level deep). + repeated HttpRule additional_bindings = 11; +} + +// A custom pattern is used for defining custom HTTP verb. +message CustomHttpPattern { + // The name of this custom HTTP verb. + string kind = 1; + + // The path matched by this custom verb. + string path = 2; +} diff --git a/seed/fastapi/grpc/.mock/proto/user/v1/user.proto b/seed/fastapi/grpc/.mock/proto/user/v1/user.proto new file mode 100644 index 00000000000..adab9ceefb9 --- /dev/null +++ b/seed/fastapi/grpc/.mock/proto/user/v1/user.proto @@ -0,0 +1,55 @@ +syntax = "proto3"; + +package user.v1; + +import "google/api/annotations.proto"; +import "google/api/field_behavior.proto"; +import "google/protobuf/struct.proto"; + +option csharp_namespace = "User.V1"; + +message User { + string username = 1 [ + (google.api.field_behavior) = REQUIRED + ]; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateUserRequest { + string username = 1 [ + (google.api.field_behavior) = REQUIRED + ]; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateUserResponse { + User user = 1; +} + +message GetUserRequest { + string username = 1; + string email = 2; + uint32 age = 3; + float weight = 4; +} + +service UserService { + rpc CreateUser(CreateUserRequest) returns (CreateUserResponse) { + option (google.api.http) = { + post: "/users" + body: "*" + }; + } + + rpc GetUser(GetUserRequest) returns (User) { + option (google.api.http) = { + get: "/users" + }; + } +} diff --git a/seed/fastapi/grpc/__init__.py b/seed/fastapi/grpc/__init__.py new file mode 100644 index 00000000000..679b0f7354b --- /dev/null +++ b/seed/fastapi/grpc/__init__.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +from .resources import CreateUserRequest, CreateUserResponse, Metadata, MetadataValue, User, user + +__all__ = ["CreateUserRequest", "CreateUserResponse", "Metadata", "MetadataValue", "User", "user"] diff --git a/seed/fastapi/grpc/core/__init__.py b/seed/fastapi/grpc/core/__init__.py new file mode 100644 index 00000000000..f375cd27ae8 --- /dev/null +++ b/seed/fastapi/grpc/core/__init__.py @@ -0,0 +1,41 @@ +# This file was auto-generated by Fern from our API Definition. + +from .datetime_utils import serialize_datetime +from .exceptions import ( + FernHTTPException, + UnauthorizedException, + default_exception_handler, + fern_http_exception_handler, + http_exception_handler, +) +from .pydantic_utilities import ( + IS_PYDANTIC_V2, + UniversalBaseModel, + UniversalRootModel, + deep_union_pydantic_dicts, + parse_obj_as, + universal_field_validator, + universal_root_validator, + update_forward_refs, +) +from .route_args import route_args +from .security import BearerToken + +__all__ = [ + "BearerToken", + "FernHTTPException", + "IS_PYDANTIC_V2", + "UnauthorizedException", + "UniversalBaseModel", + "UniversalRootModel", + "deep_union_pydantic_dicts", + "default_exception_handler", + "fern_http_exception_handler", + "http_exception_handler", + "parse_obj_as", + "route_args", + "serialize_datetime", + "universal_field_validator", + "universal_root_validator", + "update_forward_refs", +] diff --git a/seed/fastapi/grpc/core/abstract_fern_service.py b/seed/fastapi/grpc/core/abstract_fern_service.py new file mode 100644 index 00000000000..da7c8dc49c4 --- /dev/null +++ b/seed/fastapi/grpc/core/abstract_fern_service.py @@ -0,0 +1,11 @@ +# This file was auto-generated by Fern from our API Definition. + +import abc + +import fastapi + + +class AbstractFernService(abc.ABC): + @classmethod + def _init_fern(cls, router: fastapi.APIRouter) -> None: + ... diff --git a/seed/fastapi/grpc/core/datetime_utils.py b/seed/fastapi/grpc/core/datetime_utils.py new file mode 100644 index 00000000000..7c9864a944c --- /dev/null +++ b/seed/fastapi/grpc/core/datetime_utils.py @@ -0,0 +1,28 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt + + +def serialize_datetime(v: dt.datetime) -> str: + """ + Serialize a datetime including timezone info. + + Uses the timezone info provided if present, otherwise uses the current runtime's timezone info. + + UTC datetimes end in "Z" while all other timezones are represented as offset from UTC, e.g. +05:00. + """ + + def _serialize_zoned_datetime(v: dt.datetime) -> str: + if v.tzinfo is not None and v.tzinfo.tzname(None) == dt.timezone.utc.tzname(None): + # UTC is a special case where we use "Z" at the end instead of "+00:00" + return v.isoformat().replace("+00:00", "Z") + else: + # Delegate to the typical +/- offset format + return v.isoformat() + + if v.tzinfo is not None: + return _serialize_zoned_datetime(v) + else: + local_tz = dt.datetime.now().astimezone().tzinfo + localized_dt = v.replace(tzinfo=local_tz) + return _serialize_zoned_datetime(localized_dt) diff --git a/seed/fastapi/grpc/core/exceptions/__init__.py b/seed/fastapi/grpc/core/exceptions/__init__.py new file mode 100644 index 00000000000..297d6e06f5f --- /dev/null +++ b/seed/fastapi/grpc/core/exceptions/__init__.py @@ -0,0 +1,13 @@ +# This file was auto-generated by Fern from our API Definition. + +from .fern_http_exception import FernHTTPException +from .handlers import default_exception_handler, fern_http_exception_handler, http_exception_handler +from .unauthorized import UnauthorizedException + +__all__ = [ + "FernHTTPException", + "UnauthorizedException", + "default_exception_handler", + "fern_http_exception_handler", + "http_exception_handler", +] diff --git a/seed/fastapi/grpc/core/exceptions/fern_http_exception.py b/seed/fastapi/grpc/core/exceptions/fern_http_exception.py new file mode 100644 index 00000000000..bdf03862487 --- /dev/null +++ b/seed/fastapi/grpc/core/exceptions/fern_http_exception.py @@ -0,0 +1,20 @@ +# This file was auto-generated by Fern from our API Definition. + +import abc +import typing + +import fastapi + + +class FernHTTPException(abc.ABC, fastapi.HTTPException): + def __init__( + self, status_code: int, name: typing.Optional[str] = None, content: typing.Optional[typing.Any] = None + ): + super().__init__(status_code=status_code) + self.name = name + self.status_code = status_code + self.content = content + + def to_json_response(self) -> fastapi.responses.JSONResponse: + content = fastapi.encoders.jsonable_encoder(self.content, exclude_none=True) + return fastapi.responses.JSONResponse(content=content, status_code=self.status_code) diff --git a/seed/fastapi/grpc/core/exceptions/handlers.py b/seed/fastapi/grpc/core/exceptions/handlers.py new file mode 100644 index 00000000000..6d8c4155c5a --- /dev/null +++ b/seed/fastapi/grpc/core/exceptions/handlers.py @@ -0,0 +1,33 @@ +# This file was auto-generated by Fern from our API Definition. + +import logging + +import fastapi +import starlette +import starlette.exceptions + +from .fern_http_exception import FernHTTPException + + +def fern_http_exception_handler( + request: fastapi.requests.Request, exc: FernHTTPException, skip_log: bool = False +) -> fastapi.responses.JSONResponse: + if not skip_log: + logging.getLogger(__name__).error(f"{exc.__class__.__name__} in {request.url.path}", exc_info=exc) + return exc.to_json_response() + + +def http_exception_handler( + request: fastapi.requests.Request, exc: starlette.exceptions.HTTPException, skip_log: bool = False +) -> fastapi.responses.JSONResponse: + if not skip_log: + logging.getLogger(__name__).error(f"{exc.__class__.__name__} in {request.url.path}", exc_info=exc) + return FernHTTPException(status_code=exc.status_code, content=exc.detail).to_json_response() + + +def default_exception_handler( + request: fastapi.requests.Request, exc: Exception, skip_log: bool = False +) -> fastapi.responses.JSONResponse: + if not skip_log: + logging.getLogger(__name__).error(f"{exc.__class__.__name__} in {request.url.path}", exc_info=exc) + return FernHTTPException(status_code=500, content="Internal Server Error").to_json_response() diff --git a/seed/fastapi/grpc/core/exceptions/unauthorized.py b/seed/fastapi/grpc/core/exceptions/unauthorized.py new file mode 100644 index 00000000000..32d532e5ef2 --- /dev/null +++ b/seed/fastapi/grpc/core/exceptions/unauthorized.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from .fern_http_exception import FernHTTPException + + +class UnauthorizedException(FernHTTPException): + """ + This is the exception that is thrown by Fern when auth is not present on a + request. + """ + + def __init__(self, content: typing.Optional[str] = None) -> None: + super().__init__(status_code=401, content=content) diff --git a/seed/fastapi/grpc/core/pydantic_utilities.py b/seed/fastapi/grpc/core/pydantic_utilities.py new file mode 100644 index 00000000000..f95015f89bd --- /dev/null +++ b/seed/fastapi/grpc/core/pydantic_utilities.py @@ -0,0 +1,179 @@ +# This file was auto-generated by Fern from our API Definition. + +# nopycln: file +import datetime as dt +import typing +from collections import defaultdict +from functools import wraps + +import pydantic + +from .datetime_utils import serialize_datetime + +IS_PYDANTIC_V2 = pydantic.VERSION.startswith("2.") + +if IS_PYDANTIC_V2: + # isort will try to reformat the comments on these imports, which breaks mypy + # isort: off + from pydantic.v1.datetime_parse import ( # type: ignore # pyright: ignore[reportMissingImports] # Pydantic v2 + parse_date as parse_date, + ) + from pydantic.v1.datetime_parse import ( # pyright: ignore[reportMissingImports] # Pydantic v2 + parse_datetime as parse_datetime, + ) + from pydantic.v1.json import ( # type: ignore # pyright: ignore[reportMissingImports] # Pydantic v2 + ENCODERS_BY_TYPE as encoders_by_type, + ) + from pydantic.v1.typing import ( # type: ignore # pyright: ignore[reportMissingImports] # Pydantic v2 + get_args as get_args, + ) + from pydantic.v1.typing import get_origin as get_origin # pyright: ignore[reportMissingImports] # Pydantic v2 + from pydantic.v1.typing import ( # pyright: ignore[reportMissingImports] # Pydantic v2 + is_literal_type as is_literal_type, + ) + from pydantic.v1.typing import is_union as is_union # pyright: ignore[reportMissingImports] # Pydantic v2 + from pydantic.v1.fields import ModelField as ModelField # type: ignore # pyright: ignore[reportMissingImports] # Pydantic v2 +else: + from pydantic.datetime_parse import parse_date as parse_date # type: ignore # Pydantic v1 + from pydantic.datetime_parse import parse_datetime as parse_datetime # type: ignore # Pydantic v1 + from pydantic.fields import ModelField as ModelField # type: ignore # Pydantic v1 + from pydantic.json import ENCODERS_BY_TYPE as encoders_by_type # type: ignore # Pydantic v1 + from pydantic.typing import get_args as get_args # type: ignore # Pydantic v1 + from pydantic.typing import get_origin as get_origin # type: ignore # Pydantic v1 + from pydantic.typing import is_literal_type as is_literal_type # type: ignore # Pydantic v1 + from pydantic.typing import is_union as is_union # type: ignore # Pydantic v1 + + # isort: on + + +T = typing.TypeVar("T") +Model = typing.TypeVar("Model", bound=pydantic.BaseModel) + + +def deep_union_pydantic_dicts( + source: typing.Dict[str, typing.Any], destination: typing.Dict[str, typing.Any] +) -> typing.Dict[str, typing.Any]: + for key, value in source.items(): + if isinstance(value, dict): + node = destination.setdefault(key, {}) + deep_union_pydantic_dicts(value, node) + else: + destination[key] = value + + return destination + + +def parse_obj_as(type_: typing.Type[T], object_: typing.Any) -> T: + if IS_PYDANTIC_V2: + adapter = pydantic.TypeAdapter(type_) # type: ignore # Pydantic v2 + return adapter.validate_python(object_) + else: + return pydantic.parse_obj_as(type_, object_) + + +def to_jsonable_with_fallback( + obj: typing.Any, fallback_serializer: typing.Callable[[typing.Any], typing.Any] +) -> typing.Any: + if IS_PYDANTIC_V2: + from pydantic_core import to_jsonable_python + + return to_jsonable_python(obj, fallback=fallback_serializer) + else: + return fallback_serializer(obj) + + +class UniversalBaseModel(pydantic.BaseModel): + class Config: + populate_by_name = True + smart_union = True + allow_population_by_field_name = True + json_encoders = {dt.datetime: serialize_datetime} + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = {"by_alias": True, "exclude_unset": True, **kwargs} + if IS_PYDANTIC_V2: + return super().model_dump_json(**kwargs_with_defaults) # type: ignore # Pydantic v2 + else: + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + kwargs_with_defaults_exclude_unset: typing.Any = {"by_alias": True, "exclude_unset": True, **kwargs} + kwargs_with_defaults_exclude_none: typing.Any = {"by_alias": True, "exclude_none": True, **kwargs} + + if IS_PYDANTIC_V2: + return deep_union_pydantic_dicts( + super().model_dump(**kwargs_with_defaults_exclude_unset), # type: ignore # Pydantic v2 + super().model_dump(**kwargs_with_defaults_exclude_none), # type: ignore # Pydantic v2 + ) + else: + return deep_union_pydantic_dicts( + super().dict(**kwargs_with_defaults_exclude_unset), super().dict(**kwargs_with_defaults_exclude_none) + ) + + +UniversalRootModel: typing.Type[pydantic.BaseModel] +if IS_PYDANTIC_V2: + + class V2RootModel(UniversalBaseModel, pydantic.RootModel): # type: ignore # Pydantic v2 + pass + + UniversalRootModel = V2RootModel +else: + UniversalRootModel = UniversalBaseModel + + +def encode_by_type(o: typing.Any) -> typing.Any: + encoders_by_class_tuples: typing.Dict[ + typing.Callable[[typing.Any], typing.Any], typing.Tuple[typing.Any, ...] + ] = defaultdict(tuple) + for type_, encoder in encoders_by_type.items(): + encoders_by_class_tuples[encoder] += (type_,) + + if type(o) in encoders_by_type: + return encoders_by_type[type(o)](o) + for encoder, classes_tuple in encoders_by_class_tuples.items(): + if isinstance(o, classes_tuple): + return encoder(o) + + +def update_forward_refs(model: typing.Type["Model"], **localns: typing.Any) -> None: + if IS_PYDANTIC_V2: + model.model_rebuild(force=True, raise_errors=False) # type: ignore # Pydantic v2 + else: + model.update_forward_refs(**localns) + + +# Mirrors Pydantic's internal typing +AnyCallable = typing.Callable[..., typing.Any] + + +def universal_root_validator(pre: bool = False) -> typing.Callable[[AnyCallable], AnyCallable]: + def decorator(func: AnyCallable) -> AnyCallable: + @wraps(func) + def validate(*args: typing.Any, **kwargs: typing.Any) -> AnyCallable: + if IS_PYDANTIC_V2: + wrapped_func = pydantic.model_validator("before" if pre else "after")(func) # type: ignore # Pydantic v2 + else: + wrapped_func = pydantic.root_validator(pre=pre)(func) # type: ignore # Pydantic v1 + + return wrapped_func(*args, **kwargs) + + return validate + + return decorator + + +def universal_field_validator(field_name: str, pre: bool = False) -> typing.Callable[[AnyCallable], AnyCallable]: + def decorator(func: AnyCallable) -> AnyCallable: + @wraps(func) + def validate(*args: typing.Any, **kwargs: typing.Any) -> AnyCallable: + if IS_PYDANTIC_V2: + wrapped_func = pydantic.field_validator(field_name, mode="before" if pre else "after")(func) # type: ignore # Pydantic v2 + else: + wrapped_func = pydantic.validator(field_name, pre=pre)(func) + + return wrapped_func(*args, **kwargs) + + return validate + + return decorator diff --git a/seed/fastapi/grpc/core/route_args.py b/seed/fastapi/grpc/core/route_args.py new file mode 100644 index 00000000000..4228e2fe05d --- /dev/null +++ b/seed/fastapi/grpc/core/route_args.py @@ -0,0 +1,63 @@ +# This file was auto-generated by Fern from our API Definition. + +import enum +import inspect +import typing + +import typing_extensions + +T = typing.TypeVar("T", bound=typing.Callable[..., typing.Any]) + +FERN_CONFIG_KEY = "__fern" + + +class RouteArgs(typing_extensions.TypedDict): + openapi_extra: typing.Optional[typing.Dict[str, typing.Any]] + tags: typing.Optional[typing.List[typing.Union[str, enum.Enum]]] + include_in_schema: bool + + +DEFAULT_ROUTE_ARGS = RouteArgs(openapi_extra=None, tags=None, include_in_schema=True) + + +def get_route_args(endpoint_function: typing.Callable[..., typing.Any], *, default_tag: str) -> RouteArgs: + unwrapped = inspect.unwrap(endpoint_function, stop=(lambda f: hasattr(f, FERN_CONFIG_KEY))) + route_args = typing.cast(RouteArgs, getattr(unwrapped, FERN_CONFIG_KEY, DEFAULT_ROUTE_ARGS)) + if route_args["tags"] is None: + return RouteArgs( + openapi_extra=route_args["openapi_extra"], + tags=[default_tag], + include_in_schema=route_args["include_in_schema"], + ) + return route_args + + +def route_args( + openapi_extra: typing.Optional[typing.Dict[str, typing.Any]] = None, + tags: typing.Optional[typing.List[typing.Union[str, enum.Enum]]] = None, + include_in_schema: bool = True, +) -> typing.Callable[[T], T]: + """ + this decorator allows you to forward certain args to the FastAPI route decorator. + + usage: + @route_args(openapi_extra=...) + def your_endpoint_method(... + + currently supported args: + - openapi_extra + - tags + + if there's another FastAPI route arg you need to pass through, please + contact the Fern team! + """ + + def decorator(endpoint_function: T) -> T: + setattr( + endpoint_function, + FERN_CONFIG_KEY, + RouteArgs(openapi_extra=openapi_extra, tags=tags, include_in_schema=include_in_schema), + ) + return endpoint_function + + return decorator diff --git a/seed/fastapi/grpc/core/security/__init__.py b/seed/fastapi/grpc/core/security/__init__.py new file mode 100644 index 00000000000..e69ee6d9c5a --- /dev/null +++ b/seed/fastapi/grpc/core/security/__init__.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +from .bearer import BearerToken + +__all__ = ["BearerToken"] diff --git a/seed/fastapi/grpc/core/security/bearer.py b/seed/fastapi/grpc/core/security/bearer.py new file mode 100644 index 00000000000..023342b668d --- /dev/null +++ b/seed/fastapi/grpc/core/security/bearer.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import fastapi + +from ..exceptions import UnauthorizedException + + +class BearerToken: + def __init__(self, token: str): + self.token = token + + +def HTTPBearer(request: fastapi.requests.Request) -> BearerToken: + authorization_header_value = request.headers.get("Authorization") + if not authorization_header_value: + raise UnauthorizedException("Missing Authorization header") + scheme, _, token = authorization_header_value.partition(" ") + if scheme.lower() != "bearer": + raise UnauthorizedException("Authorization header scheme is not bearer") + if not token: + raise UnauthorizedException("Authorization header is missing a token") + return BearerToken(token) diff --git a/seed/fastapi/grpc/register.py b/seed/fastapi/grpc/register.py new file mode 100644 index 00000000000..cad83228e21 --- /dev/null +++ b/seed/fastapi/grpc/register.py @@ -0,0 +1,44 @@ +# This file was auto-generated by Fern from our API Definition. + +import glob +import importlib +import os +import types +import typing + +import fastapi +import starlette.exceptions +from fastapi import params + +from .core.abstract_fern_service import AbstractFernService +from .core.exceptions import default_exception_handler, fern_http_exception_handler, http_exception_handler +from .core.exceptions.fern_http_exception import FernHTTPException +from .resources.user.service.service import AbstractUserService + + +def register( + _app: fastapi.FastAPI, + *, + user: AbstractUserService, + dependencies: typing.Optional[typing.Sequence[params.Depends]] = None +) -> None: + _app.include_router(__register_service(user), dependencies=dependencies) + + _app.add_exception_handler(FernHTTPException, fern_http_exception_handler) # type: ignore + _app.add_exception_handler(starlette.exceptions.HTTPException, http_exception_handler) # type: ignore + _app.add_exception_handler(Exception, default_exception_handler) # type: ignore + + +def __register_service(service: AbstractFernService) -> fastapi.APIRouter: + router = fastapi.APIRouter() + type(service)._init_fern(router) + return router + + +def register_validators(module: types.ModuleType) -> None: + validators_directory: str = os.path.dirname(module.__file__) # type: ignore + for path in glob.glob(os.path.join(validators_directory, "**/*.py"), recursive=True): + if os.path.isfile(path): + relative_path = os.path.relpath(path, start=validators_directory) + module_path = ".".join([module.__name__] + relative_path[:-3].split("/")) + importlib.import_module(module_path) diff --git a/seed/fastapi/grpc/resources/__init__.py b/seed/fastapi/grpc/resources/__init__.py new file mode 100644 index 00000000000..7a89aa4eecd --- /dev/null +++ b/seed/fastapi/grpc/resources/__init__.py @@ -0,0 +1,6 @@ +# This file was auto-generated by Fern from our API Definition. + +from . import user +from .user import CreateUserRequest, CreateUserResponse, Metadata, MetadataValue, User + +__all__ = ["CreateUserRequest", "CreateUserResponse", "Metadata", "MetadataValue", "User", "user"] diff --git a/seed/fastapi/grpc/resources/user/__init__.py b/seed/fastapi/grpc/resources/user/__init__.py new file mode 100644 index 00000000000..3c44b656765 --- /dev/null +++ b/seed/fastapi/grpc/resources/user/__init__.py @@ -0,0 +1,6 @@ +# This file was auto-generated by Fern from our API Definition. + +from .service import CreateUserRequest +from .types import CreateUserResponse, Metadata, MetadataValue, User + +__all__ = ["CreateUserRequest", "CreateUserResponse", "Metadata", "MetadataValue", "User"] diff --git a/seed/fastapi/grpc/resources/user/service/__init__.py b/seed/fastapi/grpc/resources/user/service/__init__.py new file mode 100644 index 00000000000..2853f8ef78a --- /dev/null +++ b/seed/fastapi/grpc/resources/user/service/__init__.py @@ -0,0 +1,6 @@ +# This file was auto-generated by Fern from our API Definition. + +from .create_user_request import CreateUserRequest +from .service import AbstractUserService + +__all__ = ["AbstractUserService", "CreateUserRequest"] diff --git a/seed/fastapi/grpc/resources/user/service/create_user_request.py b/seed/fastapi/grpc/resources/user/service/create_user_request.py new file mode 100644 index 00000000000..57f22ce991e --- /dev/null +++ b/seed/fastapi/grpc/resources/user/service/create_user_request.py @@ -0,0 +1,21 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic + +from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class CreateUserRequest(UniversalBaseModel): + username: str + email: typing.Optional[str] = None + age: typing.Optional[int] = None + weight: typing.Optional[float] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="forbid") # type: ignore # Pydantic v2 + else: + + class Config: + extra = pydantic.Extra.forbid diff --git a/seed/fastapi/grpc/resources/user/service/service.py b/seed/fastapi/grpc/resources/user/service/service.py new file mode 100644 index 00000000000..18e95a1e333 --- /dev/null +++ b/seed/fastapi/grpc/resources/user/service/service.py @@ -0,0 +1,126 @@ +# This file was auto-generated by Fern from our API Definition. + +import abc +import functools +import inspect +import logging +import typing + +import fastapi + +from ....core.abstract_fern_service import AbstractFernService +from ....core.exceptions.fern_http_exception import FernHTTPException +from ....core.route_args import get_route_args +from ..types.create_user_response import CreateUserResponse +from ..types.user import User +from .create_user_request import CreateUserRequest + + +class AbstractUserService(AbstractFernService): + """ + AbstractUserService is an abstract class containing the methods that you should implement. + + Each method is associated with an API route, which will be registered + with FastAPI when you register your implementation using Fern's register() + function. + """ + + @abc.abstractmethod + def create_user(self, *, body: CreateUserRequest) -> CreateUserResponse: + ... + + @abc.abstractmethod + def get_user( + self, + *, + username: typing.Optional[str] = None, + age: typing.Optional[int] = None, + weight: typing.Optional[float] = None, + ) -> User: + ... + + """ + Below are internal methods used by Fern to register your implementation. + You can ignore them. + """ + + @classmethod + def _init_fern(cls, router: fastapi.APIRouter) -> None: + cls.__init_create_user(router=router) + cls.__init_get_user(router=router) + + @classmethod + def __init_create_user(cls, router: fastapi.APIRouter) -> None: + endpoint_function = inspect.signature(cls.create_user) + new_parameters: typing.List[inspect.Parameter] = [] + for index, (parameter_name, parameter) in enumerate(endpoint_function.parameters.items()): + if index == 0: + new_parameters.append(parameter.replace(default=fastapi.Depends(cls))) + elif parameter_name == "body": + new_parameters.append(parameter.replace(default=fastapi.Body(...))) + else: + new_parameters.append(parameter) + setattr(cls.create_user, "__signature__", endpoint_function.replace(parameters=new_parameters)) + + @functools.wraps(cls.create_user) + def wrapper(*args: typing.Any, **kwargs: typing.Any) -> CreateUserResponse: + try: + return cls.create_user(*args, **kwargs) + except FernHTTPException as e: + logging.getLogger(f"{cls.__module__}.{cls.__name__}").warn( + f"Endpoint 'create_user' unexpectedly threw {e.__class__.__name__}. " + + f"If this was intentional, please add {e.__class__.__name__} to " + + "the endpoint's errors list in your Fern Definition." + ) + raise e + + # this is necessary for FastAPI to find forward-ref'ed type hints. + # https://github.com/tiangolo/fastapi/pull/5077 + wrapper.__globals__.update(cls.create_user.__globals__) + + router.post( + path="/users", + response_model=CreateUserResponse, + description=AbstractUserService.create_user.__doc__, + **get_route_args(cls.create_user, default_tag="user"), + )(wrapper) + + @classmethod + def __init_get_user(cls, router: fastapi.APIRouter) -> None: + endpoint_function = inspect.signature(cls.get_user) + new_parameters: typing.List[inspect.Parameter] = [] + for index, (parameter_name, parameter) in enumerate(endpoint_function.parameters.items()): + if index == 0: + new_parameters.append(parameter.replace(default=fastapi.Depends(cls))) + elif parameter_name == "username": + new_parameters.append(parameter.replace(default=fastapi.Query(default=None))) + elif parameter_name == "age": + new_parameters.append(parameter.replace(default=fastapi.Query(default=None))) + elif parameter_name == "weight": + new_parameters.append(parameter.replace(default=fastapi.Query(default=None))) + else: + new_parameters.append(parameter) + setattr(cls.get_user, "__signature__", endpoint_function.replace(parameters=new_parameters)) + + @functools.wraps(cls.get_user) + def wrapper(*args: typing.Any, **kwargs: typing.Any) -> User: + try: + return cls.get_user(*args, **kwargs) + except FernHTTPException as e: + logging.getLogger(f"{cls.__module__}.{cls.__name__}").warn( + f"Endpoint 'get_user' unexpectedly threw {e.__class__.__name__}. " + + f"If this was intentional, please add {e.__class__.__name__} to " + + "the endpoint's errors list in your Fern Definition." + ) + raise e + + # this is necessary for FastAPI to find forward-ref'ed type hints. + # https://github.com/tiangolo/fastapi/pull/5077 + wrapper.__globals__.update(cls.get_user.__globals__) + + router.get( + path="/users", + response_model=User, + description=AbstractUserService.get_user.__doc__, + **get_route_args(cls.get_user, default_tag="user"), + )(wrapper) diff --git a/seed/fastapi/grpc/resources/user/types/__init__.py b/seed/fastapi/grpc/resources/user/types/__init__.py new file mode 100644 index 00000000000..25fbdf08a23 --- /dev/null +++ b/seed/fastapi/grpc/resources/user/types/__init__.py @@ -0,0 +1,8 @@ +# This file was auto-generated by Fern from our API Definition. + +from .create_user_response import CreateUserResponse +from .metadata import Metadata +from .metadata_value import MetadataValue +from .user import User + +__all__ = ["CreateUserResponse", "Metadata", "MetadataValue", "User"] diff --git a/seed/fastapi/grpc/resources/user/types/create_user_response.py b/seed/fastapi/grpc/resources/user/types/create_user_response.py new file mode 100644 index 00000000000..ecb8b2f0183 --- /dev/null +++ b/seed/fastapi/grpc/resources/user/types/create_user_response.py @@ -0,0 +1,19 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic + +from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .user import User + + +class CreateUserResponse(UniversalBaseModel): + user: User + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="forbid") # type: ignore # Pydantic v2 + else: + + class Config: + extra = pydantic.Extra.forbid diff --git a/seed/fastapi/grpc/resources/user/types/metadata.py b/seed/fastapi/grpc/resources/user/types/metadata.py new file mode 100644 index 00000000000..511c4984799 --- /dev/null +++ b/seed/fastapi/grpc/resources/user/types/metadata.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from .metadata_value import MetadataValue + +Metadata = typing.Dict[str, typing.Optional[MetadataValue]] diff --git a/seed/fastapi/grpc/resources/user/types/metadata_value.py b/seed/fastapi/grpc/resources/user/types/metadata_value.py new file mode 100644 index 00000000000..28b9ba3e1b6 --- /dev/null +++ b/seed/fastapi/grpc/resources/user/types/metadata_value.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +MetadataValue = typing.Union[float, str, bool, typing.List[MetadataValue]] diff --git a/seed/fastapi/grpc/resources/user/types/user.py b/seed/fastapi/grpc/resources/user/types/user.py new file mode 100644 index 00000000000..2590bb66d4a --- /dev/null +++ b/seed/fastapi/grpc/resources/user/types/user.py @@ -0,0 +1,24 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic + +from ....core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .metadata import Metadata + + +class User(UniversalBaseModel): + id: str + username: str + email: typing.Optional[str] = None + age: typing.Optional[int] = None + weight: typing.Optional[float] = None + metadata: typing.Optional[Metadata] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="forbid") # type: ignore # Pydantic v2 + else: + + class Config: + extra = pydantic.Extra.forbid diff --git a/seed/fastapi/grpc/snippet-templates.json b/seed/fastapi/grpc/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/fastapi/grpc/snippet.json b/seed/fastapi/grpc/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-sdk/examples/.github/workflows/ci.yml b/seed/go-fiber/grpc-proto/.github/workflows/ci.yml similarity index 100% rename from seed/go-sdk/examples/.github/workflows/ci.yml rename to seed/go-fiber/grpc-proto/.github/workflows/ci.yml diff --git a/seed/go-fiber/grpc-proto/.mock/fern.config.json b/seed/go-fiber/grpc-proto/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/go-fiber/grpc-proto/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/go-fiber/grpc-proto/.mock/generators.yml b/seed/go-fiber/grpc-proto/.mock/generators.yml new file mode 100644 index 00000000000..902c4f50168 --- /dev/null +++ b/seed/go-fiber/grpc-proto/.mock/generators.yml @@ -0,0 +1,5 @@ +api: + - proto: + root: proto + target: proto/user/v1/user.proto + local-generation: true \ No newline at end of file diff --git a/seed/go-fiber/grpc-proto/.mock/proto/google/api/annotations.proto b/seed/go-fiber/grpc-proto/.mock/proto/google/api/annotations.proto new file mode 100644 index 00000000000..8ff42098404 --- /dev/null +++ b/seed/go-fiber/grpc-proto/.mock/proto/google/api/annotations.proto @@ -0,0 +1,31 @@ +// Copyright 2015 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/api/http.proto"; +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "AnnotationsProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // See `HttpRule`. + HttpRule http = 72295728; +} \ No newline at end of file diff --git a/seed/go-fiber/grpc-proto/.mock/proto/google/api/field_behavior.proto b/seed/go-fiber/grpc-proto/.mock/proto/google/api/field_behavior.proto new file mode 100644 index 00000000000..128799c558d --- /dev/null +++ b/seed/go-fiber/grpc-proto/.mock/proto/google/api/field_behavior.proto @@ -0,0 +1,104 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "FieldBehaviorProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.FieldOptions { + // A designation of a specific field behavior (required, output only, etc.) + // in protobuf messages. + // + // Examples: + // + // string name = 1 [(google.api.field_behavior) = REQUIRED]; + // State state = 1 [(google.api.field_behavior) = OUTPUT_ONLY]; + // google.protobuf.Duration ttl = 1 + // [(google.api.field_behavior) = INPUT_ONLY]; + // google.protobuf.Timestamp expire_time = 1 + // [(google.api.field_behavior) = OUTPUT_ONLY, + // (google.api.field_behavior) = IMMUTABLE]; + repeated google.api.FieldBehavior field_behavior = 1052; +} + +// An indicator of the behavior of a given field (for example, that a field +// is required in requests, or given as output but ignored as input). +// This **does not** change the behavior in protocol buffers itself; it only +// denotes the behavior and may affect how API tooling handles the field. +// +// Note: This enum **may** receive new values in the future. +enum FieldBehavior { + // Conventional default for enums. Do not use this. + FIELD_BEHAVIOR_UNSPECIFIED = 0; + + // Specifically denotes a field as optional. + // While all fields in protocol buffers are optional, this may be specified + // for emphasis if appropriate. + OPTIONAL = 1; + + // Denotes a field as required. + // This indicates that the field **must** be provided as part of the request, + // and failure to do so will cause an error (usually `INVALID_ARGUMENT`). + REQUIRED = 2; + + // Denotes a field as output only. + // This indicates that the field is provided in responses, but including the + // field in a request does nothing (the server *must* ignore it and + // *must not* throw an error as a result of the field's presence). + OUTPUT_ONLY = 3; + + // Denotes a field as input only. + // This indicates that the field is provided in requests, and the + // corresponding field is not included in output. + INPUT_ONLY = 4; + + // Denotes a field as immutable. + // This indicates that the field may be set once in a request to create a + // resource, but may not be changed thereafter. + IMMUTABLE = 5; + + // Denotes that a (repeated) field is an unordered list. + // This indicates that the service may provide the elements of the list + // in any arbitrary order, rather than the order the user originally + // provided. Additionally, the list's order may or may not be stable. + UNORDERED_LIST = 6; + + // Denotes that this field returns a non-empty default value if not set. + // This indicates that if the user provides the empty value in a request, + // a non-empty value will be returned. The user will not be aware of what + // non-empty value to expect. + NON_EMPTY_DEFAULT = 7; + + // Denotes that the field in a resource (a message annotated with + // google.api.resource) is used in the resource name to uniquely identify the + // resource. For AIP-compliant APIs, this should only be applied to the + // `name` field on the resource. + // + // This behavior should not be applied to references to other resources within + // the message. + // + // The identifier field of resources often have different field behavior + // depending on the request it is embedded in (e.g. for Create methods name + // is optional and unused, while for Update methods it is required). Instead + // of method-specific annotations, only `IDENTIFIER` is required. + IDENTIFIER = 8; +} \ No newline at end of file diff --git a/seed/go-fiber/grpc-proto/.mock/proto/google/api/http.proto b/seed/go-fiber/grpc-proto/.mock/proto/google/api/http.proto new file mode 100644 index 00000000000..c8392381eb9 --- /dev/null +++ b/seed/go-fiber/grpc-proto/.mock/proto/google/api/http.proto @@ -0,0 +1,379 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "HttpProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Defines the HTTP configuration for an API service. It contains a list of +// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method +// to one or more HTTP REST API methods. +message Http { + // A list of HTTP configuration rules that apply to individual API methods. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated HttpRule rules = 1; + + // When set to true, URL path parameters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // + // The default behavior is to not decode RFC 6570 reserved characters in multi + // segment matches. + bool fully_decode_reserved_expansion = 2; +} + +// # gRPC Transcoding +// +// gRPC Transcoding is a feature for mapping between a gRPC method and one or +// more HTTP REST endpoints. It allows developers to build a single API service +// that supports both gRPC APIs and REST APIs. Many systems, including [Google +// APIs](https://github.com/googleapis/googleapis), +// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC +// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), +// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature +// and use it for large scale production services. +// +// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies +// how different portions of the gRPC request message are mapped to the URL +// path, URL query parameters, and HTTP request body. It also controls how the +// gRPC response message is mapped to the HTTP response body. `HttpRule` is +// typically specified as an `google.api.http` annotation on the gRPC method. +// +// Each mapping specifies a URL path template and an HTTP method. The path +// template may refer to one or more fields in the gRPC request message, as long +// as each field is a non-repeated field with a primitive (non-message) type. +// The path template controls how fields of the request message are mapped to +// the URL path. +// +// Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/{name=messages/*}" +// }; +// } +// } +// message GetMessageRequest { +// string name = 1; // Mapped to URL path. +// } +// message Message { +// string text = 1; // The resource content. +// } +// +// This enables an HTTP REST to gRPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` +// +// Any fields in the request message which are not bound by the path template +// automatically become HTTP query parameters if there is no HTTP request body. +// For example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get:"/v1/messages/{message_id}" +// }; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // Mapped to URL path. +// int64 revision = 2; // Mapped to URL query parameter `revision`. +// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. +// } +// +// This enables a HTTP JSON to RPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | +// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: +// "foo"))` +// +// Note that fields which are mapped to URL query parameters must have a +// primitive type or a repeated primitive type or a non-repeated message type. +// In the case of a repeated type, the parameter can be repeated in the URL +// as `...?param=A¶m=B`. In the case of a message type, each field of the +// message is mapped to a separate parameter, such as +// `...?foo.a=A&foo.b=B&foo.c=C`. +// +// For HTTP methods that allow a request body, the `body` field +// specifies the mapping. Consider a REST update method on the +// message resource collection: +// +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } +// +// The following HTTP JSON to RPC mapping is enabled, where the +// representation of the JSON in the request body is determined by +// protos JSON encoding: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" message { text: "Hi!" })` +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. This enables the following alternative definition of +// the update method: +// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } +// +// +// The following HTTP JSON to RPC mapping is enabled: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" text: "Hi!")` +// +// Note that when using `*` in the body mapping, it is not possible to +// have HTTP parameters, as all fields not bound by the path end in +// the body. This makes this option more rarely used in practice when +// defining REST APIs. The common usage of `*` is in custom methods +// which don't use the URL at all for transferring data. +// +// It is possible to define multiple HTTP methods for one RPC by using +// the `additional_bindings` option. Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } +// +// This enables the following two alternative HTTP JSON to RPC mappings: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` +// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: +// "123456")` +// +// ## Rules for HTTP mapping +// +// 1. Leaf request fields (recursive expansion nested messages in the request +// message) are classified into three categories: +// - Fields referred by the path template. They are passed via the URL path. +// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They +// are passed via the HTTP +// request body. +// - All other fields are passed via the URL query parameters, and the +// parameter name is the field path in the request message. A repeated +// field can be represented as multiple query parameters under the same +// name. +// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL +// query parameter, all fields +// are passed via URL path and HTTP request body. +// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP +// request body, all +// fields are passed via URL path and URL query parameters. +// +// ### Path template syntax +// +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +// +// The syntax `*` matches a single URL path segment. The syntax `**` matches +// zero or more URL path segments, which must be the last part of the URL path +// except the `Verb`. +// +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable +// matches a single path segment, its template may be omitted, e.g. `{var}` +// is equivalent to `{var=*}`. +// +// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` +// contains any reserved character, such characters should be percent-encoded +// before the matching. +// +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path on the client +// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The +// server side does the reverse decoding. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{var}`. +// +// If a variable contains multiple path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path on the +// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. +// The server side does the reverse decoding, except "%2F" and "%2f" are left +// unchanged. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{+var}`. +// +// ## Using gRPC API Service Configuration +// +// gRPC API Service Configuration (service config) is a configuration language +// for configuring a gRPC service to become a user-facing product. The +// service config is simply the YAML representation of the `google.api.Service` +// proto message. +// +// As an alternative to annotating your proto file, you can configure gRPC +// transcoding in your service config YAML files. You do this by specifying a +// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same +// effect as the proto annotation. This can be particularly useful if you +// have a proto that is reused in multiple services. Note that any transcoding +// specified in the service config will override any matching transcoding +// configuration in the proto. +// +// Example: +// +// http: +// rules: +// # Selects a gRPC method and applies HttpRule to it. +// - selector: example.v1.Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} +// +// ## Special notes +// +// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the +// proto to JSON conversion must follow the [proto3 +// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). +// +// While the single segment variable follows the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String +// Expansion, the multi segment variable **does not** follow RFC 6570 Section +// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding +// for multi segment variables. +// +// The path variables **must not** refer to any repeated or mapped field, +// because client libraries are not capable of handling such variable expansion. +// +// The path variables **must not** capture the leading "/" character. The reason +// is that the most common use case "{var}" does not capture the leading "/" +// character. For consistency, all path variables must share the same behavior. +// +// Repeated message fields must not be mapped to URL query parameters, because +// no client library can support such complicated mapping. +// +// If an API needs to use a JSON array for request or response body, it can map +// the request or response body to a repeated field. However, some gRPC +// Transcoding implementations may not support this feature. +message HttpRule { + // Selects a method to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax + // details. + string selector = 1; + + // Determines the URL pattern is matched by this rules. This pattern can be + // used with any of the {get|put|post|delete|patch} methods. A custom method + // can be defined using the 'custom' field. + oneof pattern { + // Maps to HTTP GET. Used for listing and getting information about + // resources. + string get = 2; + + // Maps to HTTP PUT. Used for replacing a resource. + string put = 3; + + // Maps to HTTP POST. Used for creating a resource or performing an action. + string post = 4; + + // Maps to HTTP DELETE. Used for deleting a resource. + string delete = 5; + + // Maps to HTTP PATCH. Used for updating a resource. + string patch = 6; + + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. + CustomHttpPattern custom = 8; + } + + // The name of the request field whose value is mapped to the HTTP request + // body, or `*` for mapping all request fields not captured by the path + // pattern to the HTTP body, or omitted for not having any HTTP request body. + // + // NOTE: the referred field must be present at the top-level of the request + // message type. + string body = 7; + + // Optional. The name of the response field whose value is mapped to the HTTP + // response body. When omitted, the entire response message will be used + // as the HTTP response body. + // + // NOTE: The referred field must be present at the top-level of the response + // message type. + string response_body = 12; + + // Additional HTTP bindings for the selector. Nested bindings must + // not contain an `additional_bindings` field themselves (that is, + // the nesting may only be one level deep). + repeated HttpRule additional_bindings = 11; +} + +// A custom pattern is used for defining custom HTTP verb. +message CustomHttpPattern { + // The name of this custom HTTP verb. + string kind = 1; + + // The path matched by this custom verb. + string path = 2; +} \ No newline at end of file diff --git a/seed/go-fiber/grpc-proto/.mock/proto/user/v1/user.proto b/seed/go-fiber/grpc-proto/.mock/proto/user/v1/user.proto new file mode 100644 index 00000000000..a3ad0195bd4 --- /dev/null +++ b/seed/go-fiber/grpc-proto/.mock/proto/user/v1/user.proto @@ -0,0 +1,39 @@ +syntax = "proto3"; + +package user.v1; + +import "google/api/annotations.proto"; +import "google/api/field_behavior.proto"; +import "google/protobuf/struct.proto"; + +option go_package = "user/v1"; +option csharp_namespace = "User.V1"; + +message UserModel { + string username = 1; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateRequest { + string username = 1; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateResponse { + UserModel user = 1; +} + +service User { + rpc Create(CreateRequest) returns (CreateResponse) { + option (google.api.http) = { + post: "/users" + body: "*" + }; + } +} \ No newline at end of file diff --git a/seed/go-sdk/examples/core/extra_properties.go b/seed/go-fiber/grpc-proto/core/extra_properties.go similarity index 100% rename from seed/go-sdk/examples/core/extra_properties.go rename to seed/go-fiber/grpc-proto/core/extra_properties.go diff --git a/seed/go-sdk/examples/core/extra_properties_test.go b/seed/go-fiber/grpc-proto/core/extra_properties_test.go similarity index 100% rename from seed/go-sdk/examples/core/extra_properties_test.go rename to seed/go-fiber/grpc-proto/core/extra_properties_test.go diff --git a/seed/go-sdk/examples/core/stringer.go b/seed/go-fiber/grpc-proto/core/stringer.go similarity index 100% rename from seed/go-sdk/examples/core/stringer.go rename to seed/go-fiber/grpc-proto/core/stringer.go diff --git a/seed/go-sdk/examples/core/time.go b/seed/go-fiber/grpc-proto/core/time.go similarity index 100% rename from seed/go-sdk/examples/core/time.go rename to seed/go-fiber/grpc-proto/core/time.go diff --git a/seed/go-fiber/grpc-proto/go.mod b/seed/go-fiber/grpc-proto/go.mod new file mode 100644 index 00000000000..d3f2562778e --- /dev/null +++ b/seed/go-fiber/grpc-proto/go.mod @@ -0,0 +1,8 @@ +module github.com/grpc-proto/fern + +go 1.13 + +require ( + github.com/stretchr/testify v1.7.0 + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/seed/go-fiber/grpc-proto/go.sum b/seed/go-fiber/grpc-proto/go.sum new file mode 100644 index 00000000000..fc3dd9e67e8 --- /dev/null +++ b/seed/go-fiber/grpc-proto/go.sum @@ -0,0 +1,12 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/seed/go-fiber/grpc-proto/snippet-templates.json b/seed/go-fiber/grpc-proto/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/grpc-proto/snippet.json b/seed/go-fiber/grpc-proto/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/grpc-proto/types.go b/seed/go-fiber/grpc-proto/types.go new file mode 100644 index 00000000000..8825dca1723 --- /dev/null +++ b/seed/go-fiber/grpc-proto/types.go @@ -0,0 +1,81 @@ +// This file was auto-generated by Fern from our API Definition. + +package api + +import ( + json "encoding/json" + fmt "fmt" + core "github.com/grpc-proto/fern/core" +) + +type CreateResponse struct { + User *UserModel `json:"user,omitempty" url:"user,omitempty"` + + extraProperties map[string]interface{} +} + +func (c *CreateResponse) GetExtraProperties() map[string]interface{} { + return c.extraProperties +} + +func (c *CreateResponse) UnmarshalJSON(data []byte) error { + type unmarshaler CreateResponse + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *c = CreateResponse(value) + + extraProperties, err := core.ExtractExtraProperties(data, *c) + if err != nil { + return err + } + c.extraProperties = extraProperties + + return nil +} + +func (c *CreateResponse) String() string { + if value, err := core.StringifyJSON(c); err == nil { + return value + } + return fmt.Sprintf("%#v", c) +} + +type UserModel struct { + Username *string `json:"username,omitempty" url:"username,omitempty"` + Email *string `json:"email,omitempty" url:"email,omitempty"` + Age *int `json:"age,omitempty" url:"age,omitempty"` + Weight *float64 `json:"weight,omitempty" url:"weight,omitempty"` + Metadata map[string]interface{} `json:"metadata,omitempty" url:"metadata,omitempty"` + + extraProperties map[string]interface{} +} + +func (u *UserModel) GetExtraProperties() map[string]interface{} { + return u.extraProperties +} + +func (u *UserModel) UnmarshalJSON(data []byte) error { + type unmarshaler UserModel + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *u = UserModel(value) + + extraProperties, err := core.ExtractExtraProperties(data, *u) + if err != nil { + return err + } + u.extraProperties = extraProperties + + return nil +} + +func (u *UserModel) String() string { + if value, err := core.StringifyJSON(u); err == nil { + return value + } + return fmt.Sprintf("%#v", u) +} diff --git a/seed/go-fiber/grpc-proto/user.go b/seed/go-fiber/grpc-proto/user.go new file mode 100644 index 00000000000..f6a496f5d8a --- /dev/null +++ b/seed/go-fiber/grpc-proto/user.go @@ -0,0 +1,11 @@ +// This file was auto-generated by Fern from our API Definition. + +package api + +type CreateRequest struct { + Username *string `json:"username,omitempty" url:"-"` + Email *string `json:"email,omitempty" url:"-"` + Age *int `json:"age,omitempty" url:"-"` + Weight *float64 `json:"weight,omitempty" url:"-"` + Metadata map[string]interface{} `json:"metadata,omitempty" url:"-"` +} diff --git a/seed/go-fiber/grpc/.github/workflows/ci.yml b/seed/go-fiber/grpc/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-fiber/grpc/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-fiber/grpc/.mock/definition/api.yml b/seed/go-fiber/grpc/.mock/definition/api.yml new file mode 100644 index 00000000000..15a9f823724 --- /dev/null +++ b/seed/go-fiber/grpc/.mock/definition/api.yml @@ -0,0 +1,4 @@ +name: api + +error-discrimination: + strategy: status-code \ No newline at end of file diff --git a/seed/go-fiber/grpc/.mock/definition/user.yml b/seed/go-fiber/grpc/.mock/definition/user.yml new file mode 100644 index 00000000000..80223300afc --- /dev/null +++ b/seed/go-fiber/grpc/.mock/definition/user.yml @@ -0,0 +1,61 @@ +types: + Metadata: + type: map> + encoding: + proto: + type: google.protobuf.Struct + + MetadataValue: + discriminated: false + union: + - double + - string + - boolean + - list + encoding: + proto: + type: google.protobuf.Value + + User: + properties: + id: string + username: string + email: optional + age: optional + weight: optional + metadata: optional + + CreateUserResponse: + properties: + user: User + +service: + auth: false + base-path: / + transport: + grpc: + service-name: UserService + endpoints: + createUser: + method: POST + path: /users + request: + name: CreateUserRequest + body: + properties: + username: string + email: optional + age: optional + weight: optional + response: CreateUserResponse + + getUser: + method: GET + path: /users + request: + name: GetUserRequest + query-parameters: + username: optional + age: optional + weight: optional + response: User diff --git a/seed/go-fiber/grpc/.mock/fern.config.json b/seed/go-fiber/grpc/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/go-fiber/grpc/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/go-fiber/grpc/.mock/generators.yml b/seed/go-fiber/grpc/.mock/generators.yml new file mode 100644 index 00000000000..f62dfba5843 --- /dev/null +++ b/seed/go-fiber/grpc/.mock/generators.yml @@ -0,0 +1,4 @@ +api: + - proto: + root: proto + target: proto/user/v1/user.proto \ No newline at end of file diff --git a/seed/go-fiber/grpc/core/extra_properties.go b/seed/go-fiber/grpc/core/extra_properties.go new file mode 100644 index 00000000000..a6af3e12410 --- /dev/null +++ b/seed/go-fiber/grpc/core/extra_properties.go @@ -0,0 +1,141 @@ +package core + +import ( + "bytes" + "encoding/json" + "fmt" + "reflect" + "strings" +) + +// MarshalJSONWithExtraProperty marshals the given value to JSON, including the extra property. +func MarshalJSONWithExtraProperty(marshaler interface{}, key string, value interface{}) ([]byte, error) { + return MarshalJSONWithExtraProperties(marshaler, map[string]interface{}{key: value}) +} + +// MarshalJSONWithExtraProperties marshals the given value to JSON, including any extra properties. +func MarshalJSONWithExtraProperties(marshaler interface{}, extraProperties map[string]interface{}) ([]byte, error) { + bytes, err := json.Marshal(marshaler) + if err != nil { + return nil, err + } + if len(extraProperties) == 0 { + return bytes, nil + } + keys, err := getKeys(marshaler) + if err != nil { + return nil, err + } + for _, key := range keys { + if _, ok := extraProperties[key]; ok { + return nil, fmt.Errorf("cannot add extra property %q because it is already defined on the type", key) + } + } + extraBytes, err := json.Marshal(extraProperties) + if err != nil { + return nil, err + } + if isEmptyJSON(bytes) { + if isEmptyJSON(extraBytes) { + return bytes, nil + } + return extraBytes, nil + } + result := bytes[:len(bytes)-1] + result = append(result, ',') + result = append(result, extraBytes[1:len(extraBytes)-1]...) + result = append(result, '}') + return result, nil +} + +// ExtractExtraProperties extracts any extra properties from the given value. +func ExtractExtraProperties(bytes []byte, value interface{}, exclude ...string) (map[string]interface{}, error) { + val := reflect.ValueOf(value) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return nil, fmt.Errorf("value must be non-nil to extract extra properties") + } + val = val.Elem() + } + if err := json.Unmarshal(bytes, &value); err != nil { + return nil, err + } + var extraProperties map[string]interface{} + if err := json.Unmarshal(bytes, &extraProperties); err != nil { + return nil, err + } + for i := 0; i < val.Type().NumField(); i++ { + key := jsonKey(val.Type().Field(i)) + if key == "" || key == "-" { + continue + } + delete(extraProperties, key) + } + for _, key := range exclude { + delete(extraProperties, key) + } + if len(extraProperties) == 0 { + return nil, nil + } + return extraProperties, nil +} + +// getKeys returns the keys associated with the given value. The value must be a +// a struct or a map with string keys. +func getKeys(value interface{}) ([]string, error) { + val := reflect.ValueOf(value) + if val.Kind() == reflect.Ptr { + val = val.Elem() + } + if !val.IsValid() { + return nil, nil + } + switch val.Kind() { + case reflect.Struct: + return getKeysForStructType(val.Type()), nil + case reflect.Map: + var keys []string + if val.Type().Key().Kind() != reflect.String { + return nil, fmt.Errorf("cannot extract keys from %T; only structs and maps with string keys are supported", value) + } + for _, key := range val.MapKeys() { + keys = append(keys, key.String()) + } + return keys, nil + default: + return nil, fmt.Errorf("cannot extract keys from %T; only structs and maps with string keys are supported", value) + } +} + +// getKeysForStructType returns all the keys associated with the given struct type, +// visiting embedded fields recursively. +func getKeysForStructType(structType reflect.Type) []string { + if structType.Kind() == reflect.Pointer { + structType = structType.Elem() + } + if structType.Kind() != reflect.Struct { + return nil + } + var keys []string + for i := 0; i < structType.NumField(); i++ { + field := structType.Field(i) + if field.Anonymous { + keys = append(keys, getKeysForStructType(field.Type)...) + continue + } + keys = append(keys, jsonKey(field)) + } + return keys +} + +// jsonKey returns the JSON key from the struct tag of the given field, +// excluding the omitempty flag (if any). +func jsonKey(field reflect.StructField) string { + return strings.TrimSuffix(field.Tag.Get("json"), ",omitempty") +} + +// isEmptyJSON returns true if the given data is empty, the empty JSON object, or +// an explicit null. +func isEmptyJSON(data []byte) bool { + return len(data) <= 2 || bytes.Equal(data, []byte("null")) +} diff --git a/seed/go-fiber/grpc/core/extra_properties_test.go b/seed/go-fiber/grpc/core/extra_properties_test.go new file mode 100644 index 00000000000..dc66fccd7f1 --- /dev/null +++ b/seed/go-fiber/grpc/core/extra_properties_test.go @@ -0,0 +1,228 @@ +package core + +import ( + "encoding/json" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type testMarshaler struct { + Name string `json:"name"` + BirthDate time.Time `json:"birthDate"` + CreatedAt time.Time `json:"created_at"` +} + +func (t *testMarshaler) MarshalJSON() ([]byte, error) { + type embed testMarshaler + var marshaler = struct { + embed + BirthDate string `json:"birthDate"` + CreatedAt string `json:"created_at"` + }{ + embed: embed(*t), + BirthDate: t.BirthDate.Format("2006-01-02"), + CreatedAt: t.CreatedAt.Format(time.RFC3339), + } + return MarshalJSONWithExtraProperty(marshaler, "type", "test") +} + +func TestMarshalJSONWithExtraProperties(t *testing.T) { + tests := []struct { + desc string + giveMarshaler interface{} + giveExtraProperties map[string]interface{} + wantBytes []byte + wantError string + }{ + { + desc: "invalid type", + giveMarshaler: []string{"invalid"}, + giveExtraProperties: map[string]interface{}{"key": "overwrite"}, + wantError: `cannot extract keys from []string; only structs and maps with string keys are supported`, + }, + { + desc: "invalid key type", + giveMarshaler: map[int]interface{}{42: "value"}, + giveExtraProperties: map[string]interface{}{"key": "overwrite"}, + wantError: `cannot extract keys from map[int]interface {}; only structs and maps with string keys are supported`, + }, + { + desc: "invalid map overwrite", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{"key": "overwrite"}, + wantError: `cannot add extra property "key" because it is already defined on the type`, + }, + { + desc: "invalid struct overwrite", + giveMarshaler: new(testMarshaler), + giveExtraProperties: map[string]interface{}{"birthDate": "2000-01-01"}, + wantError: `cannot add extra property "birthDate" because it is already defined on the type`, + }, + { + desc: "invalid struct overwrite embedded type", + giveMarshaler: new(testMarshaler), + giveExtraProperties: map[string]interface{}{"name": "bob"}, + wantError: `cannot add extra property "name" because it is already defined on the type`, + }, + { + desc: "nil", + giveMarshaler: nil, + giveExtraProperties: nil, + wantBytes: []byte(`null`), + }, + { + desc: "empty", + giveMarshaler: map[string]interface{}{}, + giveExtraProperties: map[string]interface{}{}, + wantBytes: []byte(`{}`), + }, + { + desc: "no extra properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{}, + wantBytes: []byte(`{"key":"value"}`), + }, + { + desc: "only extra properties", + giveMarshaler: map[string]interface{}{}, + giveExtraProperties: map[string]interface{}{"key": "value"}, + wantBytes: []byte(`{"key":"value"}`), + }, + { + desc: "single extra property", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{"extra": "property"}, + wantBytes: []byte(`{"key":"value","extra":"property"}`), + }, + { + desc: "multiple extra properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{"one": 1, "two": 2}, + wantBytes: []byte(`{"key":"value","one":1,"two":2}`), + }, + { + desc: "nested properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{ + "user": map[string]interface{}{ + "age": 42, + "name": "alice", + }, + }, + wantBytes: []byte(`{"key":"value","user":{"age":42,"name":"alice"}}`), + }, + { + desc: "multiple nested properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{ + "metadata": map[string]interface{}{ + "ip": "127.0.0.1", + }, + "user": map[string]interface{}{ + "age": 42, + "name": "alice", + }, + }, + wantBytes: []byte(`{"key":"value","metadata":{"ip":"127.0.0.1"},"user":{"age":42,"name":"alice"}}`), + }, + { + desc: "custom marshaler", + giveMarshaler: &testMarshaler{ + Name: "alice", + BirthDate: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), + CreatedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), + }, + giveExtraProperties: map[string]interface{}{ + "extra": "property", + }, + wantBytes: []byte(`{"name":"alice","birthDate":"2000-01-01","created_at":"2024-01-01T00:00:00Z","type":"test","extra":"property"}`), + }, + } + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + bytes, err := MarshalJSONWithExtraProperties(tt.giveMarshaler, tt.giveExtraProperties) + if tt.wantError != "" { + require.EqualError(t, err, tt.wantError) + assert.Nil(t, tt.wantBytes) + return + } + require.NoError(t, err) + assert.Equal(t, tt.wantBytes, bytes) + + value := make(map[string]interface{}) + require.NoError(t, json.Unmarshal(bytes, &value)) + }) + } +} + +func TestExtractExtraProperties(t *testing.T) { + t.Run("none", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := &user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice"}`), value) + require.NoError(t, err) + assert.Nil(t, extraProperties) + }) + + t.Run("non-nil pointer", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := &user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + require.NoError(t, err) + assert.Equal(t, map[string]interface{}{"age": float64(42)}, extraProperties) + }) + + t.Run("nil pointer", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + var value *user + _, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + assert.EqualError(t, err, "value must be non-nil to extract extra properties") + }) + + t.Run("non-zero value", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + require.NoError(t, err) + assert.Equal(t, map[string]interface{}{"age": float64(42)}, extraProperties) + }) + + t.Run("zero value", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + var value user + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + require.NoError(t, err) + assert.Equal(t, map[string]interface{}{"age": float64(42)}, extraProperties) + }) + + t.Run("exclude", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := &user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value, "age") + require.NoError(t, err) + assert.Nil(t, extraProperties) + }) +} diff --git a/seed/go-fiber/grpc/core/stringer.go b/seed/go-fiber/grpc/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-fiber/grpc/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-fiber/grpc/core/time.go b/seed/go-fiber/grpc/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-fiber/grpc/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-fiber/grpc/go.mod b/seed/go-fiber/grpc/go.mod new file mode 100644 index 00000000000..d02629e859c --- /dev/null +++ b/seed/go-fiber/grpc/go.mod @@ -0,0 +1,8 @@ +module github.com/grpc/fern + +go 1.13 + +require ( + github.com/stretchr/testify v1.7.0 + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/seed/go-fiber/grpc/go.sum b/seed/go-fiber/grpc/go.sum new file mode 100644 index 00000000000..fc3dd9e67e8 --- /dev/null +++ b/seed/go-fiber/grpc/go.sum @@ -0,0 +1,12 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/seed/go-fiber/grpc/snippet-templates.json b/seed/go-fiber/grpc/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/grpc/snippet.json b/seed/go-fiber/grpc/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-fiber/grpc/types.go b/seed/go-fiber/grpc/types.go new file mode 100644 index 00000000000..52c62abb64a --- /dev/null +++ b/seed/go-fiber/grpc/types.go @@ -0,0 +1,96 @@ +// This file was auto-generated by Fern from our API Definition. + +package api + +import ( + json "encoding/json" + fmt "fmt" +) + +type Metadata = map[string]*MetadataValue + +type MetadataValue struct { + Double float64 + String string + Boolean bool + MetadataValueList []*MetadataValue +} + +func NewMetadataValueFromDouble(value float64) *MetadataValue { + return &MetadataValue{Double: value} +} + +func NewMetadataValueFromString(value string) *MetadataValue { + return &MetadataValue{String: value} +} + +func NewMetadataValueFromBoolean(value bool) *MetadataValue { + return &MetadataValue{Boolean: value} +} + +func NewMetadataValueFromMetadataValueList(value []*MetadataValue) *MetadataValue { + return &MetadataValue{MetadataValueList: value} +} + +func (m *MetadataValue) UnmarshalJSON(data []byte) error { + var valueDouble float64 + if err := json.Unmarshal(data, &valueDouble); err == nil { + m.Double = valueDouble + return nil + } + var valueString string + if err := json.Unmarshal(data, &valueString); err == nil { + m.String = valueString + return nil + } + var valueBoolean bool + if err := json.Unmarshal(data, &valueBoolean); err == nil { + m.Boolean = valueBoolean + return nil + } + var valueMetadataValueList []*MetadataValue + if err := json.Unmarshal(data, &valueMetadataValueList); err == nil { + m.MetadataValueList = valueMetadataValueList + return nil + } + return fmt.Errorf("%s cannot be deserialized as a %T", data, m) +} + +func (m MetadataValue) MarshalJSON() ([]byte, error) { + if m.Double != 0 { + return json.Marshal(m.Double) + } + if m.String != "" { + return json.Marshal(m.String) + } + if m.Boolean != false { + return json.Marshal(m.Boolean) + } + if m.MetadataValueList != nil { + return json.Marshal(m.MetadataValueList) + } + return nil, fmt.Errorf("type %T does not include a non-empty union type", m) +} + +type MetadataValueVisitor interface { + VisitDouble(float64) error + VisitString(string) error + VisitBoolean(bool) error + VisitMetadataValueList([]*MetadataValue) error +} + +func (m *MetadataValue) Accept(visitor MetadataValueVisitor) error { + if m.Double != 0 { + return visitor.VisitDouble(m.Double) + } + if m.String != "" { + return visitor.VisitString(m.String) + } + if m.Boolean != false { + return visitor.VisitBoolean(m.Boolean) + } + if m.MetadataValueList != nil { + return visitor.VisitMetadataValueList(m.MetadataValueList) + } + return fmt.Errorf("type %T does not include a non-empty union type", m) +} diff --git a/seed/go-fiber/grpc/user.go b/seed/go-fiber/grpc/user.go new file mode 100644 index 00000000000..35a927804df --- /dev/null +++ b/seed/go-fiber/grpc/user.go @@ -0,0 +1,95 @@ +// This file was auto-generated by Fern from our API Definition. + +package api + +import ( + json "encoding/json" + fmt "fmt" + core "github.com/grpc/fern/core" +) + +type CreateUserRequest struct { + Username string `json:"username" url:"-"` + Email *string `json:"email,omitempty" url:"-"` + Age *int `json:"age,omitempty" url:"-"` + Weight *float64 `json:"weight,omitempty" url:"-"` +} + +type GetUserRequest struct { + Username *string `query:"username"` + Age *int `query:"age"` + Weight *float64 `query:"weight"` +} + +type CreateUserResponse struct { + User *User `json:"user,omitempty" url:"user,omitempty"` + + extraProperties map[string]interface{} +} + +func (c *CreateUserResponse) GetExtraProperties() map[string]interface{} { + return c.extraProperties +} + +func (c *CreateUserResponse) UnmarshalJSON(data []byte) error { + type unmarshaler CreateUserResponse + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *c = CreateUserResponse(value) + + extraProperties, err := core.ExtractExtraProperties(data, *c) + if err != nil { + return err + } + c.extraProperties = extraProperties + + return nil +} + +func (c *CreateUserResponse) String() string { + if value, err := core.StringifyJSON(c); err == nil { + return value + } + return fmt.Sprintf("%#v", c) +} + +type User struct { + Id string `json:"id" url:"id"` + Username string `json:"username" url:"username"` + Email *string `json:"email,omitempty" url:"email,omitempty"` + Age *int `json:"age,omitempty" url:"age,omitempty"` + Weight *float64 `json:"weight,omitempty" url:"weight,omitempty"` + Metadata *Metadata `json:"metadata,omitempty" url:"metadata,omitempty"` + + extraProperties map[string]interface{} +} + +func (u *User) GetExtraProperties() map[string]interface{} { + return u.extraProperties +} + +func (u *User) UnmarshalJSON(data []byte) error { + type unmarshaler User + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *u = User(value) + + extraProperties, err := core.ExtractExtraProperties(data, *u) + if err != nil { + return err + } + u.extraProperties = extraProperties + + return nil +} + +func (u *User) String() string { + if value, err := core.StringifyJSON(u); err == nil { + return value + } + return fmt.Sprintf("%#v", u) +} diff --git a/seed/go-model/grpc-proto/.github/workflows/ci.yml b/seed/go-model/grpc-proto/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-model/grpc-proto/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-model/grpc-proto/.mock/fern.config.json b/seed/go-model/grpc-proto/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/go-model/grpc-proto/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/go-model/grpc-proto/.mock/generators.yml b/seed/go-model/grpc-proto/.mock/generators.yml new file mode 100644 index 00000000000..902c4f50168 --- /dev/null +++ b/seed/go-model/grpc-proto/.mock/generators.yml @@ -0,0 +1,5 @@ +api: + - proto: + root: proto + target: proto/user/v1/user.proto + local-generation: true \ No newline at end of file diff --git a/seed/go-model/grpc-proto/.mock/proto/google/api/annotations.proto b/seed/go-model/grpc-proto/.mock/proto/google/api/annotations.proto new file mode 100644 index 00000000000..8ff42098404 --- /dev/null +++ b/seed/go-model/grpc-proto/.mock/proto/google/api/annotations.proto @@ -0,0 +1,31 @@ +// Copyright 2015 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/api/http.proto"; +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "AnnotationsProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // See `HttpRule`. + HttpRule http = 72295728; +} \ No newline at end of file diff --git a/seed/go-model/grpc-proto/.mock/proto/google/api/field_behavior.proto b/seed/go-model/grpc-proto/.mock/proto/google/api/field_behavior.proto new file mode 100644 index 00000000000..128799c558d --- /dev/null +++ b/seed/go-model/grpc-proto/.mock/proto/google/api/field_behavior.proto @@ -0,0 +1,104 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "FieldBehaviorProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.FieldOptions { + // A designation of a specific field behavior (required, output only, etc.) + // in protobuf messages. + // + // Examples: + // + // string name = 1 [(google.api.field_behavior) = REQUIRED]; + // State state = 1 [(google.api.field_behavior) = OUTPUT_ONLY]; + // google.protobuf.Duration ttl = 1 + // [(google.api.field_behavior) = INPUT_ONLY]; + // google.protobuf.Timestamp expire_time = 1 + // [(google.api.field_behavior) = OUTPUT_ONLY, + // (google.api.field_behavior) = IMMUTABLE]; + repeated google.api.FieldBehavior field_behavior = 1052; +} + +// An indicator of the behavior of a given field (for example, that a field +// is required in requests, or given as output but ignored as input). +// This **does not** change the behavior in protocol buffers itself; it only +// denotes the behavior and may affect how API tooling handles the field. +// +// Note: This enum **may** receive new values in the future. +enum FieldBehavior { + // Conventional default for enums. Do not use this. + FIELD_BEHAVIOR_UNSPECIFIED = 0; + + // Specifically denotes a field as optional. + // While all fields in protocol buffers are optional, this may be specified + // for emphasis if appropriate. + OPTIONAL = 1; + + // Denotes a field as required. + // This indicates that the field **must** be provided as part of the request, + // and failure to do so will cause an error (usually `INVALID_ARGUMENT`). + REQUIRED = 2; + + // Denotes a field as output only. + // This indicates that the field is provided in responses, but including the + // field in a request does nothing (the server *must* ignore it and + // *must not* throw an error as a result of the field's presence). + OUTPUT_ONLY = 3; + + // Denotes a field as input only. + // This indicates that the field is provided in requests, and the + // corresponding field is not included in output. + INPUT_ONLY = 4; + + // Denotes a field as immutable. + // This indicates that the field may be set once in a request to create a + // resource, but may not be changed thereafter. + IMMUTABLE = 5; + + // Denotes that a (repeated) field is an unordered list. + // This indicates that the service may provide the elements of the list + // in any arbitrary order, rather than the order the user originally + // provided. Additionally, the list's order may or may not be stable. + UNORDERED_LIST = 6; + + // Denotes that this field returns a non-empty default value if not set. + // This indicates that if the user provides the empty value in a request, + // a non-empty value will be returned. The user will not be aware of what + // non-empty value to expect. + NON_EMPTY_DEFAULT = 7; + + // Denotes that the field in a resource (a message annotated with + // google.api.resource) is used in the resource name to uniquely identify the + // resource. For AIP-compliant APIs, this should only be applied to the + // `name` field on the resource. + // + // This behavior should not be applied to references to other resources within + // the message. + // + // The identifier field of resources often have different field behavior + // depending on the request it is embedded in (e.g. for Create methods name + // is optional and unused, while for Update methods it is required). Instead + // of method-specific annotations, only `IDENTIFIER` is required. + IDENTIFIER = 8; +} \ No newline at end of file diff --git a/seed/go-model/grpc-proto/.mock/proto/google/api/http.proto b/seed/go-model/grpc-proto/.mock/proto/google/api/http.proto new file mode 100644 index 00000000000..c8392381eb9 --- /dev/null +++ b/seed/go-model/grpc-proto/.mock/proto/google/api/http.proto @@ -0,0 +1,379 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "HttpProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Defines the HTTP configuration for an API service. It contains a list of +// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method +// to one or more HTTP REST API methods. +message Http { + // A list of HTTP configuration rules that apply to individual API methods. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated HttpRule rules = 1; + + // When set to true, URL path parameters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // + // The default behavior is to not decode RFC 6570 reserved characters in multi + // segment matches. + bool fully_decode_reserved_expansion = 2; +} + +// # gRPC Transcoding +// +// gRPC Transcoding is a feature for mapping between a gRPC method and one or +// more HTTP REST endpoints. It allows developers to build a single API service +// that supports both gRPC APIs and REST APIs. Many systems, including [Google +// APIs](https://github.com/googleapis/googleapis), +// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC +// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), +// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature +// and use it for large scale production services. +// +// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies +// how different portions of the gRPC request message are mapped to the URL +// path, URL query parameters, and HTTP request body. It also controls how the +// gRPC response message is mapped to the HTTP response body. `HttpRule` is +// typically specified as an `google.api.http` annotation on the gRPC method. +// +// Each mapping specifies a URL path template and an HTTP method. The path +// template may refer to one or more fields in the gRPC request message, as long +// as each field is a non-repeated field with a primitive (non-message) type. +// The path template controls how fields of the request message are mapped to +// the URL path. +// +// Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/{name=messages/*}" +// }; +// } +// } +// message GetMessageRequest { +// string name = 1; // Mapped to URL path. +// } +// message Message { +// string text = 1; // The resource content. +// } +// +// This enables an HTTP REST to gRPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` +// +// Any fields in the request message which are not bound by the path template +// automatically become HTTP query parameters if there is no HTTP request body. +// For example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get:"/v1/messages/{message_id}" +// }; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // Mapped to URL path. +// int64 revision = 2; // Mapped to URL query parameter `revision`. +// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. +// } +// +// This enables a HTTP JSON to RPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | +// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: +// "foo"))` +// +// Note that fields which are mapped to URL query parameters must have a +// primitive type or a repeated primitive type or a non-repeated message type. +// In the case of a repeated type, the parameter can be repeated in the URL +// as `...?param=A¶m=B`. In the case of a message type, each field of the +// message is mapped to a separate parameter, such as +// `...?foo.a=A&foo.b=B&foo.c=C`. +// +// For HTTP methods that allow a request body, the `body` field +// specifies the mapping. Consider a REST update method on the +// message resource collection: +// +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } +// +// The following HTTP JSON to RPC mapping is enabled, where the +// representation of the JSON in the request body is determined by +// protos JSON encoding: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" message { text: "Hi!" })` +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. This enables the following alternative definition of +// the update method: +// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } +// +// +// The following HTTP JSON to RPC mapping is enabled: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" text: "Hi!")` +// +// Note that when using `*` in the body mapping, it is not possible to +// have HTTP parameters, as all fields not bound by the path end in +// the body. This makes this option more rarely used in practice when +// defining REST APIs. The common usage of `*` is in custom methods +// which don't use the URL at all for transferring data. +// +// It is possible to define multiple HTTP methods for one RPC by using +// the `additional_bindings` option. Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } +// +// This enables the following two alternative HTTP JSON to RPC mappings: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` +// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: +// "123456")` +// +// ## Rules for HTTP mapping +// +// 1. Leaf request fields (recursive expansion nested messages in the request +// message) are classified into three categories: +// - Fields referred by the path template. They are passed via the URL path. +// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They +// are passed via the HTTP +// request body. +// - All other fields are passed via the URL query parameters, and the +// parameter name is the field path in the request message. A repeated +// field can be represented as multiple query parameters under the same +// name. +// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL +// query parameter, all fields +// are passed via URL path and HTTP request body. +// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP +// request body, all +// fields are passed via URL path and URL query parameters. +// +// ### Path template syntax +// +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +// +// The syntax `*` matches a single URL path segment. The syntax `**` matches +// zero or more URL path segments, which must be the last part of the URL path +// except the `Verb`. +// +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable +// matches a single path segment, its template may be omitted, e.g. `{var}` +// is equivalent to `{var=*}`. +// +// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` +// contains any reserved character, such characters should be percent-encoded +// before the matching. +// +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path on the client +// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The +// server side does the reverse decoding. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{var}`. +// +// If a variable contains multiple path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path on the +// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. +// The server side does the reverse decoding, except "%2F" and "%2f" are left +// unchanged. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{+var}`. +// +// ## Using gRPC API Service Configuration +// +// gRPC API Service Configuration (service config) is a configuration language +// for configuring a gRPC service to become a user-facing product. The +// service config is simply the YAML representation of the `google.api.Service` +// proto message. +// +// As an alternative to annotating your proto file, you can configure gRPC +// transcoding in your service config YAML files. You do this by specifying a +// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same +// effect as the proto annotation. This can be particularly useful if you +// have a proto that is reused in multiple services. Note that any transcoding +// specified in the service config will override any matching transcoding +// configuration in the proto. +// +// Example: +// +// http: +// rules: +// # Selects a gRPC method and applies HttpRule to it. +// - selector: example.v1.Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} +// +// ## Special notes +// +// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the +// proto to JSON conversion must follow the [proto3 +// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). +// +// While the single segment variable follows the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String +// Expansion, the multi segment variable **does not** follow RFC 6570 Section +// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding +// for multi segment variables. +// +// The path variables **must not** refer to any repeated or mapped field, +// because client libraries are not capable of handling such variable expansion. +// +// The path variables **must not** capture the leading "/" character. The reason +// is that the most common use case "{var}" does not capture the leading "/" +// character. For consistency, all path variables must share the same behavior. +// +// Repeated message fields must not be mapped to URL query parameters, because +// no client library can support such complicated mapping. +// +// If an API needs to use a JSON array for request or response body, it can map +// the request or response body to a repeated field. However, some gRPC +// Transcoding implementations may not support this feature. +message HttpRule { + // Selects a method to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax + // details. + string selector = 1; + + // Determines the URL pattern is matched by this rules. This pattern can be + // used with any of the {get|put|post|delete|patch} methods. A custom method + // can be defined using the 'custom' field. + oneof pattern { + // Maps to HTTP GET. Used for listing and getting information about + // resources. + string get = 2; + + // Maps to HTTP PUT. Used for replacing a resource. + string put = 3; + + // Maps to HTTP POST. Used for creating a resource or performing an action. + string post = 4; + + // Maps to HTTP DELETE. Used for deleting a resource. + string delete = 5; + + // Maps to HTTP PATCH. Used for updating a resource. + string patch = 6; + + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. + CustomHttpPattern custom = 8; + } + + // The name of the request field whose value is mapped to the HTTP request + // body, or `*` for mapping all request fields not captured by the path + // pattern to the HTTP body, or omitted for not having any HTTP request body. + // + // NOTE: the referred field must be present at the top-level of the request + // message type. + string body = 7; + + // Optional. The name of the response field whose value is mapped to the HTTP + // response body. When omitted, the entire response message will be used + // as the HTTP response body. + // + // NOTE: The referred field must be present at the top-level of the response + // message type. + string response_body = 12; + + // Additional HTTP bindings for the selector. Nested bindings must + // not contain an `additional_bindings` field themselves (that is, + // the nesting may only be one level deep). + repeated HttpRule additional_bindings = 11; +} + +// A custom pattern is used for defining custom HTTP verb. +message CustomHttpPattern { + // The name of this custom HTTP verb. + string kind = 1; + + // The path matched by this custom verb. + string path = 2; +} \ No newline at end of file diff --git a/seed/go-model/grpc-proto/.mock/proto/user/v1/user.proto b/seed/go-model/grpc-proto/.mock/proto/user/v1/user.proto new file mode 100644 index 00000000000..a3ad0195bd4 --- /dev/null +++ b/seed/go-model/grpc-proto/.mock/proto/user/v1/user.proto @@ -0,0 +1,39 @@ +syntax = "proto3"; + +package user.v1; + +import "google/api/annotations.proto"; +import "google/api/field_behavior.proto"; +import "google/protobuf/struct.proto"; + +option go_package = "user/v1"; +option csharp_namespace = "User.V1"; + +message UserModel { + string username = 1; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateRequest { + string username = 1; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateResponse { + UserModel user = 1; +} + +service User { + rpc Create(CreateRequest) returns (CreateResponse) { + option (google.api.http) = { + post: "/users" + body: "*" + }; + } +} \ No newline at end of file diff --git a/seed/go-model/grpc-proto/core/extra_properties.go b/seed/go-model/grpc-proto/core/extra_properties.go new file mode 100644 index 00000000000..a6af3e12410 --- /dev/null +++ b/seed/go-model/grpc-proto/core/extra_properties.go @@ -0,0 +1,141 @@ +package core + +import ( + "bytes" + "encoding/json" + "fmt" + "reflect" + "strings" +) + +// MarshalJSONWithExtraProperty marshals the given value to JSON, including the extra property. +func MarshalJSONWithExtraProperty(marshaler interface{}, key string, value interface{}) ([]byte, error) { + return MarshalJSONWithExtraProperties(marshaler, map[string]interface{}{key: value}) +} + +// MarshalJSONWithExtraProperties marshals the given value to JSON, including any extra properties. +func MarshalJSONWithExtraProperties(marshaler interface{}, extraProperties map[string]interface{}) ([]byte, error) { + bytes, err := json.Marshal(marshaler) + if err != nil { + return nil, err + } + if len(extraProperties) == 0 { + return bytes, nil + } + keys, err := getKeys(marshaler) + if err != nil { + return nil, err + } + for _, key := range keys { + if _, ok := extraProperties[key]; ok { + return nil, fmt.Errorf("cannot add extra property %q because it is already defined on the type", key) + } + } + extraBytes, err := json.Marshal(extraProperties) + if err != nil { + return nil, err + } + if isEmptyJSON(bytes) { + if isEmptyJSON(extraBytes) { + return bytes, nil + } + return extraBytes, nil + } + result := bytes[:len(bytes)-1] + result = append(result, ',') + result = append(result, extraBytes[1:len(extraBytes)-1]...) + result = append(result, '}') + return result, nil +} + +// ExtractExtraProperties extracts any extra properties from the given value. +func ExtractExtraProperties(bytes []byte, value interface{}, exclude ...string) (map[string]interface{}, error) { + val := reflect.ValueOf(value) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return nil, fmt.Errorf("value must be non-nil to extract extra properties") + } + val = val.Elem() + } + if err := json.Unmarshal(bytes, &value); err != nil { + return nil, err + } + var extraProperties map[string]interface{} + if err := json.Unmarshal(bytes, &extraProperties); err != nil { + return nil, err + } + for i := 0; i < val.Type().NumField(); i++ { + key := jsonKey(val.Type().Field(i)) + if key == "" || key == "-" { + continue + } + delete(extraProperties, key) + } + for _, key := range exclude { + delete(extraProperties, key) + } + if len(extraProperties) == 0 { + return nil, nil + } + return extraProperties, nil +} + +// getKeys returns the keys associated with the given value. The value must be a +// a struct or a map with string keys. +func getKeys(value interface{}) ([]string, error) { + val := reflect.ValueOf(value) + if val.Kind() == reflect.Ptr { + val = val.Elem() + } + if !val.IsValid() { + return nil, nil + } + switch val.Kind() { + case reflect.Struct: + return getKeysForStructType(val.Type()), nil + case reflect.Map: + var keys []string + if val.Type().Key().Kind() != reflect.String { + return nil, fmt.Errorf("cannot extract keys from %T; only structs and maps with string keys are supported", value) + } + for _, key := range val.MapKeys() { + keys = append(keys, key.String()) + } + return keys, nil + default: + return nil, fmt.Errorf("cannot extract keys from %T; only structs and maps with string keys are supported", value) + } +} + +// getKeysForStructType returns all the keys associated with the given struct type, +// visiting embedded fields recursively. +func getKeysForStructType(structType reflect.Type) []string { + if structType.Kind() == reflect.Pointer { + structType = structType.Elem() + } + if structType.Kind() != reflect.Struct { + return nil + } + var keys []string + for i := 0; i < structType.NumField(); i++ { + field := structType.Field(i) + if field.Anonymous { + keys = append(keys, getKeysForStructType(field.Type)...) + continue + } + keys = append(keys, jsonKey(field)) + } + return keys +} + +// jsonKey returns the JSON key from the struct tag of the given field, +// excluding the omitempty flag (if any). +func jsonKey(field reflect.StructField) string { + return strings.TrimSuffix(field.Tag.Get("json"), ",omitempty") +} + +// isEmptyJSON returns true if the given data is empty, the empty JSON object, or +// an explicit null. +func isEmptyJSON(data []byte) bool { + return len(data) <= 2 || bytes.Equal(data, []byte("null")) +} diff --git a/seed/go-model/grpc-proto/core/extra_properties_test.go b/seed/go-model/grpc-proto/core/extra_properties_test.go new file mode 100644 index 00000000000..dc66fccd7f1 --- /dev/null +++ b/seed/go-model/grpc-proto/core/extra_properties_test.go @@ -0,0 +1,228 @@ +package core + +import ( + "encoding/json" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type testMarshaler struct { + Name string `json:"name"` + BirthDate time.Time `json:"birthDate"` + CreatedAt time.Time `json:"created_at"` +} + +func (t *testMarshaler) MarshalJSON() ([]byte, error) { + type embed testMarshaler + var marshaler = struct { + embed + BirthDate string `json:"birthDate"` + CreatedAt string `json:"created_at"` + }{ + embed: embed(*t), + BirthDate: t.BirthDate.Format("2006-01-02"), + CreatedAt: t.CreatedAt.Format(time.RFC3339), + } + return MarshalJSONWithExtraProperty(marshaler, "type", "test") +} + +func TestMarshalJSONWithExtraProperties(t *testing.T) { + tests := []struct { + desc string + giveMarshaler interface{} + giveExtraProperties map[string]interface{} + wantBytes []byte + wantError string + }{ + { + desc: "invalid type", + giveMarshaler: []string{"invalid"}, + giveExtraProperties: map[string]interface{}{"key": "overwrite"}, + wantError: `cannot extract keys from []string; only structs and maps with string keys are supported`, + }, + { + desc: "invalid key type", + giveMarshaler: map[int]interface{}{42: "value"}, + giveExtraProperties: map[string]interface{}{"key": "overwrite"}, + wantError: `cannot extract keys from map[int]interface {}; only structs and maps with string keys are supported`, + }, + { + desc: "invalid map overwrite", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{"key": "overwrite"}, + wantError: `cannot add extra property "key" because it is already defined on the type`, + }, + { + desc: "invalid struct overwrite", + giveMarshaler: new(testMarshaler), + giveExtraProperties: map[string]interface{}{"birthDate": "2000-01-01"}, + wantError: `cannot add extra property "birthDate" because it is already defined on the type`, + }, + { + desc: "invalid struct overwrite embedded type", + giveMarshaler: new(testMarshaler), + giveExtraProperties: map[string]interface{}{"name": "bob"}, + wantError: `cannot add extra property "name" because it is already defined on the type`, + }, + { + desc: "nil", + giveMarshaler: nil, + giveExtraProperties: nil, + wantBytes: []byte(`null`), + }, + { + desc: "empty", + giveMarshaler: map[string]interface{}{}, + giveExtraProperties: map[string]interface{}{}, + wantBytes: []byte(`{}`), + }, + { + desc: "no extra properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{}, + wantBytes: []byte(`{"key":"value"}`), + }, + { + desc: "only extra properties", + giveMarshaler: map[string]interface{}{}, + giveExtraProperties: map[string]interface{}{"key": "value"}, + wantBytes: []byte(`{"key":"value"}`), + }, + { + desc: "single extra property", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{"extra": "property"}, + wantBytes: []byte(`{"key":"value","extra":"property"}`), + }, + { + desc: "multiple extra properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{"one": 1, "two": 2}, + wantBytes: []byte(`{"key":"value","one":1,"two":2}`), + }, + { + desc: "nested properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{ + "user": map[string]interface{}{ + "age": 42, + "name": "alice", + }, + }, + wantBytes: []byte(`{"key":"value","user":{"age":42,"name":"alice"}}`), + }, + { + desc: "multiple nested properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{ + "metadata": map[string]interface{}{ + "ip": "127.0.0.1", + }, + "user": map[string]interface{}{ + "age": 42, + "name": "alice", + }, + }, + wantBytes: []byte(`{"key":"value","metadata":{"ip":"127.0.0.1"},"user":{"age":42,"name":"alice"}}`), + }, + { + desc: "custom marshaler", + giveMarshaler: &testMarshaler{ + Name: "alice", + BirthDate: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), + CreatedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), + }, + giveExtraProperties: map[string]interface{}{ + "extra": "property", + }, + wantBytes: []byte(`{"name":"alice","birthDate":"2000-01-01","created_at":"2024-01-01T00:00:00Z","type":"test","extra":"property"}`), + }, + } + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + bytes, err := MarshalJSONWithExtraProperties(tt.giveMarshaler, tt.giveExtraProperties) + if tt.wantError != "" { + require.EqualError(t, err, tt.wantError) + assert.Nil(t, tt.wantBytes) + return + } + require.NoError(t, err) + assert.Equal(t, tt.wantBytes, bytes) + + value := make(map[string]interface{}) + require.NoError(t, json.Unmarshal(bytes, &value)) + }) + } +} + +func TestExtractExtraProperties(t *testing.T) { + t.Run("none", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := &user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice"}`), value) + require.NoError(t, err) + assert.Nil(t, extraProperties) + }) + + t.Run("non-nil pointer", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := &user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + require.NoError(t, err) + assert.Equal(t, map[string]interface{}{"age": float64(42)}, extraProperties) + }) + + t.Run("nil pointer", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + var value *user + _, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + assert.EqualError(t, err, "value must be non-nil to extract extra properties") + }) + + t.Run("non-zero value", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + require.NoError(t, err) + assert.Equal(t, map[string]interface{}{"age": float64(42)}, extraProperties) + }) + + t.Run("zero value", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + var value user + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + require.NoError(t, err) + assert.Equal(t, map[string]interface{}{"age": float64(42)}, extraProperties) + }) + + t.Run("exclude", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := &user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value, "age") + require.NoError(t, err) + assert.Nil(t, extraProperties) + }) +} diff --git a/seed/go-model/grpc-proto/core/stringer.go b/seed/go-model/grpc-proto/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-model/grpc-proto/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-model/grpc-proto/core/time.go b/seed/go-model/grpc-proto/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-model/grpc-proto/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-model/grpc-proto/go.mod b/seed/go-model/grpc-proto/go.mod new file mode 100644 index 00000000000..d3f2562778e --- /dev/null +++ b/seed/go-model/grpc-proto/go.mod @@ -0,0 +1,8 @@ +module github.com/grpc-proto/fern + +go 1.13 + +require ( + github.com/stretchr/testify v1.7.0 + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/seed/go-model/grpc-proto/go.sum b/seed/go-model/grpc-proto/go.sum new file mode 100644 index 00000000000..fc3dd9e67e8 --- /dev/null +++ b/seed/go-model/grpc-proto/go.sum @@ -0,0 +1,12 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/seed/go-model/grpc-proto/snippet-templates.json b/seed/go-model/grpc-proto/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/grpc-proto/snippet.json b/seed/go-model/grpc-proto/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/grpc-proto/types.go b/seed/go-model/grpc-proto/types.go new file mode 100644 index 00000000000..8825dca1723 --- /dev/null +++ b/seed/go-model/grpc-proto/types.go @@ -0,0 +1,81 @@ +// This file was auto-generated by Fern from our API Definition. + +package api + +import ( + json "encoding/json" + fmt "fmt" + core "github.com/grpc-proto/fern/core" +) + +type CreateResponse struct { + User *UserModel `json:"user,omitempty" url:"user,omitempty"` + + extraProperties map[string]interface{} +} + +func (c *CreateResponse) GetExtraProperties() map[string]interface{} { + return c.extraProperties +} + +func (c *CreateResponse) UnmarshalJSON(data []byte) error { + type unmarshaler CreateResponse + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *c = CreateResponse(value) + + extraProperties, err := core.ExtractExtraProperties(data, *c) + if err != nil { + return err + } + c.extraProperties = extraProperties + + return nil +} + +func (c *CreateResponse) String() string { + if value, err := core.StringifyJSON(c); err == nil { + return value + } + return fmt.Sprintf("%#v", c) +} + +type UserModel struct { + Username *string `json:"username,omitempty" url:"username,omitempty"` + Email *string `json:"email,omitempty" url:"email,omitempty"` + Age *int `json:"age,omitempty" url:"age,omitempty"` + Weight *float64 `json:"weight,omitempty" url:"weight,omitempty"` + Metadata map[string]interface{} `json:"metadata,omitempty" url:"metadata,omitempty"` + + extraProperties map[string]interface{} +} + +func (u *UserModel) GetExtraProperties() map[string]interface{} { + return u.extraProperties +} + +func (u *UserModel) UnmarshalJSON(data []byte) error { + type unmarshaler UserModel + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *u = UserModel(value) + + extraProperties, err := core.ExtractExtraProperties(data, *u) + if err != nil { + return err + } + u.extraProperties = extraProperties + + return nil +} + +func (u *UserModel) String() string { + if value, err := core.StringifyJSON(u); err == nil { + return value + } + return fmt.Sprintf("%#v", u) +} diff --git a/seed/go-model/grpc-proto/user.go b/seed/go-model/grpc-proto/user.go new file mode 100644 index 00000000000..884eed6ba61 --- /dev/null +++ b/seed/go-model/grpc-proto/user.go @@ -0,0 +1,3 @@ +// This file was auto-generated by Fern from our API Definition. + +package api diff --git a/seed/go-model/grpc/.github/workflows/ci.yml b/seed/go-model/grpc/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-model/grpc/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-model/grpc/.mock/definition/api.yml b/seed/go-model/grpc/.mock/definition/api.yml new file mode 100644 index 00000000000..15a9f823724 --- /dev/null +++ b/seed/go-model/grpc/.mock/definition/api.yml @@ -0,0 +1,4 @@ +name: api + +error-discrimination: + strategy: status-code \ No newline at end of file diff --git a/seed/go-model/grpc/.mock/definition/user.yml b/seed/go-model/grpc/.mock/definition/user.yml new file mode 100644 index 00000000000..80223300afc --- /dev/null +++ b/seed/go-model/grpc/.mock/definition/user.yml @@ -0,0 +1,61 @@ +types: + Metadata: + type: map> + encoding: + proto: + type: google.protobuf.Struct + + MetadataValue: + discriminated: false + union: + - double + - string + - boolean + - list + encoding: + proto: + type: google.protobuf.Value + + User: + properties: + id: string + username: string + email: optional + age: optional + weight: optional + metadata: optional + + CreateUserResponse: + properties: + user: User + +service: + auth: false + base-path: / + transport: + grpc: + service-name: UserService + endpoints: + createUser: + method: POST + path: /users + request: + name: CreateUserRequest + body: + properties: + username: string + email: optional + age: optional + weight: optional + response: CreateUserResponse + + getUser: + method: GET + path: /users + request: + name: GetUserRequest + query-parameters: + username: optional + age: optional + weight: optional + response: User diff --git a/seed/go-model/grpc/.mock/fern.config.json b/seed/go-model/grpc/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/go-model/grpc/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/go-model/grpc/.mock/generators.yml b/seed/go-model/grpc/.mock/generators.yml new file mode 100644 index 00000000000..f62dfba5843 --- /dev/null +++ b/seed/go-model/grpc/.mock/generators.yml @@ -0,0 +1,4 @@ +api: + - proto: + root: proto + target: proto/user/v1/user.proto \ No newline at end of file diff --git a/seed/go-model/grpc/core/extra_properties.go b/seed/go-model/grpc/core/extra_properties.go new file mode 100644 index 00000000000..a6af3e12410 --- /dev/null +++ b/seed/go-model/grpc/core/extra_properties.go @@ -0,0 +1,141 @@ +package core + +import ( + "bytes" + "encoding/json" + "fmt" + "reflect" + "strings" +) + +// MarshalJSONWithExtraProperty marshals the given value to JSON, including the extra property. +func MarshalJSONWithExtraProperty(marshaler interface{}, key string, value interface{}) ([]byte, error) { + return MarshalJSONWithExtraProperties(marshaler, map[string]interface{}{key: value}) +} + +// MarshalJSONWithExtraProperties marshals the given value to JSON, including any extra properties. +func MarshalJSONWithExtraProperties(marshaler interface{}, extraProperties map[string]interface{}) ([]byte, error) { + bytes, err := json.Marshal(marshaler) + if err != nil { + return nil, err + } + if len(extraProperties) == 0 { + return bytes, nil + } + keys, err := getKeys(marshaler) + if err != nil { + return nil, err + } + for _, key := range keys { + if _, ok := extraProperties[key]; ok { + return nil, fmt.Errorf("cannot add extra property %q because it is already defined on the type", key) + } + } + extraBytes, err := json.Marshal(extraProperties) + if err != nil { + return nil, err + } + if isEmptyJSON(bytes) { + if isEmptyJSON(extraBytes) { + return bytes, nil + } + return extraBytes, nil + } + result := bytes[:len(bytes)-1] + result = append(result, ',') + result = append(result, extraBytes[1:len(extraBytes)-1]...) + result = append(result, '}') + return result, nil +} + +// ExtractExtraProperties extracts any extra properties from the given value. +func ExtractExtraProperties(bytes []byte, value interface{}, exclude ...string) (map[string]interface{}, error) { + val := reflect.ValueOf(value) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return nil, fmt.Errorf("value must be non-nil to extract extra properties") + } + val = val.Elem() + } + if err := json.Unmarshal(bytes, &value); err != nil { + return nil, err + } + var extraProperties map[string]interface{} + if err := json.Unmarshal(bytes, &extraProperties); err != nil { + return nil, err + } + for i := 0; i < val.Type().NumField(); i++ { + key := jsonKey(val.Type().Field(i)) + if key == "" || key == "-" { + continue + } + delete(extraProperties, key) + } + for _, key := range exclude { + delete(extraProperties, key) + } + if len(extraProperties) == 0 { + return nil, nil + } + return extraProperties, nil +} + +// getKeys returns the keys associated with the given value. The value must be a +// a struct or a map with string keys. +func getKeys(value interface{}) ([]string, error) { + val := reflect.ValueOf(value) + if val.Kind() == reflect.Ptr { + val = val.Elem() + } + if !val.IsValid() { + return nil, nil + } + switch val.Kind() { + case reflect.Struct: + return getKeysForStructType(val.Type()), nil + case reflect.Map: + var keys []string + if val.Type().Key().Kind() != reflect.String { + return nil, fmt.Errorf("cannot extract keys from %T; only structs and maps with string keys are supported", value) + } + for _, key := range val.MapKeys() { + keys = append(keys, key.String()) + } + return keys, nil + default: + return nil, fmt.Errorf("cannot extract keys from %T; only structs and maps with string keys are supported", value) + } +} + +// getKeysForStructType returns all the keys associated with the given struct type, +// visiting embedded fields recursively. +func getKeysForStructType(structType reflect.Type) []string { + if structType.Kind() == reflect.Pointer { + structType = structType.Elem() + } + if structType.Kind() != reflect.Struct { + return nil + } + var keys []string + for i := 0; i < structType.NumField(); i++ { + field := structType.Field(i) + if field.Anonymous { + keys = append(keys, getKeysForStructType(field.Type)...) + continue + } + keys = append(keys, jsonKey(field)) + } + return keys +} + +// jsonKey returns the JSON key from the struct tag of the given field, +// excluding the omitempty flag (if any). +func jsonKey(field reflect.StructField) string { + return strings.TrimSuffix(field.Tag.Get("json"), ",omitempty") +} + +// isEmptyJSON returns true if the given data is empty, the empty JSON object, or +// an explicit null. +func isEmptyJSON(data []byte) bool { + return len(data) <= 2 || bytes.Equal(data, []byte("null")) +} diff --git a/seed/go-model/grpc/core/extra_properties_test.go b/seed/go-model/grpc/core/extra_properties_test.go new file mode 100644 index 00000000000..dc66fccd7f1 --- /dev/null +++ b/seed/go-model/grpc/core/extra_properties_test.go @@ -0,0 +1,228 @@ +package core + +import ( + "encoding/json" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type testMarshaler struct { + Name string `json:"name"` + BirthDate time.Time `json:"birthDate"` + CreatedAt time.Time `json:"created_at"` +} + +func (t *testMarshaler) MarshalJSON() ([]byte, error) { + type embed testMarshaler + var marshaler = struct { + embed + BirthDate string `json:"birthDate"` + CreatedAt string `json:"created_at"` + }{ + embed: embed(*t), + BirthDate: t.BirthDate.Format("2006-01-02"), + CreatedAt: t.CreatedAt.Format(time.RFC3339), + } + return MarshalJSONWithExtraProperty(marshaler, "type", "test") +} + +func TestMarshalJSONWithExtraProperties(t *testing.T) { + tests := []struct { + desc string + giveMarshaler interface{} + giveExtraProperties map[string]interface{} + wantBytes []byte + wantError string + }{ + { + desc: "invalid type", + giveMarshaler: []string{"invalid"}, + giveExtraProperties: map[string]interface{}{"key": "overwrite"}, + wantError: `cannot extract keys from []string; only structs and maps with string keys are supported`, + }, + { + desc: "invalid key type", + giveMarshaler: map[int]interface{}{42: "value"}, + giveExtraProperties: map[string]interface{}{"key": "overwrite"}, + wantError: `cannot extract keys from map[int]interface {}; only structs and maps with string keys are supported`, + }, + { + desc: "invalid map overwrite", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{"key": "overwrite"}, + wantError: `cannot add extra property "key" because it is already defined on the type`, + }, + { + desc: "invalid struct overwrite", + giveMarshaler: new(testMarshaler), + giveExtraProperties: map[string]interface{}{"birthDate": "2000-01-01"}, + wantError: `cannot add extra property "birthDate" because it is already defined on the type`, + }, + { + desc: "invalid struct overwrite embedded type", + giveMarshaler: new(testMarshaler), + giveExtraProperties: map[string]interface{}{"name": "bob"}, + wantError: `cannot add extra property "name" because it is already defined on the type`, + }, + { + desc: "nil", + giveMarshaler: nil, + giveExtraProperties: nil, + wantBytes: []byte(`null`), + }, + { + desc: "empty", + giveMarshaler: map[string]interface{}{}, + giveExtraProperties: map[string]interface{}{}, + wantBytes: []byte(`{}`), + }, + { + desc: "no extra properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{}, + wantBytes: []byte(`{"key":"value"}`), + }, + { + desc: "only extra properties", + giveMarshaler: map[string]interface{}{}, + giveExtraProperties: map[string]interface{}{"key": "value"}, + wantBytes: []byte(`{"key":"value"}`), + }, + { + desc: "single extra property", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{"extra": "property"}, + wantBytes: []byte(`{"key":"value","extra":"property"}`), + }, + { + desc: "multiple extra properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{"one": 1, "two": 2}, + wantBytes: []byte(`{"key":"value","one":1,"two":2}`), + }, + { + desc: "nested properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{ + "user": map[string]interface{}{ + "age": 42, + "name": "alice", + }, + }, + wantBytes: []byte(`{"key":"value","user":{"age":42,"name":"alice"}}`), + }, + { + desc: "multiple nested properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{ + "metadata": map[string]interface{}{ + "ip": "127.0.0.1", + }, + "user": map[string]interface{}{ + "age": 42, + "name": "alice", + }, + }, + wantBytes: []byte(`{"key":"value","metadata":{"ip":"127.0.0.1"},"user":{"age":42,"name":"alice"}}`), + }, + { + desc: "custom marshaler", + giveMarshaler: &testMarshaler{ + Name: "alice", + BirthDate: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), + CreatedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), + }, + giveExtraProperties: map[string]interface{}{ + "extra": "property", + }, + wantBytes: []byte(`{"name":"alice","birthDate":"2000-01-01","created_at":"2024-01-01T00:00:00Z","type":"test","extra":"property"}`), + }, + } + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + bytes, err := MarshalJSONWithExtraProperties(tt.giveMarshaler, tt.giveExtraProperties) + if tt.wantError != "" { + require.EqualError(t, err, tt.wantError) + assert.Nil(t, tt.wantBytes) + return + } + require.NoError(t, err) + assert.Equal(t, tt.wantBytes, bytes) + + value := make(map[string]interface{}) + require.NoError(t, json.Unmarshal(bytes, &value)) + }) + } +} + +func TestExtractExtraProperties(t *testing.T) { + t.Run("none", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := &user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice"}`), value) + require.NoError(t, err) + assert.Nil(t, extraProperties) + }) + + t.Run("non-nil pointer", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := &user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + require.NoError(t, err) + assert.Equal(t, map[string]interface{}{"age": float64(42)}, extraProperties) + }) + + t.Run("nil pointer", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + var value *user + _, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + assert.EqualError(t, err, "value must be non-nil to extract extra properties") + }) + + t.Run("non-zero value", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + require.NoError(t, err) + assert.Equal(t, map[string]interface{}{"age": float64(42)}, extraProperties) + }) + + t.Run("zero value", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + var value user + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + require.NoError(t, err) + assert.Equal(t, map[string]interface{}{"age": float64(42)}, extraProperties) + }) + + t.Run("exclude", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := &user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value, "age") + require.NoError(t, err) + assert.Nil(t, extraProperties) + }) +} diff --git a/seed/go-model/grpc/core/stringer.go b/seed/go-model/grpc/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-model/grpc/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-model/grpc/core/time.go b/seed/go-model/grpc/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-model/grpc/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-model/grpc/go.mod b/seed/go-model/grpc/go.mod new file mode 100644 index 00000000000..d02629e859c --- /dev/null +++ b/seed/go-model/grpc/go.mod @@ -0,0 +1,8 @@ +module github.com/grpc/fern + +go 1.13 + +require ( + github.com/stretchr/testify v1.7.0 + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/seed/go-model/grpc/go.sum b/seed/go-model/grpc/go.sum new file mode 100644 index 00000000000..fc3dd9e67e8 --- /dev/null +++ b/seed/go-model/grpc/go.sum @@ -0,0 +1,12 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/seed/go-model/grpc/snippet-templates.json b/seed/go-model/grpc/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/grpc/snippet.json b/seed/go-model/grpc/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-model/grpc/types.go b/seed/go-model/grpc/types.go new file mode 100644 index 00000000000..52c62abb64a --- /dev/null +++ b/seed/go-model/grpc/types.go @@ -0,0 +1,96 @@ +// This file was auto-generated by Fern from our API Definition. + +package api + +import ( + json "encoding/json" + fmt "fmt" +) + +type Metadata = map[string]*MetadataValue + +type MetadataValue struct { + Double float64 + String string + Boolean bool + MetadataValueList []*MetadataValue +} + +func NewMetadataValueFromDouble(value float64) *MetadataValue { + return &MetadataValue{Double: value} +} + +func NewMetadataValueFromString(value string) *MetadataValue { + return &MetadataValue{String: value} +} + +func NewMetadataValueFromBoolean(value bool) *MetadataValue { + return &MetadataValue{Boolean: value} +} + +func NewMetadataValueFromMetadataValueList(value []*MetadataValue) *MetadataValue { + return &MetadataValue{MetadataValueList: value} +} + +func (m *MetadataValue) UnmarshalJSON(data []byte) error { + var valueDouble float64 + if err := json.Unmarshal(data, &valueDouble); err == nil { + m.Double = valueDouble + return nil + } + var valueString string + if err := json.Unmarshal(data, &valueString); err == nil { + m.String = valueString + return nil + } + var valueBoolean bool + if err := json.Unmarshal(data, &valueBoolean); err == nil { + m.Boolean = valueBoolean + return nil + } + var valueMetadataValueList []*MetadataValue + if err := json.Unmarshal(data, &valueMetadataValueList); err == nil { + m.MetadataValueList = valueMetadataValueList + return nil + } + return fmt.Errorf("%s cannot be deserialized as a %T", data, m) +} + +func (m MetadataValue) MarshalJSON() ([]byte, error) { + if m.Double != 0 { + return json.Marshal(m.Double) + } + if m.String != "" { + return json.Marshal(m.String) + } + if m.Boolean != false { + return json.Marshal(m.Boolean) + } + if m.MetadataValueList != nil { + return json.Marshal(m.MetadataValueList) + } + return nil, fmt.Errorf("type %T does not include a non-empty union type", m) +} + +type MetadataValueVisitor interface { + VisitDouble(float64) error + VisitString(string) error + VisitBoolean(bool) error + VisitMetadataValueList([]*MetadataValue) error +} + +func (m *MetadataValue) Accept(visitor MetadataValueVisitor) error { + if m.Double != 0 { + return visitor.VisitDouble(m.Double) + } + if m.String != "" { + return visitor.VisitString(m.String) + } + if m.Boolean != false { + return visitor.VisitBoolean(m.Boolean) + } + if m.MetadataValueList != nil { + return visitor.VisitMetadataValueList(m.MetadataValueList) + } + return fmt.Errorf("type %T does not include a non-empty union type", m) +} diff --git a/seed/go-model/grpc/user.go b/seed/go-model/grpc/user.go new file mode 100644 index 00000000000..05516ff38e6 --- /dev/null +++ b/seed/go-model/grpc/user.go @@ -0,0 +1,82 @@ +// This file was auto-generated by Fern from our API Definition. + +package api + +import ( + json "encoding/json" + fmt "fmt" + core "github.com/grpc/fern/core" +) + +type CreateUserResponse struct { + User *User `json:"user,omitempty" url:"user,omitempty"` + + extraProperties map[string]interface{} +} + +func (c *CreateUserResponse) GetExtraProperties() map[string]interface{} { + return c.extraProperties +} + +func (c *CreateUserResponse) UnmarshalJSON(data []byte) error { + type unmarshaler CreateUserResponse + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *c = CreateUserResponse(value) + + extraProperties, err := core.ExtractExtraProperties(data, *c) + if err != nil { + return err + } + c.extraProperties = extraProperties + + return nil +} + +func (c *CreateUserResponse) String() string { + if value, err := core.StringifyJSON(c); err == nil { + return value + } + return fmt.Sprintf("%#v", c) +} + +type User struct { + Id string `json:"id" url:"id"` + Username string `json:"username" url:"username"` + Email *string `json:"email,omitempty" url:"email,omitempty"` + Age *int `json:"age,omitempty" url:"age,omitempty"` + Weight *float64 `json:"weight,omitempty" url:"weight,omitempty"` + Metadata *Metadata `json:"metadata,omitempty" url:"metadata,omitempty"` + + extraProperties map[string]interface{} +} + +func (u *User) GetExtraProperties() map[string]interface{} { + return u.extraProperties +} + +func (u *User) UnmarshalJSON(data []byte) error { + type unmarshaler User + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *u = User(value) + + extraProperties, err := core.ExtractExtraProperties(data, *u) + if err != nil { + return err + } + u.extraProperties = extraProperties + + return nil +} + +func (u *User) String() string { + if value, err := core.StringifyJSON(u); err == nil { + return value + } + return fmt.Sprintf("%#v", u) +} diff --git a/seed/go-sdk/examples/always-send-required-properties/.github/workflows/ci.yml b/seed/go-sdk/examples/always-send-required-properties/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-sdk/examples/always-send-required-properties/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-sdk/examples/.mock/definition/__package__.yml b/seed/go-sdk/examples/always-send-required-properties/.mock/definition/__package__.yml similarity index 100% rename from seed/go-sdk/examples/.mock/definition/__package__.yml rename to seed/go-sdk/examples/always-send-required-properties/.mock/definition/__package__.yml diff --git a/seed/go-sdk/examples/.mock/definition/api.yml b/seed/go-sdk/examples/always-send-required-properties/.mock/definition/api.yml similarity index 100% rename from seed/go-sdk/examples/.mock/definition/api.yml rename to seed/go-sdk/examples/always-send-required-properties/.mock/definition/api.yml diff --git a/seed/go-sdk/examples/.mock/definition/commons/types.yml b/seed/go-sdk/examples/always-send-required-properties/.mock/definition/commons/types.yml similarity index 100% rename from seed/go-sdk/examples/.mock/definition/commons/types.yml rename to seed/go-sdk/examples/always-send-required-properties/.mock/definition/commons/types.yml diff --git a/seed/go-sdk/examples/.mock/definition/file/notification/service.yml b/seed/go-sdk/examples/always-send-required-properties/.mock/definition/file/notification/service.yml similarity index 100% rename from seed/go-sdk/examples/.mock/definition/file/notification/service.yml rename to seed/go-sdk/examples/always-send-required-properties/.mock/definition/file/notification/service.yml diff --git a/seed/go-sdk/examples/.mock/definition/file/service.yml b/seed/go-sdk/examples/always-send-required-properties/.mock/definition/file/service.yml similarity index 100% rename from seed/go-sdk/examples/.mock/definition/file/service.yml rename to seed/go-sdk/examples/always-send-required-properties/.mock/definition/file/service.yml diff --git a/seed/go-sdk/examples/.mock/definition/health/service.yml b/seed/go-sdk/examples/always-send-required-properties/.mock/definition/health/service.yml similarity index 100% rename from seed/go-sdk/examples/.mock/definition/health/service.yml rename to seed/go-sdk/examples/always-send-required-properties/.mock/definition/health/service.yml diff --git a/seed/go-sdk/examples/.mock/definition/service.yml b/seed/go-sdk/examples/always-send-required-properties/.mock/definition/service.yml similarity index 100% rename from seed/go-sdk/examples/.mock/definition/service.yml rename to seed/go-sdk/examples/always-send-required-properties/.mock/definition/service.yml diff --git a/seed/go-sdk/examples/.mock/definition/types.yml b/seed/go-sdk/examples/always-send-required-properties/.mock/definition/types.yml similarity index 100% rename from seed/go-sdk/examples/.mock/definition/types.yml rename to seed/go-sdk/examples/always-send-required-properties/.mock/definition/types.yml diff --git a/seed/go-sdk/examples/always-send-required-properties/.mock/fern.config.json b/seed/go-sdk/examples/always-send-required-properties/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/go-sdk/examples/always-send-required-properties/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/go-sdk/examples/.mock/generators.yml b/seed/go-sdk/examples/always-send-required-properties/.mock/generators.yml similarity index 100% rename from seed/go-sdk/examples/.mock/generators.yml rename to seed/go-sdk/examples/always-send-required-properties/.mock/generators.yml diff --git a/seed/go-sdk/examples/client/client.go b/seed/go-sdk/examples/always-send-required-properties/client/client.go similarity index 100% rename from seed/go-sdk/examples/client/client.go rename to seed/go-sdk/examples/always-send-required-properties/client/client.go diff --git a/seed/go-sdk/examples/client/client_test.go b/seed/go-sdk/examples/always-send-required-properties/client/client_test.go similarity index 100% rename from seed/go-sdk/examples/client/client_test.go rename to seed/go-sdk/examples/always-send-required-properties/client/client_test.go diff --git a/seed/go-sdk/examples/commons/types.go b/seed/go-sdk/examples/always-send-required-properties/commons/types.go similarity index 100% rename from seed/go-sdk/examples/commons/types.go rename to seed/go-sdk/examples/always-send-required-properties/commons/types.go diff --git a/seed/go-sdk/examples/core/core.go b/seed/go-sdk/examples/always-send-required-properties/core/core.go similarity index 100% rename from seed/go-sdk/examples/core/core.go rename to seed/go-sdk/examples/always-send-required-properties/core/core.go diff --git a/seed/go-sdk/examples/core/core_test.go b/seed/go-sdk/examples/always-send-required-properties/core/core_test.go similarity index 100% rename from seed/go-sdk/examples/core/core_test.go rename to seed/go-sdk/examples/always-send-required-properties/core/core_test.go diff --git a/seed/go-sdk/examples/always-send-required-properties/core/extra_properties.go b/seed/go-sdk/examples/always-send-required-properties/core/extra_properties.go new file mode 100644 index 00000000000..a6af3e12410 --- /dev/null +++ b/seed/go-sdk/examples/always-send-required-properties/core/extra_properties.go @@ -0,0 +1,141 @@ +package core + +import ( + "bytes" + "encoding/json" + "fmt" + "reflect" + "strings" +) + +// MarshalJSONWithExtraProperty marshals the given value to JSON, including the extra property. +func MarshalJSONWithExtraProperty(marshaler interface{}, key string, value interface{}) ([]byte, error) { + return MarshalJSONWithExtraProperties(marshaler, map[string]interface{}{key: value}) +} + +// MarshalJSONWithExtraProperties marshals the given value to JSON, including any extra properties. +func MarshalJSONWithExtraProperties(marshaler interface{}, extraProperties map[string]interface{}) ([]byte, error) { + bytes, err := json.Marshal(marshaler) + if err != nil { + return nil, err + } + if len(extraProperties) == 0 { + return bytes, nil + } + keys, err := getKeys(marshaler) + if err != nil { + return nil, err + } + for _, key := range keys { + if _, ok := extraProperties[key]; ok { + return nil, fmt.Errorf("cannot add extra property %q because it is already defined on the type", key) + } + } + extraBytes, err := json.Marshal(extraProperties) + if err != nil { + return nil, err + } + if isEmptyJSON(bytes) { + if isEmptyJSON(extraBytes) { + return bytes, nil + } + return extraBytes, nil + } + result := bytes[:len(bytes)-1] + result = append(result, ',') + result = append(result, extraBytes[1:len(extraBytes)-1]...) + result = append(result, '}') + return result, nil +} + +// ExtractExtraProperties extracts any extra properties from the given value. +func ExtractExtraProperties(bytes []byte, value interface{}, exclude ...string) (map[string]interface{}, error) { + val := reflect.ValueOf(value) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return nil, fmt.Errorf("value must be non-nil to extract extra properties") + } + val = val.Elem() + } + if err := json.Unmarshal(bytes, &value); err != nil { + return nil, err + } + var extraProperties map[string]interface{} + if err := json.Unmarshal(bytes, &extraProperties); err != nil { + return nil, err + } + for i := 0; i < val.Type().NumField(); i++ { + key := jsonKey(val.Type().Field(i)) + if key == "" || key == "-" { + continue + } + delete(extraProperties, key) + } + for _, key := range exclude { + delete(extraProperties, key) + } + if len(extraProperties) == 0 { + return nil, nil + } + return extraProperties, nil +} + +// getKeys returns the keys associated with the given value. The value must be a +// a struct or a map with string keys. +func getKeys(value interface{}) ([]string, error) { + val := reflect.ValueOf(value) + if val.Kind() == reflect.Ptr { + val = val.Elem() + } + if !val.IsValid() { + return nil, nil + } + switch val.Kind() { + case reflect.Struct: + return getKeysForStructType(val.Type()), nil + case reflect.Map: + var keys []string + if val.Type().Key().Kind() != reflect.String { + return nil, fmt.Errorf("cannot extract keys from %T; only structs and maps with string keys are supported", value) + } + for _, key := range val.MapKeys() { + keys = append(keys, key.String()) + } + return keys, nil + default: + return nil, fmt.Errorf("cannot extract keys from %T; only structs and maps with string keys are supported", value) + } +} + +// getKeysForStructType returns all the keys associated with the given struct type, +// visiting embedded fields recursively. +func getKeysForStructType(structType reflect.Type) []string { + if structType.Kind() == reflect.Pointer { + structType = structType.Elem() + } + if structType.Kind() != reflect.Struct { + return nil + } + var keys []string + for i := 0; i < structType.NumField(); i++ { + field := structType.Field(i) + if field.Anonymous { + keys = append(keys, getKeysForStructType(field.Type)...) + continue + } + keys = append(keys, jsonKey(field)) + } + return keys +} + +// jsonKey returns the JSON key from the struct tag of the given field, +// excluding the omitempty flag (if any). +func jsonKey(field reflect.StructField) string { + return strings.TrimSuffix(field.Tag.Get("json"), ",omitempty") +} + +// isEmptyJSON returns true if the given data is empty, the empty JSON object, or +// an explicit null. +func isEmptyJSON(data []byte) bool { + return len(data) <= 2 || bytes.Equal(data, []byte("null")) +} diff --git a/seed/go-sdk/examples/always-send-required-properties/core/extra_properties_test.go b/seed/go-sdk/examples/always-send-required-properties/core/extra_properties_test.go new file mode 100644 index 00000000000..dc66fccd7f1 --- /dev/null +++ b/seed/go-sdk/examples/always-send-required-properties/core/extra_properties_test.go @@ -0,0 +1,228 @@ +package core + +import ( + "encoding/json" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type testMarshaler struct { + Name string `json:"name"` + BirthDate time.Time `json:"birthDate"` + CreatedAt time.Time `json:"created_at"` +} + +func (t *testMarshaler) MarshalJSON() ([]byte, error) { + type embed testMarshaler + var marshaler = struct { + embed + BirthDate string `json:"birthDate"` + CreatedAt string `json:"created_at"` + }{ + embed: embed(*t), + BirthDate: t.BirthDate.Format("2006-01-02"), + CreatedAt: t.CreatedAt.Format(time.RFC3339), + } + return MarshalJSONWithExtraProperty(marshaler, "type", "test") +} + +func TestMarshalJSONWithExtraProperties(t *testing.T) { + tests := []struct { + desc string + giveMarshaler interface{} + giveExtraProperties map[string]interface{} + wantBytes []byte + wantError string + }{ + { + desc: "invalid type", + giveMarshaler: []string{"invalid"}, + giveExtraProperties: map[string]interface{}{"key": "overwrite"}, + wantError: `cannot extract keys from []string; only structs and maps with string keys are supported`, + }, + { + desc: "invalid key type", + giveMarshaler: map[int]interface{}{42: "value"}, + giveExtraProperties: map[string]interface{}{"key": "overwrite"}, + wantError: `cannot extract keys from map[int]interface {}; only structs and maps with string keys are supported`, + }, + { + desc: "invalid map overwrite", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{"key": "overwrite"}, + wantError: `cannot add extra property "key" because it is already defined on the type`, + }, + { + desc: "invalid struct overwrite", + giveMarshaler: new(testMarshaler), + giveExtraProperties: map[string]interface{}{"birthDate": "2000-01-01"}, + wantError: `cannot add extra property "birthDate" because it is already defined on the type`, + }, + { + desc: "invalid struct overwrite embedded type", + giveMarshaler: new(testMarshaler), + giveExtraProperties: map[string]interface{}{"name": "bob"}, + wantError: `cannot add extra property "name" because it is already defined on the type`, + }, + { + desc: "nil", + giveMarshaler: nil, + giveExtraProperties: nil, + wantBytes: []byte(`null`), + }, + { + desc: "empty", + giveMarshaler: map[string]interface{}{}, + giveExtraProperties: map[string]interface{}{}, + wantBytes: []byte(`{}`), + }, + { + desc: "no extra properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{}, + wantBytes: []byte(`{"key":"value"}`), + }, + { + desc: "only extra properties", + giveMarshaler: map[string]interface{}{}, + giveExtraProperties: map[string]interface{}{"key": "value"}, + wantBytes: []byte(`{"key":"value"}`), + }, + { + desc: "single extra property", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{"extra": "property"}, + wantBytes: []byte(`{"key":"value","extra":"property"}`), + }, + { + desc: "multiple extra properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{"one": 1, "two": 2}, + wantBytes: []byte(`{"key":"value","one":1,"two":2}`), + }, + { + desc: "nested properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{ + "user": map[string]interface{}{ + "age": 42, + "name": "alice", + }, + }, + wantBytes: []byte(`{"key":"value","user":{"age":42,"name":"alice"}}`), + }, + { + desc: "multiple nested properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{ + "metadata": map[string]interface{}{ + "ip": "127.0.0.1", + }, + "user": map[string]interface{}{ + "age": 42, + "name": "alice", + }, + }, + wantBytes: []byte(`{"key":"value","metadata":{"ip":"127.0.0.1"},"user":{"age":42,"name":"alice"}}`), + }, + { + desc: "custom marshaler", + giveMarshaler: &testMarshaler{ + Name: "alice", + BirthDate: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), + CreatedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), + }, + giveExtraProperties: map[string]interface{}{ + "extra": "property", + }, + wantBytes: []byte(`{"name":"alice","birthDate":"2000-01-01","created_at":"2024-01-01T00:00:00Z","type":"test","extra":"property"}`), + }, + } + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + bytes, err := MarshalJSONWithExtraProperties(tt.giveMarshaler, tt.giveExtraProperties) + if tt.wantError != "" { + require.EqualError(t, err, tt.wantError) + assert.Nil(t, tt.wantBytes) + return + } + require.NoError(t, err) + assert.Equal(t, tt.wantBytes, bytes) + + value := make(map[string]interface{}) + require.NoError(t, json.Unmarshal(bytes, &value)) + }) + } +} + +func TestExtractExtraProperties(t *testing.T) { + t.Run("none", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := &user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice"}`), value) + require.NoError(t, err) + assert.Nil(t, extraProperties) + }) + + t.Run("non-nil pointer", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := &user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + require.NoError(t, err) + assert.Equal(t, map[string]interface{}{"age": float64(42)}, extraProperties) + }) + + t.Run("nil pointer", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + var value *user + _, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + assert.EqualError(t, err, "value must be non-nil to extract extra properties") + }) + + t.Run("non-zero value", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + require.NoError(t, err) + assert.Equal(t, map[string]interface{}{"age": float64(42)}, extraProperties) + }) + + t.Run("zero value", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + var value user + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + require.NoError(t, err) + assert.Equal(t, map[string]interface{}{"age": float64(42)}, extraProperties) + }) + + t.Run("exclude", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := &user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value, "age") + require.NoError(t, err) + assert.Nil(t, extraProperties) + }) +} diff --git a/seed/go-sdk/examples/core/query.go b/seed/go-sdk/examples/always-send-required-properties/core/query.go similarity index 100% rename from seed/go-sdk/examples/core/query.go rename to seed/go-sdk/examples/always-send-required-properties/core/query.go diff --git a/seed/go-sdk/examples/core/query_test.go b/seed/go-sdk/examples/always-send-required-properties/core/query_test.go similarity index 100% rename from seed/go-sdk/examples/core/query_test.go rename to seed/go-sdk/examples/always-send-required-properties/core/query_test.go diff --git a/seed/go-sdk/examples/core/request_option.go b/seed/go-sdk/examples/always-send-required-properties/core/request_option.go similarity index 100% rename from seed/go-sdk/examples/core/request_option.go rename to seed/go-sdk/examples/always-send-required-properties/core/request_option.go diff --git a/seed/go-sdk/examples/core/retrier.go b/seed/go-sdk/examples/always-send-required-properties/core/retrier.go similarity index 100% rename from seed/go-sdk/examples/core/retrier.go rename to seed/go-sdk/examples/always-send-required-properties/core/retrier.go diff --git a/seed/go-sdk/examples/always-send-required-properties/core/stringer.go b/seed/go-sdk/examples/always-send-required-properties/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-sdk/examples/always-send-required-properties/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-sdk/examples/always-send-required-properties/core/time.go b/seed/go-sdk/examples/always-send-required-properties/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-sdk/examples/always-send-required-properties/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-sdk/examples/environments.go b/seed/go-sdk/examples/always-send-required-properties/environments.go similarity index 100% rename from seed/go-sdk/examples/environments.go rename to seed/go-sdk/examples/always-send-required-properties/environments.go diff --git a/seed/go-sdk/examples/errors.go b/seed/go-sdk/examples/always-send-required-properties/errors.go similarity index 100% rename from seed/go-sdk/examples/errors.go rename to seed/go-sdk/examples/always-send-required-properties/errors.go diff --git a/seed/go-sdk/examples/file/client/client.go b/seed/go-sdk/examples/always-send-required-properties/file/client/client.go similarity index 100% rename from seed/go-sdk/examples/file/client/client.go rename to seed/go-sdk/examples/always-send-required-properties/file/client/client.go diff --git a/seed/go-sdk/examples/file/notification/client/client.go b/seed/go-sdk/examples/always-send-required-properties/file/notification/client/client.go similarity index 100% rename from seed/go-sdk/examples/file/notification/client/client.go rename to seed/go-sdk/examples/always-send-required-properties/file/notification/client/client.go diff --git a/seed/go-sdk/examples/file/notification/service/client.go b/seed/go-sdk/examples/always-send-required-properties/file/notification/service/client.go similarity index 100% rename from seed/go-sdk/examples/file/notification/service/client.go rename to seed/go-sdk/examples/always-send-required-properties/file/notification/service/client.go diff --git a/seed/go-sdk/examples/file/service.go b/seed/go-sdk/examples/always-send-required-properties/file/service.go similarity index 100% rename from seed/go-sdk/examples/file/service.go rename to seed/go-sdk/examples/always-send-required-properties/file/service.go diff --git a/seed/go-sdk/examples/file/service/client.go b/seed/go-sdk/examples/always-send-required-properties/file/service/client.go similarity index 100% rename from seed/go-sdk/examples/file/service/client.go rename to seed/go-sdk/examples/always-send-required-properties/file/service/client.go diff --git a/seed/go-sdk/examples/file/types.go b/seed/go-sdk/examples/always-send-required-properties/file/types.go similarity index 100% rename from seed/go-sdk/examples/file/types.go rename to seed/go-sdk/examples/always-send-required-properties/file/types.go diff --git a/seed/go-sdk/examples/go.mod b/seed/go-sdk/examples/always-send-required-properties/go.mod similarity index 100% rename from seed/go-sdk/examples/go.mod rename to seed/go-sdk/examples/always-send-required-properties/go.mod diff --git a/seed/go-sdk/examples/go.sum b/seed/go-sdk/examples/always-send-required-properties/go.sum similarity index 100% rename from seed/go-sdk/examples/go.sum rename to seed/go-sdk/examples/always-send-required-properties/go.sum diff --git a/seed/go-sdk/examples/health/client/client.go b/seed/go-sdk/examples/always-send-required-properties/health/client/client.go similarity index 100% rename from seed/go-sdk/examples/health/client/client.go rename to seed/go-sdk/examples/always-send-required-properties/health/client/client.go diff --git a/seed/go-sdk/examples/health/service/client.go b/seed/go-sdk/examples/always-send-required-properties/health/service/client.go similarity index 100% rename from seed/go-sdk/examples/health/service/client.go rename to seed/go-sdk/examples/always-send-required-properties/health/service/client.go diff --git a/seed/go-sdk/examples/option/request_option.go b/seed/go-sdk/examples/always-send-required-properties/option/request_option.go similarity index 100% rename from seed/go-sdk/examples/option/request_option.go rename to seed/go-sdk/examples/always-send-required-properties/option/request_option.go diff --git a/seed/go-sdk/examples/pointer.go b/seed/go-sdk/examples/always-send-required-properties/pointer.go similarity index 100% rename from seed/go-sdk/examples/pointer.go rename to seed/go-sdk/examples/always-send-required-properties/pointer.go diff --git a/seed/go-sdk/examples/service.go b/seed/go-sdk/examples/always-send-required-properties/service.go similarity index 100% rename from seed/go-sdk/examples/service.go rename to seed/go-sdk/examples/always-send-required-properties/service.go diff --git a/seed/go-sdk/examples/service/client.go b/seed/go-sdk/examples/always-send-required-properties/service/client.go similarity index 100% rename from seed/go-sdk/examples/service/client.go rename to seed/go-sdk/examples/always-send-required-properties/service/client.go diff --git a/seed/go-sdk/examples/always-send-required-properties/snippet-templates.json b/seed/go-sdk/examples/always-send-required-properties/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-sdk/examples/snippet.json b/seed/go-sdk/examples/always-send-required-properties/snippet.json similarity index 100% rename from seed/go-sdk/examples/snippet.json rename to seed/go-sdk/examples/always-send-required-properties/snippet.json diff --git a/seed/go-sdk/examples/always-send-required-properties/types.go b/seed/go-sdk/examples/always-send-required-properties/types.go new file mode 100644 index 00000000000..92106e0da33 --- /dev/null +++ b/seed/go-sdk/examples/always-send-required-properties/types.go @@ -0,0 +1,1261 @@ +// This file was auto-generated by Fern from our API Definition. + +package examples + +import ( + json "encoding/json" + fmt "fmt" + commons "github.com/examples/fern/commons" + core "github.com/examples/fern/core" + uuid "github.com/google/uuid" + time "time" +) + +type BasicType string + +const ( + BasicTypePrimitive BasicType = "primitive" + BasicTypeLiteral BasicType = "literal" +) + +func NewBasicTypeFromString(s string) (BasicType, error) { + switch s { + case "primitive": + return BasicTypePrimitive, nil + case "literal": + return BasicTypeLiteral, nil + } + var t BasicType + return "", fmt.Errorf("%s is not a valid %T", s, t) +} + +func (b BasicType) Ptr() *BasicType { + return &b +} + +type ComplexType string + +const ( + ComplexTypeObject ComplexType = "object" + ComplexTypeUnion ComplexType = "union" + ComplexTypeUnknown ComplexType = "unknown" +) + +func NewComplexTypeFromString(s string) (ComplexType, error) { + switch s { + case "object": + return ComplexTypeObject, nil + case "union": + return ComplexTypeUnion, nil + case "unknown": + return ComplexTypeUnknown, nil + } + var t ComplexType + return "", fmt.Errorf("%s is not a valid %T", s, t) +} + +func (c ComplexType) Ptr() *ComplexType { + return &c +} + +type Identifier struct { + Type *Type `json:"type" url:"type"` + Value string `json:"value" url:"value"` + Label string `json:"label" url:"label"` + + extraProperties map[string]interface{} + _rawJSON json.RawMessage +} + +func (i *Identifier) GetExtraProperties() map[string]interface{} { + return i.extraProperties +} + +func (i *Identifier) UnmarshalJSON(data []byte) error { + type unmarshaler Identifier + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *i = Identifier(value) + + extraProperties, err := core.ExtractExtraProperties(data, *i) + if err != nil { + return err + } + i.extraProperties = extraProperties + + i._rawJSON = json.RawMessage(data) + return nil +} + +func (i *Identifier) String() string { + if len(i._rawJSON) > 0 { + if value, err := core.StringifyJSON(i._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(i); err == nil { + return value + } + return fmt.Sprintf("%#v", i) +} + +type Type struct { + BasicType BasicType + ComplexType ComplexType +} + +func NewTypeFromBasicType(value BasicType) *Type { + return &Type{BasicType: value} +} + +func NewTypeFromComplexType(value ComplexType) *Type { + return &Type{ComplexType: value} +} + +func (t *Type) UnmarshalJSON(data []byte) error { + var valueBasicType BasicType + if err := json.Unmarshal(data, &valueBasicType); err == nil { + t.BasicType = valueBasicType + return nil + } + var valueComplexType ComplexType + if err := json.Unmarshal(data, &valueComplexType); err == nil { + t.ComplexType = valueComplexType + return nil + } + return fmt.Errorf("%s cannot be deserialized as a %T", data, t) +} + +func (t Type) MarshalJSON() ([]byte, error) { + if t.BasicType != "" { + return json.Marshal(t.BasicType) + } + if t.ComplexType != "" { + return json.Marshal(t.ComplexType) + } + return nil, fmt.Errorf("type %T does not include a non-empty union type", t) +} + +type TypeVisitor interface { + VisitBasicType(BasicType) error + VisitComplexType(ComplexType) error +} + +func (t *Type) Accept(visitor TypeVisitor) error { + if t.BasicType != "" { + return visitor.VisitBasicType(t.BasicType) + } + if t.ComplexType != "" { + return visitor.VisitComplexType(t.ComplexType) + } + return fmt.Errorf("type %T does not include a non-empty union type", t) +} + +type Actor struct { + Name string `json:"name" url:"name"` + Id string `json:"id" url:"id"` + + extraProperties map[string]interface{} + _rawJSON json.RawMessage +} + +func (a *Actor) GetExtraProperties() map[string]interface{} { + return a.extraProperties +} + +func (a *Actor) UnmarshalJSON(data []byte) error { + type unmarshaler Actor + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *a = Actor(value) + + extraProperties, err := core.ExtractExtraProperties(data, *a) + if err != nil { + return err + } + a.extraProperties = extraProperties + + a._rawJSON = json.RawMessage(data) + return nil +} + +func (a *Actor) String() string { + if len(a._rawJSON) > 0 { + if value, err := core.StringifyJSON(a._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(a); err == nil { + return value + } + return fmt.Sprintf("%#v", a) +} + +type Actress struct { + Name string `json:"name" url:"name"` + Id string `json:"id" url:"id"` + + extraProperties map[string]interface{} + _rawJSON json.RawMessage +} + +func (a *Actress) GetExtraProperties() map[string]interface{} { + return a.extraProperties +} + +func (a *Actress) UnmarshalJSON(data []byte) error { + type unmarshaler Actress + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *a = Actress(value) + + extraProperties, err := core.ExtractExtraProperties(data, *a) + if err != nil { + return err + } + a.extraProperties = extraProperties + + a._rawJSON = json.RawMessage(data) + return nil +} + +func (a *Actress) String() string { + if len(a._rawJSON) > 0 { + if value, err := core.StringifyJSON(a._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(a); err == nil { + return value + } + return fmt.Sprintf("%#v", a) +} + +type CastMember struct { + Actor *Actor + Actress *Actress + StuntDouble *StuntDouble +} + +func NewCastMemberFromActor(value *Actor) *CastMember { + return &CastMember{Actor: value} +} + +func NewCastMemberFromActress(value *Actress) *CastMember { + return &CastMember{Actress: value} +} + +func NewCastMemberFromStuntDouble(value *StuntDouble) *CastMember { + return &CastMember{StuntDouble: value} +} + +func (c *CastMember) UnmarshalJSON(data []byte) error { + valueActor := new(Actor) + if err := json.Unmarshal(data, &valueActor); err == nil { + c.Actor = valueActor + return nil + } + valueActress := new(Actress) + if err := json.Unmarshal(data, &valueActress); err == nil { + c.Actress = valueActress + return nil + } + valueStuntDouble := new(StuntDouble) + if err := json.Unmarshal(data, &valueStuntDouble); err == nil { + c.StuntDouble = valueStuntDouble + return nil + } + return fmt.Errorf("%s cannot be deserialized as a %T", data, c) +} + +func (c CastMember) MarshalJSON() ([]byte, error) { + if c.Actor != nil { + return json.Marshal(c.Actor) + } + if c.Actress != nil { + return json.Marshal(c.Actress) + } + if c.StuntDouble != nil { + return json.Marshal(c.StuntDouble) + } + return nil, fmt.Errorf("type %T does not include a non-empty union type", c) +} + +type CastMemberVisitor interface { + VisitActor(*Actor) error + VisitActress(*Actress) error + VisitStuntDouble(*StuntDouble) error +} + +func (c *CastMember) Accept(visitor CastMemberVisitor) error { + if c.Actor != nil { + return visitor.VisitActor(c.Actor) + } + if c.Actress != nil { + return visitor.VisitActress(c.Actress) + } + if c.StuntDouble != nil { + return visitor.VisitStuntDouble(c.StuntDouble) + } + return fmt.Errorf("type %T does not include a non-empty union type", c) +} + +type Directory struct { + Name string `json:"name" url:"name"` + Files []*File `json:"files,omitempty" url:"files,omitempty"` + Directories []*Directory `json:"directories,omitempty" url:"directories,omitempty"` + + extraProperties map[string]interface{} + _rawJSON json.RawMessage +} + +func (d *Directory) GetExtraProperties() map[string]interface{} { + return d.extraProperties +} + +func (d *Directory) UnmarshalJSON(data []byte) error { + type unmarshaler Directory + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *d = Directory(value) + + extraProperties, err := core.ExtractExtraProperties(data, *d) + if err != nil { + return err + } + d.extraProperties = extraProperties + + d._rawJSON = json.RawMessage(data) + return nil +} + +func (d *Directory) String() string { + if len(d._rawJSON) > 0 { + if value, err := core.StringifyJSON(d._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(d); err == nil { + return value + } + return fmt.Sprintf("%#v", d) +} + +type Entity struct { + Type *Type `json:"type" url:"type"` + Name string `json:"name" url:"name"` + + extraProperties map[string]interface{} + _rawJSON json.RawMessage +} + +func (e *Entity) GetExtraProperties() map[string]interface{} { + return e.extraProperties +} + +func (e *Entity) UnmarshalJSON(data []byte) error { + type unmarshaler Entity + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *e = Entity(value) + + extraProperties, err := core.ExtractExtraProperties(data, *e) + if err != nil { + return err + } + e.extraProperties = extraProperties + + e._rawJSON = json.RawMessage(data) + return nil +} + +func (e *Entity) String() string { + if len(e._rawJSON) > 0 { + if value, err := core.StringifyJSON(e._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(e); err == nil { + return value + } + return fmt.Sprintf("%#v", e) +} + +type Exception struct { + Type string + Generic *ExceptionInfo + Timeout interface{} +} + +func NewExceptionFromGeneric(value *ExceptionInfo) *Exception { + return &Exception{Type: "generic", Generic: value} +} + +func NewExceptionFromTimeout(value interface{}) *Exception { + return &Exception{Type: "timeout", Timeout: value} +} + +func (e *Exception) UnmarshalJSON(data []byte) error { + var unmarshaler struct { + Type string `json:"type"` + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + e.Type = unmarshaler.Type + switch unmarshaler.Type { + case "generic": + value := new(ExceptionInfo) + if err := json.Unmarshal(data, &value); err != nil { + return err + } + e.Generic = value + case "timeout": + value := make(map[string]interface{}) + if err := json.Unmarshal(data, &value); err != nil { + return err + } + e.Timeout = value + } + return nil +} + +func (e Exception) MarshalJSON() ([]byte, error) { + switch e.Type { + default: + return nil, fmt.Errorf("invalid type %s in %T", e.Type, e) + case "generic": + return core.MarshalJSONWithExtraProperty(e.Generic, "type", "generic") + case "timeout": + var marshaler = struct { + Type string `json:"type"` + Timeout interface{} `json:"timeout,omitempty"` + }{ + Type: "timeout", + Timeout: e.Timeout, + } + return json.Marshal(marshaler) + } +} + +type ExceptionVisitor interface { + VisitGeneric(*ExceptionInfo) error + VisitTimeout(interface{}) error +} + +func (e *Exception) Accept(visitor ExceptionVisitor) error { + switch e.Type { + default: + return fmt.Errorf("invalid type %s in %T", e.Type, e) + case "generic": + return visitor.VisitGeneric(e.Generic) + case "timeout": + return visitor.VisitTimeout(e.Timeout) + } +} + +type ExceptionInfo struct { + ExceptionType string `json:"exceptionType" url:"exceptionType"` + ExceptionMessage string `json:"exceptionMessage" url:"exceptionMessage"` + ExceptionStacktrace string `json:"exceptionStacktrace" url:"exceptionStacktrace"` + + extraProperties map[string]interface{} + _rawJSON json.RawMessage +} + +func (e *ExceptionInfo) GetExtraProperties() map[string]interface{} { + return e.extraProperties +} + +func (e *ExceptionInfo) UnmarshalJSON(data []byte) error { + type unmarshaler ExceptionInfo + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *e = ExceptionInfo(value) + + extraProperties, err := core.ExtractExtraProperties(data, *e) + if err != nil { + return err + } + e.extraProperties = extraProperties + + e._rawJSON = json.RawMessage(data) + return nil +} + +func (e *ExceptionInfo) String() string { + if len(e._rawJSON) > 0 { + if value, err := core.StringifyJSON(e._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(e); err == nil { + return value + } + return fmt.Sprintf("%#v", e) +} + +type ExtendedMovie struct { + Id MovieId `json:"id" url:"id"` + Prequel *MovieId `json:"prequel,omitempty" url:"prequel,omitempty"` + Title string `json:"title" url:"title"` + From string `json:"from" url:"from"` + // The rating scale is one to five stars + Rating float64 `json:"rating" url:"rating"` + Tag commons.Tag `json:"tag" url:"tag"` + Book *string `json:"book,omitempty" url:"book,omitempty"` + Metadata map[string]interface{} `json:"metadata" url:"metadata"` + Cast []string `json:"cast" url:"cast"` + type_ string + + extraProperties map[string]interface{} + _rawJSON json.RawMessage +} + +func (e *ExtendedMovie) GetExtraProperties() map[string]interface{} { + return e.extraProperties +} + +func (e *ExtendedMovie) Type() string { + return e.type_ +} + +func (e *ExtendedMovie) UnmarshalJSON(data []byte) error { + type embed ExtendedMovie + var unmarshaler = struct { + embed + }{ + embed: embed(*e), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + *e = ExtendedMovie(unmarshaler.embed) + e.type_ = "movie" + + extraProperties, err := core.ExtractExtraProperties(data, *e, "type") + if err != nil { + return err + } + e.extraProperties = extraProperties + + e._rawJSON = json.RawMessage(data) + return nil +} + +func (e *ExtendedMovie) MarshalJSON() ([]byte, error) { + type embed ExtendedMovie + var marshaler = struct { + embed + Type string `json:"type"` + }{ + embed: embed(*e), + Type: "movie", + } + return json.Marshal(marshaler) +} + +func (e *ExtendedMovie) String() string { + if len(e._rawJSON) > 0 { + if value, err := core.StringifyJSON(e._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(e); err == nil { + return value + } + return fmt.Sprintf("%#v", e) +} + +type File struct { + Name string `json:"name" url:"name"` + Contents string `json:"contents" url:"contents"` + + extraProperties map[string]interface{} + _rawJSON json.RawMessage +} + +func (f *File) GetExtraProperties() map[string]interface{} { + return f.extraProperties +} + +func (f *File) UnmarshalJSON(data []byte) error { + type unmarshaler File + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *f = File(value) + + extraProperties, err := core.ExtractExtraProperties(data, *f) + if err != nil { + return err + } + f.extraProperties = extraProperties + + f._rawJSON = json.RawMessage(data) + return nil +} + +func (f *File) String() string { + if len(f._rawJSON) > 0 { + if value, err := core.StringifyJSON(f._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(f); err == nil { + return value + } + return fmt.Sprintf("%#v", f) +} + +type Metadata struct { + Type string + Extra map[string]string + Tags []string + Html string + Markdown string +} + +func NewMetadataFromHtml(value string) *Metadata { + return &Metadata{Type: "html", Html: value} +} + +func NewMetadataFromMarkdown(value string) *Metadata { + return &Metadata{Type: "markdown", Markdown: value} +} + +func (m *Metadata) UnmarshalJSON(data []byte) error { + var unmarshaler struct { + Type string `json:"type"` + Extra map[string]string `json:"extra"` + Tags []string `json:"tags"` + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + m.Type = unmarshaler.Type + m.Extra = unmarshaler.Extra + m.Tags = unmarshaler.Tags + switch unmarshaler.Type { + case "html": + var valueUnmarshaler struct { + Html string `json:"value"` + } + if err := json.Unmarshal(data, &valueUnmarshaler); err != nil { + return err + } + m.Html = valueUnmarshaler.Html + case "markdown": + var valueUnmarshaler struct { + Markdown string `json:"value"` + } + if err := json.Unmarshal(data, &valueUnmarshaler); err != nil { + return err + } + m.Markdown = valueUnmarshaler.Markdown + } + return nil +} + +func (m Metadata) MarshalJSON() ([]byte, error) { + switch m.Type { + default: + return nil, fmt.Errorf("invalid type %s in %T", m.Type, m) + case "html": + var marshaler = struct { + Type string `json:"type"` + Extra map[string]string `json:"extra"` + Tags []string `json:"tags"` + Html string `json:"value"` + }{ + Type: "html", + Extra: m.Extra, + Tags: m.Tags, + Html: m.Html, + } + return json.Marshal(marshaler) + case "markdown": + var marshaler = struct { + Type string `json:"type"` + Extra map[string]string `json:"extra"` + Tags []string `json:"tags"` + Markdown string `json:"value"` + }{ + Type: "markdown", + Extra: m.Extra, + Tags: m.Tags, + Markdown: m.Markdown, + } + return json.Marshal(marshaler) + } +} + +type MetadataVisitor interface { + VisitHtml(string) error + VisitMarkdown(string) error +} + +func (m *Metadata) Accept(visitor MetadataVisitor) error { + switch m.Type { + default: + return fmt.Errorf("invalid type %s in %T", m.Type, m) + case "html": + return visitor.VisitHtml(m.Html) + case "markdown": + return visitor.VisitMarkdown(m.Markdown) + } +} + +type Migration struct { + Name string `json:"name" url:"name"` + Status MigrationStatus `json:"status" url:"status"` + + extraProperties map[string]interface{} + _rawJSON json.RawMessage +} + +func (m *Migration) GetExtraProperties() map[string]interface{} { + return m.extraProperties +} + +func (m *Migration) UnmarshalJSON(data []byte) error { + type unmarshaler Migration + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *m = Migration(value) + + extraProperties, err := core.ExtractExtraProperties(data, *m) + if err != nil { + return err + } + m.extraProperties = extraProperties + + m._rawJSON = json.RawMessage(data) + return nil +} + +func (m *Migration) String() string { + if len(m._rawJSON) > 0 { + if value, err := core.StringifyJSON(m._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(m); err == nil { + return value + } + return fmt.Sprintf("%#v", m) +} + +type MigrationStatus string + +const ( + // The migration is running. + MigrationStatusRunning MigrationStatus = "RUNNING" + // The migration failed. + MigrationStatusFailed MigrationStatus = "FAILED" + MigrationStatusFinished MigrationStatus = "FINISHED" +) + +func NewMigrationStatusFromString(s string) (MigrationStatus, error) { + switch s { + case "RUNNING": + return MigrationStatusRunning, nil + case "FAILED": + return MigrationStatusFailed, nil + case "FINISHED": + return MigrationStatusFinished, nil + } + var t MigrationStatus + return "", fmt.Errorf("%s is not a valid %T", s, t) +} + +func (m MigrationStatus) Ptr() *MigrationStatus { + return &m +} + +type Moment struct { + Id uuid.UUID `json:"id" url:"id"` + Date time.Time `json:"date" url:"date" format:"date"` + Datetime time.Time `json:"datetime" url:"datetime"` + + extraProperties map[string]interface{} + _rawJSON json.RawMessage +} + +func (m *Moment) GetExtraProperties() map[string]interface{} { + return m.extraProperties +} + +func (m *Moment) UnmarshalJSON(data []byte) error { + type embed Moment + var unmarshaler = struct { + embed + Date *core.Date `json:"date"` + Datetime *core.DateTime `json:"datetime"` + }{ + embed: embed(*m), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + *m = Moment(unmarshaler.embed) + m.Date = unmarshaler.Date.Time() + m.Datetime = unmarshaler.Datetime.Time() + + extraProperties, err := core.ExtractExtraProperties(data, *m) + if err != nil { + return err + } + m.extraProperties = extraProperties + + m._rawJSON = json.RawMessage(data) + return nil +} + +func (m *Moment) MarshalJSON() ([]byte, error) { + type embed Moment + var marshaler = struct { + embed + Date *core.Date `json:"date"` + Datetime *core.DateTime `json:"datetime"` + }{ + embed: embed(*m), + Date: core.NewDate(m.Date), + Datetime: core.NewDateTime(m.Datetime), + } + return json.Marshal(marshaler) +} + +func (m *Moment) String() string { + if len(m._rawJSON) > 0 { + if value, err := core.StringifyJSON(m._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(m); err == nil { + return value + } + return fmt.Sprintf("%#v", m) +} + +type Movie struct { + Id MovieId `json:"id" url:"id"` + Prequel *MovieId `json:"prequel,omitempty" url:"prequel,omitempty"` + Title string `json:"title" url:"title"` + From string `json:"from" url:"from"` + // The rating scale is one to five stars + Rating float64 `json:"rating" url:"rating"` + Tag commons.Tag `json:"tag" url:"tag"` + Book *string `json:"book,omitempty" url:"book,omitempty"` + Metadata map[string]interface{} `json:"metadata" url:"metadata"` + type_ string + + extraProperties map[string]interface{} + _rawJSON json.RawMessage +} + +func (m *Movie) GetExtraProperties() map[string]interface{} { + return m.extraProperties +} + +func (m *Movie) Type() string { + return m.type_ +} + +func (m *Movie) UnmarshalJSON(data []byte) error { + type embed Movie + var unmarshaler = struct { + embed + }{ + embed: embed(*m), + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + *m = Movie(unmarshaler.embed) + m.type_ = "movie" + + extraProperties, err := core.ExtractExtraProperties(data, *m, "type") + if err != nil { + return err + } + m.extraProperties = extraProperties + + m._rawJSON = json.RawMessage(data) + return nil +} + +func (m *Movie) MarshalJSON() ([]byte, error) { + type embed Movie + var marshaler = struct { + embed + Type string `json:"type"` + }{ + embed: embed(*m), + Type: "movie", + } + return json.Marshal(marshaler) +} + +func (m *Movie) String() string { + if len(m._rawJSON) > 0 { + if value, err := core.StringifyJSON(m._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(m); err == nil { + return value + } + return fmt.Sprintf("%#v", m) +} + +type MovieId = string + +type Node struct { + Name string `json:"name" url:"name"` + Nodes []*Node `json:"nodes,omitempty" url:"nodes,omitempty"` + Trees []*Tree `json:"trees,omitempty" url:"trees,omitempty"` + + extraProperties map[string]interface{} + _rawJSON json.RawMessage +} + +func (n *Node) GetExtraProperties() map[string]interface{} { + return n.extraProperties +} + +func (n *Node) UnmarshalJSON(data []byte) error { + type unmarshaler Node + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *n = Node(value) + + extraProperties, err := core.ExtractExtraProperties(data, *n) + if err != nil { + return err + } + n.extraProperties = extraProperties + + n._rawJSON = json.RawMessage(data) + return nil +} + +func (n *Node) String() string { + if len(n._rawJSON) > 0 { + if value, err := core.StringifyJSON(n._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(n); err == nil { + return value + } + return fmt.Sprintf("%#v", n) +} + +type Request struct { + Request interface{} `json:"request" url:"request"` + + extraProperties map[string]interface{} + _rawJSON json.RawMessage +} + +func (r *Request) GetExtraProperties() map[string]interface{} { + return r.extraProperties +} + +func (r *Request) UnmarshalJSON(data []byte) error { + type unmarshaler Request + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *r = Request(value) + + extraProperties, err := core.ExtractExtraProperties(data, *r) + if err != nil { + return err + } + r.extraProperties = extraProperties + + r._rawJSON = json.RawMessage(data) + return nil +} + +func (r *Request) String() string { + if len(r._rawJSON) > 0 { + if value, err := core.StringifyJSON(r._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(r); err == nil { + return value + } + return fmt.Sprintf("%#v", r) +} + +type Response struct { + Response interface{} `json:"response" url:"response"` + Identifiers []*Identifier `json:"identifiers" url:"identifiers"` + + extraProperties map[string]interface{} + _rawJSON json.RawMessage +} + +func (r *Response) GetExtraProperties() map[string]interface{} { + return r.extraProperties +} + +func (r *Response) UnmarshalJSON(data []byte) error { + type unmarshaler Response + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *r = Response(value) + + extraProperties, err := core.ExtractExtraProperties(data, *r) + if err != nil { + return err + } + r.extraProperties = extraProperties + + r._rawJSON = json.RawMessage(data) + return nil +} + +func (r *Response) String() string { + if len(r._rawJSON) > 0 { + if value, err := core.StringifyJSON(r._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(r); err == nil { + return value + } + return fmt.Sprintf("%#v", r) +} + +type ResponseType struct { + Type *Type `json:"type" url:"type"` + + extraProperties map[string]interface{} + _rawJSON json.RawMessage +} + +func (r *ResponseType) GetExtraProperties() map[string]interface{} { + return r.extraProperties +} + +func (r *ResponseType) UnmarshalJSON(data []byte) error { + type unmarshaler ResponseType + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *r = ResponseType(value) + + extraProperties, err := core.ExtractExtraProperties(data, *r) + if err != nil { + return err + } + r.extraProperties = extraProperties + + r._rawJSON = json.RawMessage(data) + return nil +} + +func (r *ResponseType) String() string { + if len(r._rawJSON) > 0 { + if value, err := core.StringifyJSON(r._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(r); err == nil { + return value + } + return fmt.Sprintf("%#v", r) +} + +type StuntDouble struct { + Name string `json:"name" url:"name"` + ActorOrActressId string `json:"actorOrActressId" url:"actorOrActressId"` + + extraProperties map[string]interface{} + _rawJSON json.RawMessage +} + +func (s *StuntDouble) GetExtraProperties() map[string]interface{} { + return s.extraProperties +} + +func (s *StuntDouble) UnmarshalJSON(data []byte) error { + type unmarshaler StuntDouble + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *s = StuntDouble(value) + + extraProperties, err := core.ExtractExtraProperties(data, *s) + if err != nil { + return err + } + s.extraProperties = extraProperties + + s._rawJSON = json.RawMessage(data) + return nil +} + +func (s *StuntDouble) String() string { + if len(s._rawJSON) > 0 { + if value, err := core.StringifyJSON(s._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(s); err == nil { + return value + } + return fmt.Sprintf("%#v", s) +} + +type Test struct { + Type string + And bool + Or bool +} + +func NewTestFromAnd(value bool) *Test { + return &Test{Type: "and", And: value} +} + +func NewTestFromOr(value bool) *Test { + return &Test{Type: "or", Or: value} +} + +func (t *Test) UnmarshalJSON(data []byte) error { + var unmarshaler struct { + Type string `json:"type"` + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + t.Type = unmarshaler.Type + switch unmarshaler.Type { + case "and": + var valueUnmarshaler struct { + And bool `json:"value"` + } + if err := json.Unmarshal(data, &valueUnmarshaler); err != nil { + return err + } + t.And = valueUnmarshaler.And + case "or": + var valueUnmarshaler struct { + Or bool `json:"value"` + } + if err := json.Unmarshal(data, &valueUnmarshaler); err != nil { + return err + } + t.Or = valueUnmarshaler.Or + } + return nil +} + +func (t Test) MarshalJSON() ([]byte, error) { + switch t.Type { + default: + return nil, fmt.Errorf("invalid type %s in %T", t.Type, t) + case "and": + var marshaler = struct { + Type string `json:"type"` + And bool `json:"value"` + }{ + Type: "and", + And: t.And, + } + return json.Marshal(marshaler) + case "or": + var marshaler = struct { + Type string `json:"type"` + Or bool `json:"value"` + }{ + Type: "or", + Or: t.Or, + } + return json.Marshal(marshaler) + } +} + +type TestVisitor interface { + VisitAnd(bool) error + VisitOr(bool) error +} + +func (t *Test) Accept(visitor TestVisitor) error { + switch t.Type { + default: + return fmt.Errorf("invalid type %s in %T", t.Type, t) + case "and": + return visitor.VisitAnd(t.And) + case "or": + return visitor.VisitOr(t.Or) + } +} + +type Tree struct { + Nodes []*Node `json:"nodes,omitempty" url:"nodes,omitempty"` + + extraProperties map[string]interface{} + _rawJSON json.RawMessage +} + +func (t *Tree) GetExtraProperties() map[string]interface{} { + return t.extraProperties +} + +func (t *Tree) UnmarshalJSON(data []byte) error { + type unmarshaler Tree + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *t = Tree(value) + + extraProperties, err := core.ExtractExtraProperties(data, *t) + if err != nil { + return err + } + t.extraProperties = extraProperties + + t._rawJSON = json.RawMessage(data) + return nil +} + +func (t *Tree) String() string { + if len(t._rawJSON) > 0 { + if value, err := core.StringifyJSON(t._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(t); err == nil { + return value + } + return fmt.Sprintf("%#v", t) +} diff --git a/seed/go-sdk/examples/no-custom-config/.github/workflows/ci.yml b/seed/go-sdk/examples/no-custom-config/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-sdk/examples/no-custom-config/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-sdk/examples/no-custom-config/.mock/definition/__package__.yml b/seed/go-sdk/examples/no-custom-config/.mock/definition/__package__.yml new file mode 100644 index 00000000000..5f3e6bfe1b1 --- /dev/null +++ b/seed/go-sdk/examples/no-custom-config/.mock/definition/__package__.yml @@ -0,0 +1,42 @@ +types: + Type: + discriminated: false + union: + - BasicType + - ComplexType + + Identifier: + properties: + type: Type + value: string + label: string + + BasicType: + enum: + - name: Primitive + value: primitive + - name: Literal + value: literal + + ComplexType: + enum: + - name: Object + value: object + - name: Union + value: union + - name: unknown + value: unknown + +service: + auth: false + base-path: / + endpoints: + echo: + method: POST + path: "" + request: string + response: string + examples: + - request: Hello world!\n\nwith\n\tnewlines + response: + body: Hello world!\n\nwith\n\tnewlines diff --git a/seed/go-sdk/examples/no-custom-config/.mock/definition/api.yml b/seed/go-sdk/examples/no-custom-config/.mock/definition/api.yml new file mode 100644 index 00000000000..3f3cb4540de --- /dev/null +++ b/seed/go-sdk/examples/no-custom-config/.mock/definition/api.yml @@ -0,0 +1,8 @@ +name: examples +auth: bearer +error-discrimination: + strategy: status-code +environments: + Production: https://production.com/api + Staging: https://staging.com/api +default-environment: null diff --git a/seed/go-sdk/examples/no-custom-config/.mock/definition/commons/types.yml b/seed/go-sdk/examples/no-custom-config/.mock/definition/commons/types.yml new file mode 100644 index 00000000000..33f95a4ab1b --- /dev/null +++ b/seed/go-sdk/examples/no-custom-config/.mock/definition/commons/types.yml @@ -0,0 +1,43 @@ +types: + Tag: + type: string + examples: + - name: One + value: tag-wf9as23d + + Metadata: + properties: + id: string + data: optional> + jsonString: optional + examples: + - name: One + value: + id: metadata-js8dg24b + data: + foo: bar + baz: qux + jsonString: '{"foo": "bar", "baz": "qux"}' + + EventInfo: + union: + metadata: Metadata + tag: Tag + examples: + - name: Metadata + value: + type: metadata + id: metadata-alskjfg8 + data: + one: two + jsonString: '{"one": "two"}' + + Data: + union: + string: string + base64: base64 + examples: + - name: String + value: + type: string + value: data diff --git a/seed/go-sdk/examples/no-custom-config/.mock/definition/file/notification/service.yml b/seed/go-sdk/examples/no-custom-config/.mock/definition/file/notification/service.yml new file mode 100644 index 00000000000..7f518565dcf --- /dev/null +++ b/seed/go-sdk/examples/no-custom-config/.mock/definition/file/notification/service.yml @@ -0,0 +1,18 @@ +imports: + types: ../../types.yml + +service: + auth: true + base-path: /file/notification/{notificationId} + path-parameters: + notificationId: string + endpoints: + getException: + method: GET + path: "" + response: types.Exception + examples: + - path-parameters: + notificationId: notification-hsy129x + response: + body: $types.Exception.One diff --git a/seed/go-sdk/examples/no-custom-config/.mock/definition/file/service.yml b/seed/go-sdk/examples/no-custom-config/.mock/definition/file/service.yml new file mode 100644 index 00000000000..349d80d6543 --- /dev/null +++ b/seed/go-sdk/examples/no-custom-config/.mock/definition/file/service.yml @@ -0,0 +1,37 @@ +imports: + types: ../types.yml + +types: + Filename: + type: string + examples: + - name: Example0 + value: file.txt + +service: + auth: true + base-path: /file + headers: + X-File-API-Version: string + endpoints: + getFile: + docs: "This endpoint returns a file by its name." + method: GET + path: /{filename} + path-parameters: + filename: + type: string + docs: This is a filename + request: + name: GetFileRequest + response: types.File + errors: + - types.NotFoundError + examples: + - path-parameters: + filename: $Filename.Example0 + headers: + X-File-API-Version: 0.0.2 + response: + error: types.NotFoundError + body: A file with that name was not found! diff --git a/seed/go-sdk/examples/no-custom-config/.mock/definition/health/service.yml b/seed/go-sdk/examples/no-custom-config/.mock/definition/health/service.yml new file mode 100644 index 00000000000..46ba829d120 --- /dev/null +++ b/seed/go-sdk/examples/no-custom-config/.mock/definition/health/service.yml @@ -0,0 +1,32 @@ +imports: + types: ../types.yml + +service: + auth: true + base-path: / + endpoints: + check: + docs: "This endpoint checks the health of a resource." + method: GET + path: /check/{id} + path-parameters: + id: + type: string + docs: The id to check + examples: + - name: Example0 + path-parameters: + id: id-2sdx82h + - name: Example2 + path-parameters: + id: id-3tey93i + + ping: + docs: "This endpoint checks the health of the service." + method: GET + path: /ping + response: boolean + examples: + - name: Example0 + response: + body: true diff --git a/seed/go-sdk/examples/no-custom-config/.mock/definition/service.yml b/seed/go-sdk/examples/no-custom-config/.mock/definition/service.yml new file mode 100644 index 00000000000..dbf858df8a2 --- /dev/null +++ b/seed/go-sdk/examples/no-custom-config/.mock/definition/service.yml @@ -0,0 +1,58 @@ +imports: + types: types.yml + +service: + auth: false + base-path: / + endpoints: + getMovie: + method: GET + path: /movie/{movieId} + path-parameters: + movieId: types.MovieId + response: types.Movie + examples: + - path-parameters: + movieId: $types.MovieId.One + response: + body: $types.Movie.One + + createMovie: + method: POST + path: /movie + request: types.Movie + response: types.MovieId + examples: + - request: $types.Movie.One + response: + body: $types.MovieId.One + + getMetadata: + method: GET + path: /metadata + request: + name: GetMetadataRequest + query-parameters: + shallow: optional + tag: + type: optional + allow-multiple: true + headers: + X-API-Version: string + response: types.Metadata + examples: + - query-parameters: + shallow: false + tag: development + headers: + X-API-Version: 0.0.1 + response: + body: $types.Metadata.One + + getResponse: + method: POST + path: /response + response: types.Response + examples: + - response: + body: $types.Response.String \ No newline at end of file diff --git a/seed/go-sdk/examples/no-custom-config/.mock/definition/types.yml b/seed/go-sdk/examples/no-custom-config/.mock/definition/types.yml new file mode 100644 index 00000000000..88e8b970f26 --- /dev/null +++ b/seed/go-sdk/examples/no-custom-config/.mock/definition/types.yml @@ -0,0 +1,295 @@ +imports: + commons: commons/types.yml + root: __package__.yml + +errors: + NotFoundError: + status-code: 404 + type: string + +types: + MovieId: + type: string + examples: + - name: One + value: movie-c06a4ad7 + + Movie: + properties: + id: MovieId + prequel: optional + title: string + from: string + rating: + type: double + docs: The rating scale is one to five stars + type: literal<"movie"> + tag: commons.Tag + book: optional + metadata: map + examples: + - name: One + value: + id: $MovieId.One + prequel: "movie-cv9b914f" + title: The Boy and the Heron + from: Hayao Miyazaki + rating: 8.0 + type: movie + tag: $commons.Tag.One + metadata: + actors: + - Christian Bale + - Florence Pugh + - Willem Dafoe + releaseDate: "2023-12-08" + ratings: + rottenTomatoes: 97 + imdb: 7.6 + + CastMember: + discriminated: false + union: + - Actor + - Actress + - StuntDouble + examples: + - name: Example0 + value: + id: actor_123 + name: "Brad Pitt" + - name: Example1 + value: $Actress.Example0 + + Actor: + properties: + name: string + id: string + + Actress: + properties: + name: string + id: string + examples: + - name: Example0 + value: + name: Jennifer Lawrence + id: actor_456 + + StuntDouble: + properties: + name: string + actorOrActressId: string + + ExtendedMovie: + extends: Movie + properties: + cast: list + examples: + - value: + id: movie-sda231x + title: Pulp Fiction + from: Quentin Tarantino + rating: 8.5 + type: movie + tag: tag-12efs9dv + cast: + - John Travolta + - Samuel L. Jackson + - Uma Thurman + - Bruce Willis + metadata: + academyAward: true + releaseDate: "2023-12-08" + ratings: + rottenTomatoes: 97 + imdb: 7.6 + + Moment: + properties: + id: uuid + date: date + datetime: datetime + examples: + - value: + id: 656f12d6-f592-444c-a1d3-a3cfd46d5b39 + date: 1994-01-01 + datetime: 1994-01-01T01:01:01Z + + File: + properties: + name: string + contents: string + examples: + - name: One + value: + name: file.txt + contents: ... + - name: Two + value: + name: another_file.txt + contents: ... + + Directory: + properties: + name: string + files: optional> + directories: optional> + examples: + - name: One + value: + name: root + files: + - $File.One + directories: + - name: tmp + files: + - $File.Two + + Node: + properties: + name: string + nodes: optional> + trees: optional> + examples: + - name: Tree + value: + name: root + nodes: + - $Node.Left + - $Node.Right + trees: + - $Tree.Root + - name: Left + value: + name: left + - name: Right + value: + name: right + + Tree: + properties: + nodes: optional> + examples: + - name: Root + value: + nodes: + - $Node.Left + - $Node.Right + + Metadata: + base-properties: + extra: map + tags: set + union: + html: string + markdown: string + examples: + - name: One + value: + type: html + extra: + version: 0.0.1 + tenancy: test + tags: + - development + - public + value: ... + + Exception: + union: + generic: ExceptionInfo + timeout: {} + examples: + - name: One + value: + type: generic + exceptionType: Unavailable + exceptionMessage: This component is unavailable! + exceptionStacktrace: + + ExceptionInfo: + properties: + exceptionType: string + exceptionMessage: string + exceptionStacktrace: string + examples: + - name: One + value: + exceptionType: Unavailable + exceptionMessage: This component is unavailable! + exceptionStacktrace: + + MigrationStatus: + enum: + - value: RUNNING + docs: The migration is running. + - value: FAILED + docs: The migration failed. + - FINISHED + examples: + - name: Running + value: RUNNING + - name: Failed + value: FAILED + + Migration: + properties: + name: string + status: MigrationStatus + examples: + - value: + name: 001_init + status: $MigrationStatus.Running + + Request: + properties: + request: unknown + examples: + - name: Empty + value: + request: {} + + Response: + properties: + response: unknown + identifiers: list + examples: + - name: String + value: + response: "Initializing..." + identifiers: + - type: primitive + value: 'example' + label: Primitive + - type: unknown + value: '{}' + label: Unknown + + ResponseType: + properties: + type: root.Type + + Test: + union: + and: boolean + or: boolean + examples: + - name: And + value: + type: and + value: true + - name: Or + value: + type: or + value: true + + Entity: + properties: + type: root.Type + name: string + examples: + - name: One + value: + type: unknown + name: unknown diff --git a/seed/go-sdk/examples/no-custom-config/.mock/fern.config.json b/seed/go-sdk/examples/no-custom-config/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/go-sdk/examples/no-custom-config/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/go-sdk/examples/no-custom-config/.mock/generators.yml b/seed/go-sdk/examples/no-custom-config/.mock/generators.yml new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/seed/go-sdk/examples/no-custom-config/.mock/generators.yml @@ -0,0 +1 @@ +{} diff --git a/seed/go-sdk/examples/no-custom-config/client/client.go b/seed/go-sdk/examples/no-custom-config/client/client.go new file mode 100644 index 00000000000..a81b56d8e49 --- /dev/null +++ b/seed/go-sdk/examples/no-custom-config/client/client.go @@ -0,0 +1,76 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + context "context" + core "github.com/examples/fern/core" + fileclient "github.com/examples/fern/file/client" + healthclient "github.com/examples/fern/health/client" + option "github.com/examples/fern/option" + service "github.com/examples/fern/service" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header + + File *fileclient.Client + Health *healthclient.Client + Service *service.Client +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + File: fileclient.NewClient(opts...), + Health: healthclient.NewClient(opts...), + Service: service.NewClient(opts...), + } +} + +func (c *Client) Echo( + ctx context.Context, + request string, + opts ...option.RequestOption, +) (string, error) { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := baseURL + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + var response string + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodPost, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Request: request, + Response: &response, + }, + ); err != nil { + return "", err + } + return response, nil +} diff --git a/seed/go-sdk/examples/no-custom-config/client/client_test.go b/seed/go-sdk/examples/no-custom-config/client/client_test.go new file mode 100644 index 00000000000..ace2a08796d --- /dev/null +++ b/seed/go-sdk/examples/no-custom-config/client/client_test.go @@ -0,0 +1,45 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + option "github.com/examples/fern/option" + assert "github.com/stretchr/testify/assert" + http "net/http" + testing "testing" + time "time" +) + +func TestNewClient(t *testing.T) { + t.Run("default", func(t *testing.T) { + c := NewClient() + assert.Empty(t, c.baseURL) + }) + + t.Run("base url", func(t *testing.T) { + c := NewClient( + option.WithBaseURL("test.co"), + ) + assert.Equal(t, "test.co", c.baseURL) + }) + + t.Run("http client", func(t *testing.T) { + httpClient := &http.Client{ + Timeout: 5 * time.Second, + } + c := NewClient( + option.WithHTTPClient(httpClient), + ) + assert.Empty(t, c.baseURL) + }) + + t.Run("http header", func(t *testing.T) { + header := make(http.Header) + header.Set("X-API-Tenancy", "test") + c := NewClient( + option.WithHTTPHeader(header), + ) + assert.Empty(t, c.baseURL) + assert.Equal(t, "test", c.header.Get("X-API-Tenancy")) + }) +} diff --git a/seed/go-sdk/examples/no-custom-config/commons/types.go b/seed/go-sdk/examples/no-custom-config/commons/types.go new file mode 100644 index 00000000000..d82aa0feae8 --- /dev/null +++ b/seed/go-sdk/examples/no-custom-config/commons/types.go @@ -0,0 +1,213 @@ +// This file was auto-generated by Fern from our API Definition. + +package commons + +import ( + json "encoding/json" + fmt "fmt" + core "github.com/examples/fern/core" +) + +type Data struct { + Type string + String string + Base64 []byte +} + +func NewDataFromString(value string) *Data { + return &Data{Type: "string", String: value} +} + +func NewDataFromBase64(value []byte) *Data { + return &Data{Type: "base64", Base64: value} +} + +func (d *Data) UnmarshalJSON(data []byte) error { + var unmarshaler struct { + Type string `json:"type"` + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + d.Type = unmarshaler.Type + switch unmarshaler.Type { + case "string": + var valueUnmarshaler struct { + String string `json:"value"` + } + if err := json.Unmarshal(data, &valueUnmarshaler); err != nil { + return err + } + d.String = valueUnmarshaler.String + case "base64": + var valueUnmarshaler struct { + Base64 []byte `json:"value"` + } + if err := json.Unmarshal(data, &valueUnmarshaler); err != nil { + return err + } + d.Base64 = valueUnmarshaler.Base64 + } + return nil +} + +func (d Data) MarshalJSON() ([]byte, error) { + switch d.Type { + default: + return nil, fmt.Errorf("invalid type %s in %T", d.Type, d) + case "string": + var marshaler = struct { + Type string `json:"type"` + String string `json:"value"` + }{ + Type: "string", + String: d.String, + } + return json.Marshal(marshaler) + case "base64": + var marshaler = struct { + Type string `json:"type"` + Base64 []byte `json:"value"` + }{ + Type: "base64", + Base64: d.Base64, + } + return json.Marshal(marshaler) + } +} + +type DataVisitor interface { + VisitString(string) error + VisitBase64([]byte) error +} + +func (d *Data) Accept(visitor DataVisitor) error { + switch d.Type { + default: + return fmt.Errorf("invalid type %s in %T", d.Type, d) + case "string": + return visitor.VisitString(d.String) + case "base64": + return visitor.VisitBase64(d.Base64) + } +} + +type EventInfo struct { + Type string + Metadata *Metadata + Tag Tag +} + +func NewEventInfoFromMetadata(value *Metadata) *EventInfo { + return &EventInfo{Type: "metadata", Metadata: value} +} + +func NewEventInfoFromTag(value Tag) *EventInfo { + return &EventInfo{Type: "tag", Tag: value} +} + +func (e *EventInfo) UnmarshalJSON(data []byte) error { + var unmarshaler struct { + Type string `json:"type"` + } + if err := json.Unmarshal(data, &unmarshaler); err != nil { + return err + } + e.Type = unmarshaler.Type + switch unmarshaler.Type { + case "metadata": + value := new(Metadata) + if err := json.Unmarshal(data, &value); err != nil { + return err + } + e.Metadata = value + case "tag": + var valueUnmarshaler struct { + Tag Tag `json:"value"` + } + if err := json.Unmarshal(data, &valueUnmarshaler); err != nil { + return err + } + e.Tag = valueUnmarshaler.Tag + } + return nil +} + +func (e EventInfo) MarshalJSON() ([]byte, error) { + switch e.Type { + default: + return nil, fmt.Errorf("invalid type %s in %T", e.Type, e) + case "metadata": + return core.MarshalJSONWithExtraProperty(e.Metadata, "type", "metadata") + case "tag": + var marshaler = struct { + Type string `json:"type"` + Tag Tag `json:"value"` + }{ + Type: "tag", + Tag: e.Tag, + } + return json.Marshal(marshaler) + } +} + +type EventInfoVisitor interface { + VisitMetadata(*Metadata) error + VisitTag(Tag) error +} + +func (e *EventInfo) Accept(visitor EventInfoVisitor) error { + switch e.Type { + default: + return fmt.Errorf("invalid type %s in %T", e.Type, e) + case "metadata": + return visitor.VisitMetadata(e.Metadata) + case "tag": + return visitor.VisitTag(e.Tag) + } +} + +type Metadata struct { + Id string `json:"id" url:"id"` + Data map[string]string `json:"data,omitempty" url:"data,omitempty"` + JsonString *string `json:"jsonString,omitempty" url:"jsonString,omitempty"` + + extraProperties map[string]interface{} + _rawJSON json.RawMessage +} + +func (m *Metadata) GetExtraProperties() map[string]interface{} { + return m.extraProperties +} + +func (m *Metadata) UnmarshalJSON(data []byte) error { + type unmarshaler Metadata + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *m = Metadata(value) + + extraProperties, err := core.ExtractExtraProperties(data, *m) + if err != nil { + return err + } + m.extraProperties = extraProperties + + m._rawJSON = json.RawMessage(data) + return nil +} + +func (m *Metadata) String() string { + if len(m._rawJSON) > 0 { + if value, err := core.StringifyJSON(m._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(m); err == nil { + return value + } + return fmt.Sprintf("%#v", m) +} + +type Tag = string diff --git a/seed/go-sdk/examples/no-custom-config/core/core.go b/seed/go-sdk/examples/no-custom-config/core/core.go new file mode 100644 index 00000000000..14c86c95cb0 --- /dev/null +++ b/seed/go-sdk/examples/no-custom-config/core/core.go @@ -0,0 +1,287 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "mime/multipart" + "net/http" + "net/url" + "reflect" +) + +const ( + // contentType specifies the JSON Content-Type header value. + contentType = "application/json" + contentTypeHeader = "Content-Type" +) + +// HTTPClient is an interface for a subset of the *http.Client. +type HTTPClient interface { + Do(*http.Request) (*http.Response, error) +} + +// EncodeURL encodes the given arguments into the URL, escaping +// values as needed. +func EncodeURL(urlFormat string, args ...interface{}) string { + escapedArgs := make([]interface{}, 0, len(args)) + for _, arg := range args { + escapedArgs = append(escapedArgs, url.PathEscape(fmt.Sprintf("%v", arg))) + } + return fmt.Sprintf(urlFormat, escapedArgs...) +} + +// MergeHeaders merges the given headers together, where the right +// takes precedence over the left. +func MergeHeaders(left, right http.Header) http.Header { + for key, values := range right { + if len(values) > 1 { + left[key] = values + continue + } + if value := right.Get(key); value != "" { + left.Set(key, value) + } + } + return left +} + +// WriteMultipartJSON writes the given value as a JSON part. +// This is used to serialize non-primitive multipart properties +// (i.e. lists, objects, etc). +func WriteMultipartJSON(writer *multipart.Writer, field string, value interface{}) error { + bytes, err := json.Marshal(value) + if err != nil { + return err + } + return writer.WriteField(field, string(bytes)) +} + +// APIError is a lightweight wrapper around the standard error +// interface that preserves the status code from the RPC, if any. +type APIError struct { + err error + + StatusCode int `json:"-"` +} + +// NewAPIError constructs a new API error. +func NewAPIError(statusCode int, err error) *APIError { + return &APIError{ + err: err, + StatusCode: statusCode, + } +} + +// Unwrap returns the underlying error. This also makes the error compatible +// with errors.As and errors.Is. +func (a *APIError) Unwrap() error { + if a == nil { + return nil + } + return a.err +} + +// Error returns the API error's message. +func (a *APIError) Error() string { + if a == nil || (a.err == nil && a.StatusCode == 0) { + return "" + } + if a.err == nil { + return fmt.Sprintf("%d", a.StatusCode) + } + if a.StatusCode == 0 { + return a.err.Error() + } + return fmt.Sprintf("%d: %s", a.StatusCode, a.err.Error()) +} + +// ErrorDecoder decodes *http.Response errors and returns a +// typed API error (e.g. *APIError). +type ErrorDecoder func(statusCode int, body io.Reader) error + +// Caller calls APIs and deserializes their response, if any. +type Caller struct { + client HTTPClient + retrier *Retrier +} + +// CallerParams represents the parameters used to constrcut a new *Caller. +type CallerParams struct { + Client HTTPClient + MaxAttempts uint +} + +// NewCaller returns a new *Caller backed by the given parameters. +func NewCaller(params *CallerParams) *Caller { + var httpClient HTTPClient = http.DefaultClient + if params.Client != nil { + httpClient = params.Client + } + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + return &Caller{ + client: httpClient, + retrier: NewRetrier(retryOptions...), + } +} + +// CallParams represents the parameters used to issue an API call. +type CallParams struct { + URL string + Method string + MaxAttempts uint + Headers http.Header + Client HTTPClient + Request interface{} + Response interface{} + ResponseIsOptional bool + ErrorDecoder ErrorDecoder +} + +// Call issues an API call according to the given call parameters. +func (c *Caller) Call(ctx context.Context, params *CallParams) error { + req, err := newRequest(ctx, params.URL, params.Method, params.Headers, params.Request) + if err != nil { + return err + } + + // If the call has been cancelled, don't issue the request. + if err := ctx.Err(); err != nil { + return err + } + + client := c.client + if params.Client != nil { + // Use the HTTP client scoped to the request. + client = params.Client + } + + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + + resp, err := c.retrier.Run( + client.Do, + req, + params.ErrorDecoder, + retryOptions..., + ) + if err != nil { + return err + } + + // Close the response body after we're done. + defer resp.Body.Close() + + // Check if the call was cancelled before we return the error + // associated with the call and/or unmarshal the response data. + if err := ctx.Err(); err != nil { + return err + } + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return decodeError(resp, params.ErrorDecoder) + } + + // Mutate the response parameter in-place. + if params.Response != nil { + if writer, ok := params.Response.(io.Writer); ok { + _, err = io.Copy(writer, resp.Body) + } else { + err = json.NewDecoder(resp.Body).Decode(params.Response) + } + if err != nil { + if err == io.EOF { + if params.ResponseIsOptional { + // The response is optional, so we should ignore the + // io.EOF error + return nil + } + return fmt.Errorf("expected a %T response, but the server responded with nothing", params.Response) + } + return err + } + } + + return nil +} + +// newRequest returns a new *http.Request with all of the fields +// required to issue the call. +func newRequest( + ctx context.Context, + url string, + method string, + endpointHeaders http.Header, + request interface{}, +) (*http.Request, error) { + requestBody, err := newRequestBody(request) + if err != nil { + return nil, err + } + req, err := http.NewRequestWithContext(ctx, method, url, requestBody) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + req.Header.Set(contentTypeHeader, contentType) + for name, values := range endpointHeaders { + req.Header[name] = values + } + return req, nil +} + +// newRequestBody returns a new io.Reader that represents the HTTP request body. +func newRequestBody(request interface{}) (io.Reader, error) { + var requestBody io.Reader + if !isNil(request) { + if body, ok := request.(io.Reader); ok { + requestBody = body + } else { + requestBytes, err := json.Marshal(request) + if err != nil { + return nil, err + } + requestBody = bytes.NewReader(requestBytes) + } + } + return requestBody, nil +} + +// decodeError decodes the error from the given HTTP response. Note that +// it's the caller's responsibility to close the response body. +func decodeError(response *http.Response, errorDecoder ErrorDecoder) error { + if errorDecoder != nil { + // This endpoint has custom errors, so we'll + // attempt to unmarshal the error into a structured + // type based on the status code. + return errorDecoder(response.StatusCode, response.Body) + } + // This endpoint doesn't have any custom error + // types, so we just read the body as-is, and + // put it into a normal error. + bytes, err := io.ReadAll(response.Body) + if err != nil && err != io.EOF { + return err + } + if err == io.EOF { + // The error didn't have a response body, + // so all we can do is return an error + // with the status code. + return NewAPIError(response.StatusCode, nil) + } + return NewAPIError(response.StatusCode, errors.New(string(bytes))) +} + +// isNil is used to determine if the request value is equal to nil (i.e. an interface +// value that holds a nil concrete value is itself non-nil). +func isNil(value interface{}) bool { + return value == nil || reflect.ValueOf(value).IsNil() +} diff --git a/seed/go-sdk/examples/no-custom-config/core/core_test.go b/seed/go-sdk/examples/no-custom-config/core/core_test.go new file mode 100644 index 00000000000..adf9e3112cd --- /dev/null +++ b/seed/go-sdk/examples/no-custom-config/core/core_test.go @@ -0,0 +1,303 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/http/httptest" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestCase represents a single test case. +type TestCase struct { + description string + + // Server-side assertions. + giveMethod string + giveResponseIsOptional bool + giveHeader http.Header + giveErrorDecoder ErrorDecoder + giveRequest *Request + + // Client-side assertions. + wantResponse *Response + wantError error +} + +// Request a simple request body. +type Request struct { + Id string `json:"id"` +} + +// Response a simple response body. +type Response struct { + Id string `json:"id"` +} + +// NotFoundError represents a 404. +type NotFoundError struct { + *APIError + + Message string `json:"message"` +} + +func TestCall(t *testing.T) { + tests := []*TestCase{ + { + description: "GET success", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + wantResponse: &Response{ + Id: "123", + }, + }, + { + description: "GET not found", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusNotFound), + }, + giveErrorDecoder: newTestErrorDecoder(t), + wantError: &NotFoundError{ + APIError: NewAPIError( + http.StatusNotFound, + errors.New(`{"message":"ID \"404\" not found"}`), + ), + }, + }, + { + description: "POST empty body", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: nil, + wantError: NewAPIError( + http.StatusBadRequest, + errors.New("invalid request"), + ), + }, + { + description: "POST optional response", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + giveResponseIsOptional: true, + }, + { + description: "POST API error", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusInternalServerError), + }, + wantError: NewAPIError( + http.StatusInternalServerError, + errors.New("failed to process request"), + ), + }, + } + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + var ( + server = newTestServer(t, test) + client = server.Client() + ) + caller := NewCaller( + &CallerParams{ + Client: client, + }, + ) + var response *Response + err := caller.Call( + context.Background(), + &CallParams{ + URL: server.URL, + Method: test.giveMethod, + Headers: test.giveHeader, + Request: test.giveRequest, + Response: &response, + ResponseIsOptional: test.giveResponseIsOptional, + ErrorDecoder: test.giveErrorDecoder, + }, + ) + if test.wantError != nil { + assert.EqualError(t, err, test.wantError.Error()) + return + } + require.NoError(t, err) + assert.Equal(t, test.wantResponse, response) + }) + } +} + +func TestMergeHeaders(t *testing.T) { + t.Run("both empty", func(t *testing.T) { + merged := MergeHeaders(make(http.Header), make(http.Header)) + assert.Empty(t, merged) + }) + + t.Run("empty left", func(t *testing.T) { + left := make(http.Header) + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("empty right", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.1") + + right := make(http.Header) + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("single value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.0") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) + + t.Run("multiple value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Versions", "0.0.0") + + right := make(http.Header) + right.Add("X-API-Versions", "0.0.1") + right.Add("X-API-Versions", "0.0.2") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1", "0.0.2"}, merged.Values("X-API-Versions")) + }) + + t.Run("disjoint merge", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Tenancy", "test") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"test"}, merged.Values("X-API-Tenancy")) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) +} + +// newTestServer returns a new *httptest.Server configured with the +// given test parameters. +func newTestServer(t *testing.T, tc *TestCase) *httptest.Server { + return httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.giveMethod, r.Method) + assert.Equal(t, contentType, r.Header.Get(contentTypeHeader)) + for header, value := range tc.giveHeader { + assert.Equal(t, value, r.Header.Values(header)) + } + + request := new(Request) + + bytes, err := io.ReadAll(r.Body) + if tc.giveRequest == nil { + require.Empty(t, bytes) + w.WriteHeader(http.StatusBadRequest) + _, err = w.Write([]byte("invalid request")) + require.NoError(t, err) + return + } + require.NoError(t, err) + require.NoError(t, json.Unmarshal(bytes, request)) + + switch request.Id { + case strconv.Itoa(http.StatusNotFound): + notFoundError := &NotFoundError{ + APIError: &APIError{ + StatusCode: http.StatusNotFound, + }, + Message: fmt.Sprintf("ID %q not found", request.Id), + } + bytes, err = json.Marshal(notFoundError) + require.NoError(t, err) + + w.WriteHeader(http.StatusNotFound) + _, err = w.Write(bytes) + require.NoError(t, err) + return + + case strconv.Itoa(http.StatusInternalServerError): + w.WriteHeader(http.StatusInternalServerError) + _, err = w.Write([]byte("failed to process request")) + require.NoError(t, err) + return + } + + if tc.giveResponseIsOptional { + w.WriteHeader(http.StatusOK) + return + } + + response := &Response{ + Id: request.Id, + } + bytes, err = json.Marshal(response) + require.NoError(t, err) + + _, err = w.Write(bytes) + require.NoError(t, err) + }, + ), + ) +} + +// newTestErrorDecoder returns an error decoder suitable for tests. +func newTestErrorDecoder(t *testing.T) func(int, io.Reader) error { + return func(statusCode int, body io.Reader) error { + raw, err := io.ReadAll(body) + require.NoError(t, err) + + var ( + apiError = NewAPIError(statusCode, errors.New(string(raw))) + decoder = json.NewDecoder(bytes.NewReader(raw)) + ) + if statusCode == http.StatusNotFound { + value := new(NotFoundError) + value.APIError = apiError + require.NoError(t, decoder.Decode(value)) + + return value + } + return apiError + } +} diff --git a/seed/go-sdk/examples/no-custom-config/core/extra_properties.go b/seed/go-sdk/examples/no-custom-config/core/extra_properties.go new file mode 100644 index 00000000000..a6af3e12410 --- /dev/null +++ b/seed/go-sdk/examples/no-custom-config/core/extra_properties.go @@ -0,0 +1,141 @@ +package core + +import ( + "bytes" + "encoding/json" + "fmt" + "reflect" + "strings" +) + +// MarshalJSONWithExtraProperty marshals the given value to JSON, including the extra property. +func MarshalJSONWithExtraProperty(marshaler interface{}, key string, value interface{}) ([]byte, error) { + return MarshalJSONWithExtraProperties(marshaler, map[string]interface{}{key: value}) +} + +// MarshalJSONWithExtraProperties marshals the given value to JSON, including any extra properties. +func MarshalJSONWithExtraProperties(marshaler interface{}, extraProperties map[string]interface{}) ([]byte, error) { + bytes, err := json.Marshal(marshaler) + if err != nil { + return nil, err + } + if len(extraProperties) == 0 { + return bytes, nil + } + keys, err := getKeys(marshaler) + if err != nil { + return nil, err + } + for _, key := range keys { + if _, ok := extraProperties[key]; ok { + return nil, fmt.Errorf("cannot add extra property %q because it is already defined on the type", key) + } + } + extraBytes, err := json.Marshal(extraProperties) + if err != nil { + return nil, err + } + if isEmptyJSON(bytes) { + if isEmptyJSON(extraBytes) { + return bytes, nil + } + return extraBytes, nil + } + result := bytes[:len(bytes)-1] + result = append(result, ',') + result = append(result, extraBytes[1:len(extraBytes)-1]...) + result = append(result, '}') + return result, nil +} + +// ExtractExtraProperties extracts any extra properties from the given value. +func ExtractExtraProperties(bytes []byte, value interface{}, exclude ...string) (map[string]interface{}, error) { + val := reflect.ValueOf(value) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return nil, fmt.Errorf("value must be non-nil to extract extra properties") + } + val = val.Elem() + } + if err := json.Unmarshal(bytes, &value); err != nil { + return nil, err + } + var extraProperties map[string]interface{} + if err := json.Unmarshal(bytes, &extraProperties); err != nil { + return nil, err + } + for i := 0; i < val.Type().NumField(); i++ { + key := jsonKey(val.Type().Field(i)) + if key == "" || key == "-" { + continue + } + delete(extraProperties, key) + } + for _, key := range exclude { + delete(extraProperties, key) + } + if len(extraProperties) == 0 { + return nil, nil + } + return extraProperties, nil +} + +// getKeys returns the keys associated with the given value. The value must be a +// a struct or a map with string keys. +func getKeys(value interface{}) ([]string, error) { + val := reflect.ValueOf(value) + if val.Kind() == reflect.Ptr { + val = val.Elem() + } + if !val.IsValid() { + return nil, nil + } + switch val.Kind() { + case reflect.Struct: + return getKeysForStructType(val.Type()), nil + case reflect.Map: + var keys []string + if val.Type().Key().Kind() != reflect.String { + return nil, fmt.Errorf("cannot extract keys from %T; only structs and maps with string keys are supported", value) + } + for _, key := range val.MapKeys() { + keys = append(keys, key.String()) + } + return keys, nil + default: + return nil, fmt.Errorf("cannot extract keys from %T; only structs and maps with string keys are supported", value) + } +} + +// getKeysForStructType returns all the keys associated with the given struct type, +// visiting embedded fields recursively. +func getKeysForStructType(structType reflect.Type) []string { + if structType.Kind() == reflect.Pointer { + structType = structType.Elem() + } + if structType.Kind() != reflect.Struct { + return nil + } + var keys []string + for i := 0; i < structType.NumField(); i++ { + field := structType.Field(i) + if field.Anonymous { + keys = append(keys, getKeysForStructType(field.Type)...) + continue + } + keys = append(keys, jsonKey(field)) + } + return keys +} + +// jsonKey returns the JSON key from the struct tag of the given field, +// excluding the omitempty flag (if any). +func jsonKey(field reflect.StructField) string { + return strings.TrimSuffix(field.Tag.Get("json"), ",omitempty") +} + +// isEmptyJSON returns true if the given data is empty, the empty JSON object, or +// an explicit null. +func isEmptyJSON(data []byte) bool { + return len(data) <= 2 || bytes.Equal(data, []byte("null")) +} diff --git a/seed/go-sdk/examples/no-custom-config/core/extra_properties_test.go b/seed/go-sdk/examples/no-custom-config/core/extra_properties_test.go new file mode 100644 index 00000000000..dc66fccd7f1 --- /dev/null +++ b/seed/go-sdk/examples/no-custom-config/core/extra_properties_test.go @@ -0,0 +1,228 @@ +package core + +import ( + "encoding/json" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type testMarshaler struct { + Name string `json:"name"` + BirthDate time.Time `json:"birthDate"` + CreatedAt time.Time `json:"created_at"` +} + +func (t *testMarshaler) MarshalJSON() ([]byte, error) { + type embed testMarshaler + var marshaler = struct { + embed + BirthDate string `json:"birthDate"` + CreatedAt string `json:"created_at"` + }{ + embed: embed(*t), + BirthDate: t.BirthDate.Format("2006-01-02"), + CreatedAt: t.CreatedAt.Format(time.RFC3339), + } + return MarshalJSONWithExtraProperty(marshaler, "type", "test") +} + +func TestMarshalJSONWithExtraProperties(t *testing.T) { + tests := []struct { + desc string + giveMarshaler interface{} + giveExtraProperties map[string]interface{} + wantBytes []byte + wantError string + }{ + { + desc: "invalid type", + giveMarshaler: []string{"invalid"}, + giveExtraProperties: map[string]interface{}{"key": "overwrite"}, + wantError: `cannot extract keys from []string; only structs and maps with string keys are supported`, + }, + { + desc: "invalid key type", + giveMarshaler: map[int]interface{}{42: "value"}, + giveExtraProperties: map[string]interface{}{"key": "overwrite"}, + wantError: `cannot extract keys from map[int]interface {}; only structs and maps with string keys are supported`, + }, + { + desc: "invalid map overwrite", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{"key": "overwrite"}, + wantError: `cannot add extra property "key" because it is already defined on the type`, + }, + { + desc: "invalid struct overwrite", + giveMarshaler: new(testMarshaler), + giveExtraProperties: map[string]interface{}{"birthDate": "2000-01-01"}, + wantError: `cannot add extra property "birthDate" because it is already defined on the type`, + }, + { + desc: "invalid struct overwrite embedded type", + giveMarshaler: new(testMarshaler), + giveExtraProperties: map[string]interface{}{"name": "bob"}, + wantError: `cannot add extra property "name" because it is already defined on the type`, + }, + { + desc: "nil", + giveMarshaler: nil, + giveExtraProperties: nil, + wantBytes: []byte(`null`), + }, + { + desc: "empty", + giveMarshaler: map[string]interface{}{}, + giveExtraProperties: map[string]interface{}{}, + wantBytes: []byte(`{}`), + }, + { + desc: "no extra properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{}, + wantBytes: []byte(`{"key":"value"}`), + }, + { + desc: "only extra properties", + giveMarshaler: map[string]interface{}{}, + giveExtraProperties: map[string]interface{}{"key": "value"}, + wantBytes: []byte(`{"key":"value"}`), + }, + { + desc: "single extra property", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{"extra": "property"}, + wantBytes: []byte(`{"key":"value","extra":"property"}`), + }, + { + desc: "multiple extra properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{"one": 1, "two": 2}, + wantBytes: []byte(`{"key":"value","one":1,"two":2}`), + }, + { + desc: "nested properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{ + "user": map[string]interface{}{ + "age": 42, + "name": "alice", + }, + }, + wantBytes: []byte(`{"key":"value","user":{"age":42,"name":"alice"}}`), + }, + { + desc: "multiple nested properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{ + "metadata": map[string]interface{}{ + "ip": "127.0.0.1", + }, + "user": map[string]interface{}{ + "age": 42, + "name": "alice", + }, + }, + wantBytes: []byte(`{"key":"value","metadata":{"ip":"127.0.0.1"},"user":{"age":42,"name":"alice"}}`), + }, + { + desc: "custom marshaler", + giveMarshaler: &testMarshaler{ + Name: "alice", + BirthDate: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), + CreatedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), + }, + giveExtraProperties: map[string]interface{}{ + "extra": "property", + }, + wantBytes: []byte(`{"name":"alice","birthDate":"2000-01-01","created_at":"2024-01-01T00:00:00Z","type":"test","extra":"property"}`), + }, + } + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + bytes, err := MarshalJSONWithExtraProperties(tt.giveMarshaler, tt.giveExtraProperties) + if tt.wantError != "" { + require.EqualError(t, err, tt.wantError) + assert.Nil(t, tt.wantBytes) + return + } + require.NoError(t, err) + assert.Equal(t, tt.wantBytes, bytes) + + value := make(map[string]interface{}) + require.NoError(t, json.Unmarshal(bytes, &value)) + }) + } +} + +func TestExtractExtraProperties(t *testing.T) { + t.Run("none", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := &user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice"}`), value) + require.NoError(t, err) + assert.Nil(t, extraProperties) + }) + + t.Run("non-nil pointer", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := &user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + require.NoError(t, err) + assert.Equal(t, map[string]interface{}{"age": float64(42)}, extraProperties) + }) + + t.Run("nil pointer", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + var value *user + _, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + assert.EqualError(t, err, "value must be non-nil to extract extra properties") + }) + + t.Run("non-zero value", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + require.NoError(t, err) + assert.Equal(t, map[string]interface{}{"age": float64(42)}, extraProperties) + }) + + t.Run("zero value", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + var value user + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + require.NoError(t, err) + assert.Equal(t, map[string]interface{}{"age": float64(42)}, extraProperties) + }) + + t.Run("exclude", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := &user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value, "age") + require.NoError(t, err) + assert.Nil(t, extraProperties) + }) +} diff --git a/seed/go-sdk/examples/no-custom-config/core/query.go b/seed/go-sdk/examples/no-custom-config/core/query.go new file mode 100644 index 00000000000..2670ff7feda --- /dev/null +++ b/seed/go-sdk/examples/no-custom-config/core/query.go @@ -0,0 +1,231 @@ +package core + +import ( + "encoding/base64" + "fmt" + "net/url" + "reflect" + "strings" + "time" + + "github.com/google/uuid" +) + +var ( + bytesType = reflect.TypeOf([]byte{}) + queryEncoderType = reflect.TypeOf(new(QueryEncoder)).Elem() + timeType = reflect.TypeOf(time.Time{}) + uuidType = reflect.TypeOf(uuid.UUID{}) +) + +// QueryEncoder is an interface implemented by any type that wishes to encode +// itself into URL values in a non-standard way. +type QueryEncoder interface { + EncodeQueryValues(key string, v *url.Values) error +} + +// QueryValues encodes url.Values from request objects. +// +// Note: This type is inspired by Google's query encoding library, but +// supports far less customization and is tailored to fit this SDK's use case. +// +// Ref: https://github.com/google/go-querystring +func QueryValues(v interface{}) (url.Values, error) { + values := make(url.Values) + val := reflect.ValueOf(v) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return values, nil + } + val = val.Elem() + } + + if v == nil { + return values, nil + } + + if val.Kind() != reflect.Struct { + return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind()) + } + + err := reflectValue(values, val, "") + return values, err +} + +// reflectValue populates the values parameter from the struct fields in val. +// Embedded structs are followed recursively (using the rules defined in the +// Values function documentation) breadth-first. +func reflectValue(values url.Values, val reflect.Value, scope string) error { + typ := val.Type() + for i := 0; i < typ.NumField(); i++ { + sf := typ.Field(i) + if sf.PkgPath != "" && !sf.Anonymous { + // Skip unexported fields. + continue + } + + sv := val.Field(i) + tag := sf.Tag.Get("url") + if tag == "" || tag == "-" { + continue + } + + name, opts := parseTag(tag) + if name == "" { + name = sf.Name + } + + if scope != "" { + name = scope + "[" + name + "]" + } + + if opts.Contains("omitempty") && isEmptyValue(sv) { + continue + } + + if sv.Type().Implements(queryEncoderType) { + // If sv is a nil pointer and the custom encoder is defined on a non-pointer + // method receiver, set sv to the zero value of the underlying type + if !reflect.Indirect(sv).IsValid() && sv.Type().Elem().Implements(queryEncoderType) { + sv = reflect.New(sv.Type().Elem()) + } + + m := sv.Interface().(QueryEncoder) + if err := m.EncodeQueryValues(name, &values); err != nil { + return err + } + continue + } + + // Recursively dereference pointers, but stop at nil pointers. + for sv.Kind() == reflect.Ptr { + if sv.IsNil() { + break + } + sv = sv.Elem() + } + + if sv.Type() == uuidType || sv.Type() == bytesType || sv.Type() == timeType { + values.Add(name, valueString(sv, opts, sf)) + continue + } + + if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { + if sv.Len() == 0 { + // Skip if slice or array is empty. + continue + } + for i := 0; i < sv.Len(); i++ { + value := sv.Index(i) + if isStructPointer(value) && !value.IsNil() { + if err := reflectValue(values, value.Elem(), name); err != nil { + return err + } + } else { + values.Add(name, valueString(value, opts, sf)) + } + } + continue + } + + if sv.Kind() == reflect.Struct { + if err := reflectValue(values, sv, name); err != nil { + return err + } + continue + } + + values.Add(name, valueString(sv, opts, sf)) + } + + return nil +} + +// valueString returns the string representation of a value. +func valueString(v reflect.Value, opts tagOptions, sf reflect.StructField) string { + for v.Kind() == reflect.Ptr { + if v.IsNil() { + return "" + } + v = v.Elem() + } + + if v.Type() == timeType { + t := v.Interface().(time.Time) + if format := sf.Tag.Get("format"); format == "date" { + return t.Format("2006-01-02") + } + return t.Format(time.RFC3339) + } + + if v.Type() == uuidType { + u := v.Interface().(uuid.UUID) + return u.String() + } + + if v.Type() == bytesType { + b := v.Interface().([]byte) + return base64.StdEncoding.EncodeToString(b) + } + + return fmt.Sprint(v.Interface()) +} + +// isEmptyValue checks if a value should be considered empty for the purposes +// of omitting fields with the "omitempty" option. +func isEmptyValue(v reflect.Value) bool { + type zeroable interface { + IsZero() bool + } + + if !v.IsZero() { + if z, ok := v.Interface().(zeroable); ok { + return z.IsZero() + } + } + + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + case reflect.Invalid, reflect.Complex64, reflect.Complex128, reflect.Chan, reflect.Func, reflect.Struct, reflect.UnsafePointer: + return false + } + + return false +} + +// isStructPointer returns true if the given reflect.Value is a pointer to a struct. +func isStructPointer(v reflect.Value) bool { + return v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct +} + +// tagOptions is the string following a comma in a struct field's "url" tag, or +// the empty string. It does not include the leading comma. +type tagOptions []string + +// parseTag splits a struct field's url tag into its name and comma-separated +// options. +func parseTag(tag string) (string, tagOptions) { + s := strings.Split(tag, ",") + return s[0], s[1:] +} + +// Contains checks whether the tagOptions contains the specified option. +func (o tagOptions) Contains(option string) bool { + for _, s := range o { + if s == option { + return true + } + } + return false +} diff --git a/seed/go-sdk/examples/no-custom-config/core/query_test.go b/seed/go-sdk/examples/no-custom-config/core/query_test.go new file mode 100644 index 00000000000..5498fa92aa5 --- /dev/null +++ b/seed/go-sdk/examples/no-custom-config/core/query_test.go @@ -0,0 +1,187 @@ +package core + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestQueryValues(t *testing.T) { + t.Run("empty optional", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Empty(t, values) + }) + + t.Run("empty required", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Equal(t, "required=", values.Encode()) + }) + + t.Run("allow multiple", func(t *testing.T) { + type example struct { + Values []string `json:"values" url:"values"` + } + + values, err := QueryValues( + &example{ + Values: []string{"foo", "bar", "baz"}, + }, + ) + require.NoError(t, err) + assert.Equal(t, "values=foo&values=bar&values=baz", values.Encode()) + }) + + t.Run("nested object", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + nestedValue := "nestedValue" + values, err := QueryValues( + &example{ + Required: "requiredValue", + Nested: &nested{ + Value: &nestedValue, + }, + }, + ) + require.NoError(t, err) + assert.Equal(t, "nested%5Bvalue%5D=nestedValue&required=requiredValue", values.Encode()) + }) + + t.Run("url unspecified", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("url ignored", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound" url:"-"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("datetime", func(t *testing.T) { + type example struct { + DateTime time.Time `json:"dateTime" url:"dateTime"` + } + + values, err := QueryValues( + &example{ + DateTime: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "dateTime=1994-03-16T12%3A34%3A56Z", values.Encode()) + }) + + t.Run("date", func(t *testing.T) { + type example struct { + Date time.Time `json:"date" url:"date" format:"date"` + } + + values, err := QueryValues( + &example{ + Date: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "date=1994-03-16", values.Encode()) + }) + + t.Run("optional time", func(t *testing.T) { + type example struct { + Date *time.Time `json:"date,omitempty" url:"date,omitempty" format:"date"` + } + + values, err := QueryValues( + &example{}, + ) + require.NoError(t, err) + assert.Empty(t, values.Encode()) + }) + + t.Run("omitempty with non-pointer zero value", func(t *testing.T) { + type enum string + + type example struct { + Enum enum `json:"enum,omitempty" url:"enum,omitempty"` + } + + values, err := QueryValues( + &example{}, + ) + require.NoError(t, err) + assert.Empty(t, values.Encode()) + }) + + t.Run("object array", func(t *testing.T) { + type object struct { + Key string `json:"key" url:"key"` + Value string `json:"value" url:"value"` + } + type example struct { + Objects []*object `json:"objects,omitempty" url:"objects,omitempty"` + } + + values, err := QueryValues( + &example{ + Objects: []*object{ + { + Key: "hello", + Value: "world", + }, + { + Key: "foo", + Value: "bar", + }, + }, + }, + ) + require.NoError(t, err) + assert.Equal(t, "objects%5Bkey%5D=hello&objects%5Bkey%5D=foo&objects%5Bvalue%5D=world&objects%5Bvalue%5D=bar", values.Encode()) + }) +} diff --git a/seed/go-sdk/examples/no-custom-config/core/request_option.go b/seed/go-sdk/examples/no-custom-config/core/request_option.go new file mode 100644 index 00000000000..74b1436bc82 --- /dev/null +++ b/seed/go-sdk/examples/no-custom-config/core/request_option.go @@ -0,0 +1,101 @@ +// This file was auto-generated by Fern from our API Definition. + +package core + +import ( + http "net/http" +) + +// RequestOption adapts the behavior of the client or an individual request. +type RequestOption interface { + applyRequestOptions(*RequestOptions) +} + +// RequestOptions defines all of the possible request options. +// +// This type is primarily used by the generated code and is not meant +// to be used directly; use the option package instead. +type RequestOptions struct { + BaseURL string + HTTPClient HTTPClient + HTTPHeader http.Header + MaxAttempts uint + Token string +} + +// NewRequestOptions returns a new *RequestOptions value. +// +// This function is primarily used by the generated code and is not meant +// to be used directly; use RequestOption instead. +func NewRequestOptions(opts ...RequestOption) *RequestOptions { + options := &RequestOptions{ + HTTPHeader: make(http.Header), + } + for _, opt := range opts { + opt.applyRequestOptions(options) + } + return options +} + +// ToHeader maps the configured request options into a http.Header used +// for the request(s). +func (r *RequestOptions) ToHeader() http.Header { + header := r.cloneHeader() + if r.Token != "" { + header.Set("Authorization", "Bearer "+r.Token) + } + return header +} + +func (r *RequestOptions) cloneHeader() http.Header { + headers := r.HTTPHeader.Clone() + headers.Set("X-Fern-Language", "Go") + headers.Set("X-Fern-SDK-Name", "github.com/examples/fern") + headers.Set("X-Fern-SDK-Version", "0.0.1") + return headers +} + +// BaseURLOption implements the RequestOption interface. +type BaseURLOption struct { + BaseURL string +} + +func (b *BaseURLOption) applyRequestOptions(opts *RequestOptions) { + opts.BaseURL = b.BaseURL +} + +// HTTPClientOption implements the RequestOption interface. +type HTTPClientOption struct { + HTTPClient HTTPClient +} + +func (h *HTTPClientOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPClient = h.HTTPClient +} + +// HTTPHeaderOption implements the RequestOption interface. +type HTTPHeaderOption struct { + HTTPHeader http.Header +} + +func (h *HTTPHeaderOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPHeader = h.HTTPHeader +} + +// MaxAttemptsOption implements the RequestOption interface. +type MaxAttemptsOption struct { + MaxAttempts uint +} + +func (m *MaxAttemptsOption) applyRequestOptions(opts *RequestOptions) { + opts.MaxAttempts = m.MaxAttempts +} + +// TokenOption implements the RequestOption interface. +type TokenOption struct { + Token string +} + +func (t *TokenOption) applyRequestOptions(opts *RequestOptions) { + opts.Token = t.Token +} diff --git a/seed/go-sdk/examples/no-custom-config/core/retrier.go b/seed/go-sdk/examples/no-custom-config/core/retrier.go new file mode 100644 index 00000000000..ea24916b786 --- /dev/null +++ b/seed/go-sdk/examples/no-custom-config/core/retrier.go @@ -0,0 +1,166 @@ +package core + +import ( + "crypto/rand" + "math/big" + "net/http" + "time" +) + +const ( + defaultRetryAttempts = 2 + minRetryDelay = 500 * time.Millisecond + maxRetryDelay = 5000 * time.Millisecond +) + +// RetryOption adapts the behavior the *Retrier. +type RetryOption func(*retryOptions) + +// RetryFunc is a retriable HTTP function call (i.e. *http.Client.Do). +type RetryFunc func(*http.Request) (*http.Response, error) + +// WithMaxAttempts configures the maximum number of attempts +// of the *Retrier. +func WithMaxAttempts(attempts uint) RetryOption { + return func(opts *retryOptions) { + opts.attempts = attempts + } +} + +// Retrier retries failed requests a configurable number of times with an +// exponential back-off between each retry. +type Retrier struct { + attempts uint +} + +// NewRetrier constructs a new *Retrier with the given options, if any. +func NewRetrier(opts ...RetryOption) *Retrier { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + attempts := uint(defaultRetryAttempts) + if options.attempts > 0 { + attempts = options.attempts + } + return &Retrier{ + attempts: attempts, + } +} + +// Run issues the request and, upon failure, retries the request if possible. +// +// The request will be retried as long as the request is deemed retriable and the +// number of retry attempts has not grown larger than the configured retry limit. +func (r *Retrier) Run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + opts ...RetryOption, +) (*http.Response, error) { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + maxRetryAttempts := r.attempts + if options.attempts > 0 { + maxRetryAttempts = options.attempts + } + var ( + retryAttempt uint + previousError error + ) + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt, + previousError, + ) +} + +func (r *Retrier) run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + maxRetryAttempts uint, + retryAttempt uint, + previousError error, +) (*http.Response, error) { + if retryAttempt >= maxRetryAttempts { + return nil, previousError + } + + // If the call has been cancelled, don't issue the request. + if err := request.Context().Err(); err != nil { + return nil, err + } + + response, err := fn(request) + if err != nil { + return nil, err + } + + if r.shouldRetry(response) { + defer response.Body.Close() + + delay, err := r.retryDelay(retryAttempt) + if err != nil { + return nil, err + } + + time.Sleep(delay) + + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt+1, + decodeError(response, errorDecoder), + ) + } + + return response, nil +} + +// shouldRetry returns true if the request should be retried based on the given +// response status code. +func (r *Retrier) shouldRetry(response *http.Response) bool { + return response.StatusCode == http.StatusTooManyRequests || + response.StatusCode == http.StatusRequestTimeout || + response.StatusCode == http.StatusConflict || + response.StatusCode >= http.StatusInternalServerError +} + +// retryDelay calculates the delay time in milliseconds based on the retry attempt. +func (r *Retrier) retryDelay(retryAttempt uint) (time.Duration, error) { + // Apply exponential backoff. + delay := minRetryDelay + minRetryDelay*time.Duration(retryAttempt*retryAttempt) + + // Do not allow the number to exceed maxRetryDelay. + if delay > maxRetryDelay { + delay = maxRetryDelay + } + + // Apply some itter by randomizing the value in the range of 75%-100%. + max := big.NewInt(int64(delay / 4)) + jitter, err := rand.Int(rand.Reader, max) + if err != nil { + return 0, err + } + + delay -= time.Duration(jitter.Int64()) + + // Never sleep less than the base sleep seconds. + if delay < minRetryDelay { + delay = minRetryDelay + } + + return delay, nil +} + +type retryOptions struct { + attempts uint +} diff --git a/seed/go-sdk/examples/no-custom-config/core/stringer.go b/seed/go-sdk/examples/no-custom-config/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-sdk/examples/no-custom-config/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-sdk/examples/no-custom-config/core/time.go b/seed/go-sdk/examples/no-custom-config/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-sdk/examples/no-custom-config/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-sdk/examples/no-custom-config/environments.go b/seed/go-sdk/examples/no-custom-config/environments.go new file mode 100644 index 00000000000..e46c475de38 --- /dev/null +++ b/seed/go-sdk/examples/no-custom-config/environments.go @@ -0,0 +1,15 @@ +// This file was auto-generated by Fern from our API Definition. + +package examples + +// Environments defines all of the API environments. +// These values can be used with the WithBaseURL +// RequestOption to override the client's default environment, +// if any. +var Environments = struct { + Production string + Staging string +}{ + Production: "https://production.com/api", + Staging: "https://staging.com/api", +} diff --git a/seed/go-sdk/examples/no-custom-config/errors.go b/seed/go-sdk/examples/no-custom-config/errors.go new file mode 100644 index 00000000000..e9063f8f7f3 --- /dev/null +++ b/seed/go-sdk/examples/no-custom-config/errors.go @@ -0,0 +1,31 @@ +// This file was auto-generated by Fern from our API Definition. + +package examples + +import ( + json "encoding/json" + core "github.com/examples/fern/core" +) + +type NotFoundError struct { + *core.APIError + Body string +} + +func (n *NotFoundError) UnmarshalJSON(data []byte) error { + var body string + if err := json.Unmarshal(data, &body); err != nil { + return err + } + n.StatusCode = 404 + n.Body = body + return nil +} + +func (n *NotFoundError) MarshalJSON() ([]byte, error) { + return json.Marshal(n.Body) +} + +func (n *NotFoundError) Unwrap() error { + return n.APIError +} diff --git a/seed/go-sdk/examples/no-custom-config/file/client/client.go b/seed/go-sdk/examples/no-custom-config/file/client/client.go new file mode 100644 index 00000000000..57acb00ff8b --- /dev/null +++ b/seed/go-sdk/examples/no-custom-config/file/client/client.go @@ -0,0 +1,36 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + core "github.com/examples/fern/core" + notificationclient "github.com/examples/fern/file/notification/client" + service "github.com/examples/fern/file/service" + option "github.com/examples/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header + + Notification *notificationclient.Client + Service *service.Client +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + Notification: notificationclient.NewClient(opts...), + Service: service.NewClient(opts...), + } +} diff --git a/seed/go-sdk/examples/no-custom-config/file/notification/client/client.go b/seed/go-sdk/examples/no-custom-config/file/notification/client/client.go new file mode 100644 index 00000000000..045636bb64d --- /dev/null +++ b/seed/go-sdk/examples/no-custom-config/file/notification/client/client.go @@ -0,0 +1,33 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + core "github.com/examples/fern/core" + service "github.com/examples/fern/file/notification/service" + option "github.com/examples/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header + + Service *service.Client +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + Service: service.NewClient(opts...), + } +} diff --git a/seed/go-sdk/examples/no-custom-config/file/notification/service/client.go b/seed/go-sdk/examples/no-custom-config/file/notification/service/client.go new file mode 100644 index 00000000000..b6a527618f5 --- /dev/null +++ b/seed/go-sdk/examples/no-custom-config/file/notification/service/client.go @@ -0,0 +1,66 @@ +// This file was auto-generated by Fern from our API Definition. + +package service + +import ( + context "context" + fern "github.com/examples/fern" + core "github.com/examples/fern/core" + option "github.com/examples/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + } +} + +func (c *Client) GetException( + ctx context.Context, + notificationId string, + opts ...option.RequestOption, +) (*fern.Exception, error) { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := core.EncodeURL(baseURL+"/file/notification/%v", notificationId) + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + var response *fern.Exception + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodGet, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Response: &response, + }, + ); err != nil { + return nil, err + } + return response, nil +} diff --git a/seed/go-sdk/examples/no-custom-config/file/service.go b/seed/go-sdk/examples/no-custom-config/file/service.go new file mode 100644 index 00000000000..5253d0234d9 --- /dev/null +++ b/seed/go-sdk/examples/no-custom-config/file/service.go @@ -0,0 +1,6 @@ +// This file was auto-generated by Fern from our API Definition. + +package file + +type GetFileRequest struct { +} diff --git a/seed/go-sdk/examples/no-custom-config/file/service/client.go b/seed/go-sdk/examples/no-custom-config/file/service/client.go new file mode 100644 index 00000000000..53acdae53af --- /dev/null +++ b/seed/go-sdk/examples/no-custom-config/file/service/client.go @@ -0,0 +1,94 @@ +// This file was auto-generated by Fern from our API Definition. + +package service + +import ( + bytes "bytes" + context "context" + json "encoding/json" + errors "errors" + fern "github.com/examples/fern" + core "github.com/examples/fern/core" + file "github.com/examples/fern/file" + option "github.com/examples/fern/option" + io "io" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + } +} + +// This endpoint returns a file by its name. +func (c *Client) GetFile( + ctx context.Context, + // This is a filename + filename string, + request *file.GetFileRequest, + opts ...option.RequestOption, +) (*fern.File, error) { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := core.EncodeURL(baseURL+"/file/%v", filename) + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + errorDecoder := func(statusCode int, body io.Reader) error { + raw, err := io.ReadAll(body) + if err != nil { + return err + } + apiError := core.NewAPIError(statusCode, errors.New(string(raw))) + decoder := json.NewDecoder(bytes.NewReader(raw)) + switch statusCode { + case 404: + value := new(fern.NotFoundError) + value.APIError = apiError + if err := decoder.Decode(value); err != nil { + return apiError + } + return value + } + return apiError + } + + var response *fern.File + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodGet, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Response: &response, + ErrorDecoder: errorDecoder, + }, + ); err != nil { + return nil, err + } + return response, nil +} diff --git a/seed/go-sdk/examples/no-custom-config/file/types.go b/seed/go-sdk/examples/no-custom-config/file/types.go new file mode 100644 index 00000000000..edc509f893e --- /dev/null +++ b/seed/go-sdk/examples/no-custom-config/file/types.go @@ -0,0 +1,5 @@ +// This file was auto-generated by Fern from our API Definition. + +package file + +type Filename = string diff --git a/seed/go-sdk/examples/no-custom-config/go.mod b/seed/go-sdk/examples/no-custom-config/go.mod new file mode 100644 index 00000000000..f75913fe054 --- /dev/null +++ b/seed/go-sdk/examples/no-custom-config/go.mod @@ -0,0 +1,9 @@ +module github.com/examples/fern + +go 1.13 + +require ( + github.com/google/uuid v1.4.0 + github.com/stretchr/testify v1.7.0 + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/seed/go-sdk/examples/no-custom-config/go.sum b/seed/go-sdk/examples/no-custom-config/go.sum new file mode 100644 index 00000000000..b3766d4366b --- /dev/null +++ b/seed/go-sdk/examples/no-custom-config/go.sum @@ -0,0 +1,14 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/seed/go-sdk/examples/no-custom-config/health/client/client.go b/seed/go-sdk/examples/no-custom-config/health/client/client.go new file mode 100644 index 00000000000..1fb05e0b7d5 --- /dev/null +++ b/seed/go-sdk/examples/no-custom-config/health/client/client.go @@ -0,0 +1,33 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + core "github.com/examples/fern/core" + service "github.com/examples/fern/health/service" + option "github.com/examples/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header + + Service *service.Client +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + Service: service.NewClient(opts...), + } +} diff --git a/seed/go-sdk/examples/no-custom-config/health/service/client.go b/seed/go-sdk/examples/no-custom-config/health/service/client.go new file mode 100644 index 00000000000..ffb2afb6c2c --- /dev/null +++ b/seed/go-sdk/examples/no-custom-config/health/service/client.go @@ -0,0 +1,100 @@ +// This file was auto-generated by Fern from our API Definition. + +package service + +import ( + context "context" + core "github.com/examples/fern/core" + option "github.com/examples/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + } +} + +// This endpoint checks the health of a resource. +func (c *Client) Check( + ctx context.Context, + // The id to check + id string, + opts ...option.RequestOption, +) error { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := core.EncodeURL(baseURL+"/check/%v", id) + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodGet, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + }, + ); err != nil { + return err + } + return nil +} + +// This endpoint checks the health of the service. +func (c *Client) Ping( + ctx context.Context, + opts ...option.RequestOption, +) (bool, error) { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := baseURL + "/ping" + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + var response bool + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodGet, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Response: &response, + }, + ); err != nil { + return false, err + } + return response, nil +} diff --git a/seed/go-sdk/examples/no-custom-config/option/request_option.go b/seed/go-sdk/examples/no-custom-config/option/request_option.go new file mode 100644 index 00000000000..8c95bd8179c --- /dev/null +++ b/seed/go-sdk/examples/no-custom-config/option/request_option.go @@ -0,0 +1,48 @@ +// This file was auto-generated by Fern from our API Definition. + +package option + +import ( + core "github.com/examples/fern/core" + http "net/http" +) + +// RequestOption adapts the behavior of an indivdual request. +type RequestOption = core.RequestOption + +// WithBaseURL sets the base URL, overriding the default +// environment, if any. +func WithBaseURL(baseURL string) *core.BaseURLOption { + return &core.BaseURLOption{ + BaseURL: baseURL, + } +} + +// WithHTTPClient uses the given HTTPClient to issue the request. +func WithHTTPClient(httpClient core.HTTPClient) *core.HTTPClientOption { + return &core.HTTPClientOption{ + HTTPClient: httpClient, + } +} + +// WithHTTPHeader adds the given http.Header to the request. +func WithHTTPHeader(httpHeader http.Header) *core.HTTPHeaderOption { + return &core.HTTPHeaderOption{ + // Clone the headers so they can't be modified after the option call. + HTTPHeader: httpHeader.Clone(), + } +} + +// WithMaxAttempts configures the maximum number of retry attempts. +func WithMaxAttempts(attempts uint) *core.MaxAttemptsOption { + return &core.MaxAttemptsOption{ + MaxAttempts: attempts, + } +} + +// WithToken sets the 'Authorization: Bearer ' request header. +func WithToken(token string) *core.TokenOption { + return &core.TokenOption{ + Token: token, + } +} diff --git a/seed/go-sdk/examples/no-custom-config/pointer.go b/seed/go-sdk/examples/no-custom-config/pointer.go new file mode 100644 index 00000000000..6938b531c04 --- /dev/null +++ b/seed/go-sdk/examples/no-custom-config/pointer.go @@ -0,0 +1,132 @@ +package examples + +import ( + "time" + + "github.com/google/uuid" +) + +// Bool returns a pointer to the given bool value. +func Bool(b bool) *bool { + return &b +} + +// Byte returns a pointer to the given byte value. +func Byte(b byte) *byte { + return &b +} + +// Complex64 returns a pointer to the given complex64 value. +func Complex64(c complex64) *complex64 { + return &c +} + +// Complex128 returns a pointer to the given complex128 value. +func Complex128(c complex128) *complex128 { + return &c +} + +// Float32 returns a pointer to the given float32 value. +func Float32(f float32) *float32 { + return &f +} + +// Float64 returns a pointer to the given float64 value. +func Float64(f float64) *float64 { + return &f +} + +// Int returns a pointer to the given int value. +func Int(i int) *int { + return &i +} + +// Int8 returns a pointer to the given int8 value. +func Int8(i int8) *int8 { + return &i +} + +// Int16 returns a pointer to the given int16 value. +func Int16(i int16) *int16 { + return &i +} + +// Int32 returns a pointer to the given int32 value. +func Int32(i int32) *int32 { + return &i +} + +// Int64 returns a pointer to the given int64 value. +func Int64(i int64) *int64 { + return &i +} + +// Rune returns a pointer to the given rune value. +func Rune(r rune) *rune { + return &r +} + +// String returns a pointer to the given string value. +func String(s string) *string { + return &s +} + +// Uint returns a pointer to the given uint value. +func Uint(u uint) *uint { + return &u +} + +// Uint8 returns a pointer to the given uint8 value. +func Uint8(u uint8) *uint8 { + return &u +} + +// Uint16 returns a pointer to the given uint16 value. +func Uint16(u uint16) *uint16 { + return &u +} + +// Uint32 returns a pointer to the given uint32 value. +func Uint32(u uint32) *uint32 { + return &u +} + +// Uint64 returns a pointer to the given uint64 value. +func Uint64(u uint64) *uint64 { + return &u +} + +// Uintptr returns a pointer to the given uintptr value. +func Uintptr(u uintptr) *uintptr { + return &u +} + +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + +// Time returns a pointer to the given time.Time value. +func Time(t time.Time) *time.Time { + return &t +} + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/seed/go-sdk/examples/no-custom-config/service.go b/seed/go-sdk/examples/no-custom-config/service.go new file mode 100644 index 00000000000..c04c639bfd8 --- /dev/null +++ b/seed/go-sdk/examples/no-custom-config/service.go @@ -0,0 +1,9 @@ +// This file was auto-generated by Fern from our API Definition. + +package examples + +type GetMetadataRequest struct { + XApiVersion string `json:"-" url:"-"` + Shallow *bool `json:"-" url:"shallow,omitempty"` + Tag []*string `json:"-" url:"tag,omitempty"` +} diff --git a/seed/go-sdk/examples/no-custom-config/service/client.go b/seed/go-sdk/examples/no-custom-config/service/client.go new file mode 100644 index 00000000000..ac0b8a937c7 --- /dev/null +++ b/seed/go-sdk/examples/no-custom-config/service/client.go @@ -0,0 +1,181 @@ +// This file was auto-generated by Fern from our API Definition. + +package service + +import ( + context "context" + fmt "fmt" + fern "github.com/examples/fern" + core "github.com/examples/fern/core" + option "github.com/examples/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + } +} + +func (c *Client) GetMovie( + ctx context.Context, + movieId fern.MovieId, + opts ...option.RequestOption, +) (*fern.Movie, error) { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := core.EncodeURL(baseURL+"/movie/%v", movieId) + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + var response *fern.Movie + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodGet, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Response: &response, + }, + ); err != nil { + return nil, err + } + return response, nil +} + +func (c *Client) CreateMovie( + ctx context.Context, + request *fern.Movie, + opts ...option.RequestOption, +) (fern.MovieId, error) { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := baseURL + "/movie" + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + var response fern.MovieId + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodPost, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Request: request, + Response: &response, + }, + ); err != nil { + return "", err + } + return response, nil +} + +func (c *Client) GetMetadata( + ctx context.Context, + request *fern.GetMetadataRequest, + opts ...option.RequestOption, +) (*fern.Metadata, error) { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := baseURL + "/metadata" + + queryParams, err := core.QueryValues(request) + if err != nil { + return nil, err + } + if len(queryParams) > 0 { + endpointURL += "?" + queryParams.Encode() + } + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + headers.Add("X-API-Version", fmt.Sprintf("%v", request.XApiVersion)) + + var response *fern.Metadata + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodGet, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Response: &response, + }, + ); err != nil { + return nil, err + } + return response, nil +} + +func (c *Client) GetResponse( + ctx context.Context, + opts ...option.RequestOption, +) (*fern.Response, error) { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := baseURL + "/response" + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + var response *fern.Response + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodPost, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Response: &response, + }, + ); err != nil { + return nil, err + } + return response, nil +} diff --git a/seed/go-sdk/examples/no-custom-config/snippet-templates.json b/seed/go-sdk/examples/no-custom-config/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-sdk/examples/no-custom-config/snippet.json b/seed/go-sdk/examples/no-custom-config/snippet.json new file mode 100644 index 00000000000..7b091124783 --- /dev/null +++ b/seed/go-sdk/examples/no-custom-config/snippet.json @@ -0,0 +1,103 @@ +{ + "endpoints": [ + { + "id": { + "path": "/", + "method": "POST", + "identifier_override": "endpoint_.echo" + }, + "snippet": { + "type": "go", + "client": "import (\n\tcontext \"context\"\n\tfern \"github.com/examples/fern\"\n\tfernclient \"github.com/examples/fern/client\"\n\toption \"github.com/examples/fern/option\"\n)\n\nclient := fernclient.NewClient(\n\toption.WithToken(\n\t\t\"\u003cYOUR_AUTH_TOKEN\u003e\",\n\t),\n\toption.WithBaseURL(\n\t\tfern.Environments.Production,\n\t),\n)\nresponse, err := client.Echo(\n\tcontext.TODO(),\n\t\"Hello world!\\\\n\\\\nwith\\\\n\\\\tnewlines\",\n)\n" + } + }, + { + "id": { + "path": "/check/{id}", + "method": "GET", + "identifier_override": "endpoint_health/service.check" + }, + "snippet": { + "type": "go", + "client": "import (\n\tcontext \"context\"\n\tfern \"github.com/examples/fern\"\n\tfernclient \"github.com/examples/fern/client\"\n\toption \"github.com/examples/fern/option\"\n)\n\nclient := fernclient.NewClient(\n\toption.WithToken(\n\t\t\"\u003cYOUR_AUTH_TOKEN\u003e\",\n\t),\n\toption.WithBaseURL(\n\t\tfern.Environments.Production,\n\t),\n)\nerr := client.Health.Service.Check(\n\tcontext.TODO(),\n\t\"id-2sdx82h\",\n)\n" + } + }, + { + "id": { + "path": "/file/notification/{notificationId}", + "method": "GET", + "identifier_override": "endpoint_file/notification/service.getException" + }, + "snippet": { + "type": "go", + "client": "import (\n\tcontext \"context\"\n\tfern \"github.com/examples/fern\"\n\tfernclient \"github.com/examples/fern/client\"\n\toption \"github.com/examples/fern/option\"\n)\n\nclient := fernclient.NewClient(\n\toption.WithToken(\n\t\t\"\u003cYOUR_AUTH_TOKEN\u003e\",\n\t),\n\toption.WithBaseURL(\n\t\tfern.Environments.Production,\n\t),\n)\nresponse, err := client.File.Notification.Service.GetException(\n\tcontext.TODO(),\n\t\"notification-hsy129x\",\n)\n" + } + }, + { + "id": { + "path": "/file/{filename}", + "method": "GET", + "identifier_override": "endpoint_file/service.getFile" + }, + "snippet": { + "type": "go", + "client": "import (\n\tcontext \"context\"\n\tfern \"github.com/examples/fern\"\n\tfernclient \"github.com/examples/fern/client\"\n\tfile \"github.com/examples/fern/file\"\n\toption \"github.com/examples/fern/option\"\n)\n\nclient := fernclient.NewClient(\n\toption.WithToken(\n\t\t\"\u003cYOUR_AUTH_TOKEN\u003e\",\n\t),\n\toption.WithBaseURL(\n\t\tfern.Environments.Production,\n\t),\n)\nresponse, err := client.File.Service.GetFile(\n\tcontext.TODO(),\n\t\"file.txt\",\n\t\u0026file.GetFileRequest{\n\t\tXFileApiVersion: \"0.0.2\",\n\t},\n)\n" + } + }, + { + "id": { + "path": "/metadata", + "method": "GET", + "identifier_override": "endpoint_service.getMetadata" + }, + "snippet": { + "type": "go", + "client": "import (\n\tcontext \"context\"\n\tfern \"github.com/examples/fern\"\n\tfernclient \"github.com/examples/fern/client\"\n\toption \"github.com/examples/fern/option\"\n)\n\nclient := fernclient.NewClient(\n\toption.WithToken(\n\t\t\"\u003cYOUR_AUTH_TOKEN\u003e\",\n\t),\n\toption.WithBaseURL(\n\t\tfern.Environments.Production,\n\t),\n)\nresponse, err := client.Service.GetMetadata(\n\tcontext.TODO(),\n\t\u0026fern.GetMetadataRequest{\n\t\tXApiVersion: \"0.0.1\",\n\t\tShallow: fern.Bool(\n\t\t\tfalse,\n\t\t),\n\t\tTag: []*string{\n\t\t\tfern.String(\n\t\t\t\t\"development\",\n\t\t\t),\n\t\t},\n\t},\n)\n" + } + }, + { + "id": { + "path": "/movie", + "method": "POST", + "identifier_override": "endpoint_service.createMovie" + }, + "snippet": { + "type": "go", + "client": "import (\n\tcontext \"context\"\n\tfern \"github.com/examples/fern\"\n\tfernclient \"github.com/examples/fern/client\"\n\toption \"github.com/examples/fern/option\"\n)\n\nclient := fernclient.NewClient(\n\toption.WithToken(\n\t\t\"\u003cYOUR_AUTH_TOKEN\u003e\",\n\t),\n\toption.WithBaseURL(\n\t\tfern.Environments.Production,\n\t),\n)\nresponse, err := client.Service.CreateMovie(\n\tcontext.TODO(),\n\t\u0026fern.Movie{\n\t\tId: \"movie-c06a4ad7\",\n\t\tPrequel: fern.String(\n\t\t\t\"movie-cv9b914f\",\n\t\t),\n\t\tTitle: \"The Boy and the Heron\",\n\t\tFrom: \"Hayao Miyazaki\",\n\t\tRating: 8,\n\t\tTag: \"tag-wf9as23d\",\n\t\tMetadata: map[string]interface{}{\n\t\t\t\"actors\": []interface{}{\n\t\t\t\t\"Christian Bale\",\n\t\t\t\t\"Florence Pugh\",\n\t\t\t\t\"Willem Dafoe\",\n\t\t\t},\n\t\t\t\"releaseDate\": \"2023-12-08\",\n\t\t\t\"ratings\": map[string]interface{}{\n\t\t\t\t\"imdb\": 7.6,\n\t\t\t\t\"rottenTomatoes\": 97,\n\t\t\t},\n\t\t},\n\t},\n)\n" + } + }, + { + "id": { + "path": "/movie/{movieId}", + "method": "GET", + "identifier_override": "endpoint_service.getMovie" + }, + "snippet": { + "type": "go", + "client": "import (\n\tcontext \"context\"\n\tfern \"github.com/examples/fern\"\n\tfernclient \"github.com/examples/fern/client\"\n\toption \"github.com/examples/fern/option\"\n)\n\nclient := fernclient.NewClient(\n\toption.WithToken(\n\t\t\"\u003cYOUR_AUTH_TOKEN\u003e\",\n\t),\n\toption.WithBaseURL(\n\t\tfern.Environments.Production,\n\t),\n)\nresponse, err := client.Service.GetMovie(\n\tcontext.TODO(),\n\t\"movie-c06a4ad7\",\n)\n" + } + }, + { + "id": { + "path": "/ping", + "method": "GET", + "identifier_override": "endpoint_health/service.ping" + }, + "snippet": { + "type": "go", + "client": "import (\n\tcontext \"context\"\n\tfern \"github.com/examples/fern\"\n\tfernclient \"github.com/examples/fern/client\"\n\toption \"github.com/examples/fern/option\"\n)\n\nclient := fernclient.NewClient(\n\toption.WithToken(\n\t\t\"\u003cYOUR_AUTH_TOKEN\u003e\",\n\t),\n\toption.WithBaseURL(\n\t\tfern.Environments.Production,\n\t),\n)\nresponse, err := client.Health.Service.Ping(\n\tcontext.TODO(),\n)\n" + } + }, + { + "id": { + "path": "/response", + "method": "POST", + "identifier_override": "endpoint_service.getResponse" + }, + "snippet": { + "type": "go", + "client": "import (\n\tcontext \"context\"\n\tfern \"github.com/examples/fern\"\n\tfernclient \"github.com/examples/fern/client\"\n\toption \"github.com/examples/fern/option\"\n)\n\nclient := fernclient.NewClient(\n\toption.WithToken(\n\t\t\"\u003cYOUR_AUTH_TOKEN\u003e\",\n\t),\n\toption.WithBaseURL(\n\t\tfern.Environments.Production,\n\t),\n)\nresponse, err := client.Service.GetResponse(\n\tcontext.TODO(),\n)\n" + } + } + ] +} \ No newline at end of file diff --git a/seed/go-sdk/examples/types.go b/seed/go-sdk/examples/no-custom-config/types.go similarity index 100% rename from seed/go-sdk/examples/types.go rename to seed/go-sdk/examples/no-custom-config/types.go diff --git a/seed/go-sdk/grpc-proto/.github/workflows/ci.yml b/seed/go-sdk/grpc-proto/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-sdk/grpc-proto/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-sdk/grpc-proto/.mock/fern.config.json b/seed/go-sdk/grpc-proto/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/go-sdk/grpc-proto/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/go-sdk/grpc-proto/.mock/generators.yml b/seed/go-sdk/grpc-proto/.mock/generators.yml new file mode 100644 index 00000000000..902c4f50168 --- /dev/null +++ b/seed/go-sdk/grpc-proto/.mock/generators.yml @@ -0,0 +1,5 @@ +api: + - proto: + root: proto + target: proto/user/v1/user.proto + local-generation: true \ No newline at end of file diff --git a/seed/go-sdk/grpc-proto/.mock/proto/google/api/annotations.proto b/seed/go-sdk/grpc-proto/.mock/proto/google/api/annotations.proto new file mode 100644 index 00000000000..8ff42098404 --- /dev/null +++ b/seed/go-sdk/grpc-proto/.mock/proto/google/api/annotations.proto @@ -0,0 +1,31 @@ +// Copyright 2015 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/api/http.proto"; +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "AnnotationsProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // See `HttpRule`. + HttpRule http = 72295728; +} \ No newline at end of file diff --git a/seed/go-sdk/grpc-proto/.mock/proto/google/api/field_behavior.proto b/seed/go-sdk/grpc-proto/.mock/proto/google/api/field_behavior.proto new file mode 100644 index 00000000000..128799c558d --- /dev/null +++ b/seed/go-sdk/grpc-proto/.mock/proto/google/api/field_behavior.proto @@ -0,0 +1,104 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "FieldBehaviorProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.FieldOptions { + // A designation of a specific field behavior (required, output only, etc.) + // in protobuf messages. + // + // Examples: + // + // string name = 1 [(google.api.field_behavior) = REQUIRED]; + // State state = 1 [(google.api.field_behavior) = OUTPUT_ONLY]; + // google.protobuf.Duration ttl = 1 + // [(google.api.field_behavior) = INPUT_ONLY]; + // google.protobuf.Timestamp expire_time = 1 + // [(google.api.field_behavior) = OUTPUT_ONLY, + // (google.api.field_behavior) = IMMUTABLE]; + repeated google.api.FieldBehavior field_behavior = 1052; +} + +// An indicator of the behavior of a given field (for example, that a field +// is required in requests, or given as output but ignored as input). +// This **does not** change the behavior in protocol buffers itself; it only +// denotes the behavior and may affect how API tooling handles the field. +// +// Note: This enum **may** receive new values in the future. +enum FieldBehavior { + // Conventional default for enums. Do not use this. + FIELD_BEHAVIOR_UNSPECIFIED = 0; + + // Specifically denotes a field as optional. + // While all fields in protocol buffers are optional, this may be specified + // for emphasis if appropriate. + OPTIONAL = 1; + + // Denotes a field as required. + // This indicates that the field **must** be provided as part of the request, + // and failure to do so will cause an error (usually `INVALID_ARGUMENT`). + REQUIRED = 2; + + // Denotes a field as output only. + // This indicates that the field is provided in responses, but including the + // field in a request does nothing (the server *must* ignore it and + // *must not* throw an error as a result of the field's presence). + OUTPUT_ONLY = 3; + + // Denotes a field as input only. + // This indicates that the field is provided in requests, and the + // corresponding field is not included in output. + INPUT_ONLY = 4; + + // Denotes a field as immutable. + // This indicates that the field may be set once in a request to create a + // resource, but may not be changed thereafter. + IMMUTABLE = 5; + + // Denotes that a (repeated) field is an unordered list. + // This indicates that the service may provide the elements of the list + // in any arbitrary order, rather than the order the user originally + // provided. Additionally, the list's order may or may not be stable. + UNORDERED_LIST = 6; + + // Denotes that this field returns a non-empty default value if not set. + // This indicates that if the user provides the empty value in a request, + // a non-empty value will be returned. The user will not be aware of what + // non-empty value to expect. + NON_EMPTY_DEFAULT = 7; + + // Denotes that the field in a resource (a message annotated with + // google.api.resource) is used in the resource name to uniquely identify the + // resource. For AIP-compliant APIs, this should only be applied to the + // `name` field on the resource. + // + // This behavior should not be applied to references to other resources within + // the message. + // + // The identifier field of resources often have different field behavior + // depending on the request it is embedded in (e.g. for Create methods name + // is optional and unused, while for Update methods it is required). Instead + // of method-specific annotations, only `IDENTIFIER` is required. + IDENTIFIER = 8; +} \ No newline at end of file diff --git a/seed/go-sdk/grpc-proto/.mock/proto/google/api/http.proto b/seed/go-sdk/grpc-proto/.mock/proto/google/api/http.proto new file mode 100644 index 00000000000..c8392381eb9 --- /dev/null +++ b/seed/go-sdk/grpc-proto/.mock/proto/google/api/http.proto @@ -0,0 +1,379 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "HttpProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Defines the HTTP configuration for an API service. It contains a list of +// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method +// to one or more HTTP REST API methods. +message Http { + // A list of HTTP configuration rules that apply to individual API methods. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated HttpRule rules = 1; + + // When set to true, URL path parameters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // + // The default behavior is to not decode RFC 6570 reserved characters in multi + // segment matches. + bool fully_decode_reserved_expansion = 2; +} + +// # gRPC Transcoding +// +// gRPC Transcoding is a feature for mapping between a gRPC method and one or +// more HTTP REST endpoints. It allows developers to build a single API service +// that supports both gRPC APIs and REST APIs. Many systems, including [Google +// APIs](https://github.com/googleapis/googleapis), +// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC +// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), +// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature +// and use it for large scale production services. +// +// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies +// how different portions of the gRPC request message are mapped to the URL +// path, URL query parameters, and HTTP request body. It also controls how the +// gRPC response message is mapped to the HTTP response body. `HttpRule` is +// typically specified as an `google.api.http` annotation on the gRPC method. +// +// Each mapping specifies a URL path template and an HTTP method. The path +// template may refer to one or more fields in the gRPC request message, as long +// as each field is a non-repeated field with a primitive (non-message) type. +// The path template controls how fields of the request message are mapped to +// the URL path. +// +// Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/{name=messages/*}" +// }; +// } +// } +// message GetMessageRequest { +// string name = 1; // Mapped to URL path. +// } +// message Message { +// string text = 1; // The resource content. +// } +// +// This enables an HTTP REST to gRPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` +// +// Any fields in the request message which are not bound by the path template +// automatically become HTTP query parameters if there is no HTTP request body. +// For example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get:"/v1/messages/{message_id}" +// }; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // Mapped to URL path. +// int64 revision = 2; // Mapped to URL query parameter `revision`. +// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. +// } +// +// This enables a HTTP JSON to RPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | +// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: +// "foo"))` +// +// Note that fields which are mapped to URL query parameters must have a +// primitive type or a repeated primitive type or a non-repeated message type. +// In the case of a repeated type, the parameter can be repeated in the URL +// as `...?param=A¶m=B`. In the case of a message type, each field of the +// message is mapped to a separate parameter, such as +// `...?foo.a=A&foo.b=B&foo.c=C`. +// +// For HTTP methods that allow a request body, the `body` field +// specifies the mapping. Consider a REST update method on the +// message resource collection: +// +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } +// +// The following HTTP JSON to RPC mapping is enabled, where the +// representation of the JSON in the request body is determined by +// protos JSON encoding: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" message { text: "Hi!" })` +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. This enables the following alternative definition of +// the update method: +// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } +// +// +// The following HTTP JSON to RPC mapping is enabled: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" text: "Hi!")` +// +// Note that when using `*` in the body mapping, it is not possible to +// have HTTP parameters, as all fields not bound by the path end in +// the body. This makes this option more rarely used in practice when +// defining REST APIs. The common usage of `*` is in custom methods +// which don't use the URL at all for transferring data. +// +// It is possible to define multiple HTTP methods for one RPC by using +// the `additional_bindings` option. Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } +// +// This enables the following two alternative HTTP JSON to RPC mappings: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` +// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: +// "123456")` +// +// ## Rules for HTTP mapping +// +// 1. Leaf request fields (recursive expansion nested messages in the request +// message) are classified into three categories: +// - Fields referred by the path template. They are passed via the URL path. +// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They +// are passed via the HTTP +// request body. +// - All other fields are passed via the URL query parameters, and the +// parameter name is the field path in the request message. A repeated +// field can be represented as multiple query parameters under the same +// name. +// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL +// query parameter, all fields +// are passed via URL path and HTTP request body. +// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP +// request body, all +// fields are passed via URL path and URL query parameters. +// +// ### Path template syntax +// +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +// +// The syntax `*` matches a single URL path segment. The syntax `**` matches +// zero or more URL path segments, which must be the last part of the URL path +// except the `Verb`. +// +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable +// matches a single path segment, its template may be omitted, e.g. `{var}` +// is equivalent to `{var=*}`. +// +// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` +// contains any reserved character, such characters should be percent-encoded +// before the matching. +// +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path on the client +// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The +// server side does the reverse decoding. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{var}`. +// +// If a variable contains multiple path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path on the +// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. +// The server side does the reverse decoding, except "%2F" and "%2f" are left +// unchanged. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{+var}`. +// +// ## Using gRPC API Service Configuration +// +// gRPC API Service Configuration (service config) is a configuration language +// for configuring a gRPC service to become a user-facing product. The +// service config is simply the YAML representation of the `google.api.Service` +// proto message. +// +// As an alternative to annotating your proto file, you can configure gRPC +// transcoding in your service config YAML files. You do this by specifying a +// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same +// effect as the proto annotation. This can be particularly useful if you +// have a proto that is reused in multiple services. Note that any transcoding +// specified in the service config will override any matching transcoding +// configuration in the proto. +// +// Example: +// +// http: +// rules: +// # Selects a gRPC method and applies HttpRule to it. +// - selector: example.v1.Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} +// +// ## Special notes +// +// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the +// proto to JSON conversion must follow the [proto3 +// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). +// +// While the single segment variable follows the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String +// Expansion, the multi segment variable **does not** follow RFC 6570 Section +// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding +// for multi segment variables. +// +// The path variables **must not** refer to any repeated or mapped field, +// because client libraries are not capable of handling such variable expansion. +// +// The path variables **must not** capture the leading "/" character. The reason +// is that the most common use case "{var}" does not capture the leading "/" +// character. For consistency, all path variables must share the same behavior. +// +// Repeated message fields must not be mapped to URL query parameters, because +// no client library can support such complicated mapping. +// +// If an API needs to use a JSON array for request or response body, it can map +// the request or response body to a repeated field. However, some gRPC +// Transcoding implementations may not support this feature. +message HttpRule { + // Selects a method to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax + // details. + string selector = 1; + + // Determines the URL pattern is matched by this rules. This pattern can be + // used with any of the {get|put|post|delete|patch} methods. A custom method + // can be defined using the 'custom' field. + oneof pattern { + // Maps to HTTP GET. Used for listing and getting information about + // resources. + string get = 2; + + // Maps to HTTP PUT. Used for replacing a resource. + string put = 3; + + // Maps to HTTP POST. Used for creating a resource or performing an action. + string post = 4; + + // Maps to HTTP DELETE. Used for deleting a resource. + string delete = 5; + + // Maps to HTTP PATCH. Used for updating a resource. + string patch = 6; + + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. + CustomHttpPattern custom = 8; + } + + // The name of the request field whose value is mapped to the HTTP request + // body, or `*` for mapping all request fields not captured by the path + // pattern to the HTTP body, or omitted for not having any HTTP request body. + // + // NOTE: the referred field must be present at the top-level of the request + // message type. + string body = 7; + + // Optional. The name of the response field whose value is mapped to the HTTP + // response body. When omitted, the entire response message will be used + // as the HTTP response body. + // + // NOTE: The referred field must be present at the top-level of the response + // message type. + string response_body = 12; + + // Additional HTTP bindings for the selector. Nested bindings must + // not contain an `additional_bindings` field themselves (that is, + // the nesting may only be one level deep). + repeated HttpRule additional_bindings = 11; +} + +// A custom pattern is used for defining custom HTTP verb. +message CustomHttpPattern { + // The name of this custom HTTP verb. + string kind = 1; + + // The path matched by this custom verb. + string path = 2; +} \ No newline at end of file diff --git a/seed/go-sdk/grpc-proto/.mock/proto/user/v1/user.proto b/seed/go-sdk/grpc-proto/.mock/proto/user/v1/user.proto new file mode 100644 index 00000000000..a3ad0195bd4 --- /dev/null +++ b/seed/go-sdk/grpc-proto/.mock/proto/user/v1/user.proto @@ -0,0 +1,39 @@ +syntax = "proto3"; + +package user.v1; + +import "google/api/annotations.proto"; +import "google/api/field_behavior.proto"; +import "google/protobuf/struct.proto"; + +option go_package = "user/v1"; +option csharp_namespace = "User.V1"; + +message UserModel { + string username = 1; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateRequest { + string username = 1; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateResponse { + UserModel user = 1; +} + +service User { + rpc Create(CreateRequest) returns (CreateResponse) { + option (google.api.http) = { + post: "/users" + body: "*" + }; + } +} \ No newline at end of file diff --git a/seed/go-sdk/grpc-proto/client/client.go b/seed/go-sdk/grpc-proto/client/client.go new file mode 100644 index 00000000000..a07eedb3ed6 --- /dev/null +++ b/seed/go-sdk/grpc-proto/client/client.go @@ -0,0 +1,33 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + core "github.com/grpc-proto/fern/core" + option "github.com/grpc-proto/fern/option" + user "github.com/grpc-proto/fern/user" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header + + User *user.Client +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + User: user.NewClient(opts...), + } +} diff --git a/seed/go-sdk/grpc-proto/client/client_test.go b/seed/go-sdk/grpc-proto/client/client_test.go new file mode 100644 index 00000000000..6b7d248f2bd --- /dev/null +++ b/seed/go-sdk/grpc-proto/client/client_test.go @@ -0,0 +1,45 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + option "github.com/grpc-proto/fern/option" + assert "github.com/stretchr/testify/assert" + http "net/http" + testing "testing" + time "time" +) + +func TestNewClient(t *testing.T) { + t.Run("default", func(t *testing.T) { + c := NewClient() + assert.Empty(t, c.baseURL) + }) + + t.Run("base url", func(t *testing.T) { + c := NewClient( + option.WithBaseURL("test.co"), + ) + assert.Equal(t, "test.co", c.baseURL) + }) + + t.Run("http client", func(t *testing.T) { + httpClient := &http.Client{ + Timeout: 5 * time.Second, + } + c := NewClient( + option.WithHTTPClient(httpClient), + ) + assert.Empty(t, c.baseURL) + }) + + t.Run("http header", func(t *testing.T) { + header := make(http.Header) + header.Set("X-API-Tenancy", "test") + c := NewClient( + option.WithHTTPHeader(header), + ) + assert.Empty(t, c.baseURL) + assert.Equal(t, "test", c.header.Get("X-API-Tenancy")) + }) +} diff --git a/seed/go-sdk/grpc-proto/core/core.go b/seed/go-sdk/grpc-proto/core/core.go new file mode 100644 index 00000000000..14c86c95cb0 --- /dev/null +++ b/seed/go-sdk/grpc-proto/core/core.go @@ -0,0 +1,287 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "mime/multipart" + "net/http" + "net/url" + "reflect" +) + +const ( + // contentType specifies the JSON Content-Type header value. + contentType = "application/json" + contentTypeHeader = "Content-Type" +) + +// HTTPClient is an interface for a subset of the *http.Client. +type HTTPClient interface { + Do(*http.Request) (*http.Response, error) +} + +// EncodeURL encodes the given arguments into the URL, escaping +// values as needed. +func EncodeURL(urlFormat string, args ...interface{}) string { + escapedArgs := make([]interface{}, 0, len(args)) + for _, arg := range args { + escapedArgs = append(escapedArgs, url.PathEscape(fmt.Sprintf("%v", arg))) + } + return fmt.Sprintf(urlFormat, escapedArgs...) +} + +// MergeHeaders merges the given headers together, where the right +// takes precedence over the left. +func MergeHeaders(left, right http.Header) http.Header { + for key, values := range right { + if len(values) > 1 { + left[key] = values + continue + } + if value := right.Get(key); value != "" { + left.Set(key, value) + } + } + return left +} + +// WriteMultipartJSON writes the given value as a JSON part. +// This is used to serialize non-primitive multipart properties +// (i.e. lists, objects, etc). +func WriteMultipartJSON(writer *multipart.Writer, field string, value interface{}) error { + bytes, err := json.Marshal(value) + if err != nil { + return err + } + return writer.WriteField(field, string(bytes)) +} + +// APIError is a lightweight wrapper around the standard error +// interface that preserves the status code from the RPC, if any. +type APIError struct { + err error + + StatusCode int `json:"-"` +} + +// NewAPIError constructs a new API error. +func NewAPIError(statusCode int, err error) *APIError { + return &APIError{ + err: err, + StatusCode: statusCode, + } +} + +// Unwrap returns the underlying error. This also makes the error compatible +// with errors.As and errors.Is. +func (a *APIError) Unwrap() error { + if a == nil { + return nil + } + return a.err +} + +// Error returns the API error's message. +func (a *APIError) Error() string { + if a == nil || (a.err == nil && a.StatusCode == 0) { + return "" + } + if a.err == nil { + return fmt.Sprintf("%d", a.StatusCode) + } + if a.StatusCode == 0 { + return a.err.Error() + } + return fmt.Sprintf("%d: %s", a.StatusCode, a.err.Error()) +} + +// ErrorDecoder decodes *http.Response errors and returns a +// typed API error (e.g. *APIError). +type ErrorDecoder func(statusCode int, body io.Reader) error + +// Caller calls APIs and deserializes their response, if any. +type Caller struct { + client HTTPClient + retrier *Retrier +} + +// CallerParams represents the parameters used to constrcut a new *Caller. +type CallerParams struct { + Client HTTPClient + MaxAttempts uint +} + +// NewCaller returns a new *Caller backed by the given parameters. +func NewCaller(params *CallerParams) *Caller { + var httpClient HTTPClient = http.DefaultClient + if params.Client != nil { + httpClient = params.Client + } + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + return &Caller{ + client: httpClient, + retrier: NewRetrier(retryOptions...), + } +} + +// CallParams represents the parameters used to issue an API call. +type CallParams struct { + URL string + Method string + MaxAttempts uint + Headers http.Header + Client HTTPClient + Request interface{} + Response interface{} + ResponseIsOptional bool + ErrorDecoder ErrorDecoder +} + +// Call issues an API call according to the given call parameters. +func (c *Caller) Call(ctx context.Context, params *CallParams) error { + req, err := newRequest(ctx, params.URL, params.Method, params.Headers, params.Request) + if err != nil { + return err + } + + // If the call has been cancelled, don't issue the request. + if err := ctx.Err(); err != nil { + return err + } + + client := c.client + if params.Client != nil { + // Use the HTTP client scoped to the request. + client = params.Client + } + + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + + resp, err := c.retrier.Run( + client.Do, + req, + params.ErrorDecoder, + retryOptions..., + ) + if err != nil { + return err + } + + // Close the response body after we're done. + defer resp.Body.Close() + + // Check if the call was cancelled before we return the error + // associated with the call and/or unmarshal the response data. + if err := ctx.Err(); err != nil { + return err + } + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return decodeError(resp, params.ErrorDecoder) + } + + // Mutate the response parameter in-place. + if params.Response != nil { + if writer, ok := params.Response.(io.Writer); ok { + _, err = io.Copy(writer, resp.Body) + } else { + err = json.NewDecoder(resp.Body).Decode(params.Response) + } + if err != nil { + if err == io.EOF { + if params.ResponseIsOptional { + // The response is optional, so we should ignore the + // io.EOF error + return nil + } + return fmt.Errorf("expected a %T response, but the server responded with nothing", params.Response) + } + return err + } + } + + return nil +} + +// newRequest returns a new *http.Request with all of the fields +// required to issue the call. +func newRequest( + ctx context.Context, + url string, + method string, + endpointHeaders http.Header, + request interface{}, +) (*http.Request, error) { + requestBody, err := newRequestBody(request) + if err != nil { + return nil, err + } + req, err := http.NewRequestWithContext(ctx, method, url, requestBody) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + req.Header.Set(contentTypeHeader, contentType) + for name, values := range endpointHeaders { + req.Header[name] = values + } + return req, nil +} + +// newRequestBody returns a new io.Reader that represents the HTTP request body. +func newRequestBody(request interface{}) (io.Reader, error) { + var requestBody io.Reader + if !isNil(request) { + if body, ok := request.(io.Reader); ok { + requestBody = body + } else { + requestBytes, err := json.Marshal(request) + if err != nil { + return nil, err + } + requestBody = bytes.NewReader(requestBytes) + } + } + return requestBody, nil +} + +// decodeError decodes the error from the given HTTP response. Note that +// it's the caller's responsibility to close the response body. +func decodeError(response *http.Response, errorDecoder ErrorDecoder) error { + if errorDecoder != nil { + // This endpoint has custom errors, so we'll + // attempt to unmarshal the error into a structured + // type based on the status code. + return errorDecoder(response.StatusCode, response.Body) + } + // This endpoint doesn't have any custom error + // types, so we just read the body as-is, and + // put it into a normal error. + bytes, err := io.ReadAll(response.Body) + if err != nil && err != io.EOF { + return err + } + if err == io.EOF { + // The error didn't have a response body, + // so all we can do is return an error + // with the status code. + return NewAPIError(response.StatusCode, nil) + } + return NewAPIError(response.StatusCode, errors.New(string(bytes))) +} + +// isNil is used to determine if the request value is equal to nil (i.e. an interface +// value that holds a nil concrete value is itself non-nil). +func isNil(value interface{}) bool { + return value == nil || reflect.ValueOf(value).IsNil() +} diff --git a/seed/go-sdk/grpc-proto/core/core_test.go b/seed/go-sdk/grpc-proto/core/core_test.go new file mode 100644 index 00000000000..adf9e3112cd --- /dev/null +++ b/seed/go-sdk/grpc-proto/core/core_test.go @@ -0,0 +1,303 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/http/httptest" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestCase represents a single test case. +type TestCase struct { + description string + + // Server-side assertions. + giveMethod string + giveResponseIsOptional bool + giveHeader http.Header + giveErrorDecoder ErrorDecoder + giveRequest *Request + + // Client-side assertions. + wantResponse *Response + wantError error +} + +// Request a simple request body. +type Request struct { + Id string `json:"id"` +} + +// Response a simple response body. +type Response struct { + Id string `json:"id"` +} + +// NotFoundError represents a 404. +type NotFoundError struct { + *APIError + + Message string `json:"message"` +} + +func TestCall(t *testing.T) { + tests := []*TestCase{ + { + description: "GET success", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + wantResponse: &Response{ + Id: "123", + }, + }, + { + description: "GET not found", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusNotFound), + }, + giveErrorDecoder: newTestErrorDecoder(t), + wantError: &NotFoundError{ + APIError: NewAPIError( + http.StatusNotFound, + errors.New(`{"message":"ID \"404\" not found"}`), + ), + }, + }, + { + description: "POST empty body", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: nil, + wantError: NewAPIError( + http.StatusBadRequest, + errors.New("invalid request"), + ), + }, + { + description: "POST optional response", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + giveResponseIsOptional: true, + }, + { + description: "POST API error", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusInternalServerError), + }, + wantError: NewAPIError( + http.StatusInternalServerError, + errors.New("failed to process request"), + ), + }, + } + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + var ( + server = newTestServer(t, test) + client = server.Client() + ) + caller := NewCaller( + &CallerParams{ + Client: client, + }, + ) + var response *Response + err := caller.Call( + context.Background(), + &CallParams{ + URL: server.URL, + Method: test.giveMethod, + Headers: test.giveHeader, + Request: test.giveRequest, + Response: &response, + ResponseIsOptional: test.giveResponseIsOptional, + ErrorDecoder: test.giveErrorDecoder, + }, + ) + if test.wantError != nil { + assert.EqualError(t, err, test.wantError.Error()) + return + } + require.NoError(t, err) + assert.Equal(t, test.wantResponse, response) + }) + } +} + +func TestMergeHeaders(t *testing.T) { + t.Run("both empty", func(t *testing.T) { + merged := MergeHeaders(make(http.Header), make(http.Header)) + assert.Empty(t, merged) + }) + + t.Run("empty left", func(t *testing.T) { + left := make(http.Header) + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("empty right", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.1") + + right := make(http.Header) + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("single value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.0") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) + + t.Run("multiple value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Versions", "0.0.0") + + right := make(http.Header) + right.Add("X-API-Versions", "0.0.1") + right.Add("X-API-Versions", "0.0.2") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1", "0.0.2"}, merged.Values("X-API-Versions")) + }) + + t.Run("disjoint merge", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Tenancy", "test") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"test"}, merged.Values("X-API-Tenancy")) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) +} + +// newTestServer returns a new *httptest.Server configured with the +// given test parameters. +func newTestServer(t *testing.T, tc *TestCase) *httptest.Server { + return httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.giveMethod, r.Method) + assert.Equal(t, contentType, r.Header.Get(contentTypeHeader)) + for header, value := range tc.giveHeader { + assert.Equal(t, value, r.Header.Values(header)) + } + + request := new(Request) + + bytes, err := io.ReadAll(r.Body) + if tc.giveRequest == nil { + require.Empty(t, bytes) + w.WriteHeader(http.StatusBadRequest) + _, err = w.Write([]byte("invalid request")) + require.NoError(t, err) + return + } + require.NoError(t, err) + require.NoError(t, json.Unmarshal(bytes, request)) + + switch request.Id { + case strconv.Itoa(http.StatusNotFound): + notFoundError := &NotFoundError{ + APIError: &APIError{ + StatusCode: http.StatusNotFound, + }, + Message: fmt.Sprintf("ID %q not found", request.Id), + } + bytes, err = json.Marshal(notFoundError) + require.NoError(t, err) + + w.WriteHeader(http.StatusNotFound) + _, err = w.Write(bytes) + require.NoError(t, err) + return + + case strconv.Itoa(http.StatusInternalServerError): + w.WriteHeader(http.StatusInternalServerError) + _, err = w.Write([]byte("failed to process request")) + require.NoError(t, err) + return + } + + if tc.giveResponseIsOptional { + w.WriteHeader(http.StatusOK) + return + } + + response := &Response{ + Id: request.Id, + } + bytes, err = json.Marshal(response) + require.NoError(t, err) + + _, err = w.Write(bytes) + require.NoError(t, err) + }, + ), + ) +} + +// newTestErrorDecoder returns an error decoder suitable for tests. +func newTestErrorDecoder(t *testing.T) func(int, io.Reader) error { + return func(statusCode int, body io.Reader) error { + raw, err := io.ReadAll(body) + require.NoError(t, err) + + var ( + apiError = NewAPIError(statusCode, errors.New(string(raw))) + decoder = json.NewDecoder(bytes.NewReader(raw)) + ) + if statusCode == http.StatusNotFound { + value := new(NotFoundError) + value.APIError = apiError + require.NoError(t, decoder.Decode(value)) + + return value + } + return apiError + } +} diff --git a/seed/go-sdk/grpc-proto/core/extra_properties.go b/seed/go-sdk/grpc-proto/core/extra_properties.go new file mode 100644 index 00000000000..a6af3e12410 --- /dev/null +++ b/seed/go-sdk/grpc-proto/core/extra_properties.go @@ -0,0 +1,141 @@ +package core + +import ( + "bytes" + "encoding/json" + "fmt" + "reflect" + "strings" +) + +// MarshalJSONWithExtraProperty marshals the given value to JSON, including the extra property. +func MarshalJSONWithExtraProperty(marshaler interface{}, key string, value interface{}) ([]byte, error) { + return MarshalJSONWithExtraProperties(marshaler, map[string]interface{}{key: value}) +} + +// MarshalJSONWithExtraProperties marshals the given value to JSON, including any extra properties. +func MarshalJSONWithExtraProperties(marshaler interface{}, extraProperties map[string]interface{}) ([]byte, error) { + bytes, err := json.Marshal(marshaler) + if err != nil { + return nil, err + } + if len(extraProperties) == 0 { + return bytes, nil + } + keys, err := getKeys(marshaler) + if err != nil { + return nil, err + } + for _, key := range keys { + if _, ok := extraProperties[key]; ok { + return nil, fmt.Errorf("cannot add extra property %q because it is already defined on the type", key) + } + } + extraBytes, err := json.Marshal(extraProperties) + if err != nil { + return nil, err + } + if isEmptyJSON(bytes) { + if isEmptyJSON(extraBytes) { + return bytes, nil + } + return extraBytes, nil + } + result := bytes[:len(bytes)-1] + result = append(result, ',') + result = append(result, extraBytes[1:len(extraBytes)-1]...) + result = append(result, '}') + return result, nil +} + +// ExtractExtraProperties extracts any extra properties from the given value. +func ExtractExtraProperties(bytes []byte, value interface{}, exclude ...string) (map[string]interface{}, error) { + val := reflect.ValueOf(value) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return nil, fmt.Errorf("value must be non-nil to extract extra properties") + } + val = val.Elem() + } + if err := json.Unmarshal(bytes, &value); err != nil { + return nil, err + } + var extraProperties map[string]interface{} + if err := json.Unmarshal(bytes, &extraProperties); err != nil { + return nil, err + } + for i := 0; i < val.Type().NumField(); i++ { + key := jsonKey(val.Type().Field(i)) + if key == "" || key == "-" { + continue + } + delete(extraProperties, key) + } + for _, key := range exclude { + delete(extraProperties, key) + } + if len(extraProperties) == 0 { + return nil, nil + } + return extraProperties, nil +} + +// getKeys returns the keys associated with the given value. The value must be a +// a struct or a map with string keys. +func getKeys(value interface{}) ([]string, error) { + val := reflect.ValueOf(value) + if val.Kind() == reflect.Ptr { + val = val.Elem() + } + if !val.IsValid() { + return nil, nil + } + switch val.Kind() { + case reflect.Struct: + return getKeysForStructType(val.Type()), nil + case reflect.Map: + var keys []string + if val.Type().Key().Kind() != reflect.String { + return nil, fmt.Errorf("cannot extract keys from %T; only structs and maps with string keys are supported", value) + } + for _, key := range val.MapKeys() { + keys = append(keys, key.String()) + } + return keys, nil + default: + return nil, fmt.Errorf("cannot extract keys from %T; only structs and maps with string keys are supported", value) + } +} + +// getKeysForStructType returns all the keys associated with the given struct type, +// visiting embedded fields recursively. +func getKeysForStructType(structType reflect.Type) []string { + if structType.Kind() == reflect.Pointer { + structType = structType.Elem() + } + if structType.Kind() != reflect.Struct { + return nil + } + var keys []string + for i := 0; i < structType.NumField(); i++ { + field := structType.Field(i) + if field.Anonymous { + keys = append(keys, getKeysForStructType(field.Type)...) + continue + } + keys = append(keys, jsonKey(field)) + } + return keys +} + +// jsonKey returns the JSON key from the struct tag of the given field, +// excluding the omitempty flag (if any). +func jsonKey(field reflect.StructField) string { + return strings.TrimSuffix(field.Tag.Get("json"), ",omitempty") +} + +// isEmptyJSON returns true if the given data is empty, the empty JSON object, or +// an explicit null. +func isEmptyJSON(data []byte) bool { + return len(data) <= 2 || bytes.Equal(data, []byte("null")) +} diff --git a/seed/go-sdk/grpc-proto/core/extra_properties_test.go b/seed/go-sdk/grpc-proto/core/extra_properties_test.go new file mode 100644 index 00000000000..dc66fccd7f1 --- /dev/null +++ b/seed/go-sdk/grpc-proto/core/extra_properties_test.go @@ -0,0 +1,228 @@ +package core + +import ( + "encoding/json" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type testMarshaler struct { + Name string `json:"name"` + BirthDate time.Time `json:"birthDate"` + CreatedAt time.Time `json:"created_at"` +} + +func (t *testMarshaler) MarshalJSON() ([]byte, error) { + type embed testMarshaler + var marshaler = struct { + embed + BirthDate string `json:"birthDate"` + CreatedAt string `json:"created_at"` + }{ + embed: embed(*t), + BirthDate: t.BirthDate.Format("2006-01-02"), + CreatedAt: t.CreatedAt.Format(time.RFC3339), + } + return MarshalJSONWithExtraProperty(marshaler, "type", "test") +} + +func TestMarshalJSONWithExtraProperties(t *testing.T) { + tests := []struct { + desc string + giveMarshaler interface{} + giveExtraProperties map[string]interface{} + wantBytes []byte + wantError string + }{ + { + desc: "invalid type", + giveMarshaler: []string{"invalid"}, + giveExtraProperties: map[string]interface{}{"key": "overwrite"}, + wantError: `cannot extract keys from []string; only structs and maps with string keys are supported`, + }, + { + desc: "invalid key type", + giveMarshaler: map[int]interface{}{42: "value"}, + giveExtraProperties: map[string]interface{}{"key": "overwrite"}, + wantError: `cannot extract keys from map[int]interface {}; only structs and maps with string keys are supported`, + }, + { + desc: "invalid map overwrite", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{"key": "overwrite"}, + wantError: `cannot add extra property "key" because it is already defined on the type`, + }, + { + desc: "invalid struct overwrite", + giveMarshaler: new(testMarshaler), + giveExtraProperties: map[string]interface{}{"birthDate": "2000-01-01"}, + wantError: `cannot add extra property "birthDate" because it is already defined on the type`, + }, + { + desc: "invalid struct overwrite embedded type", + giveMarshaler: new(testMarshaler), + giveExtraProperties: map[string]interface{}{"name": "bob"}, + wantError: `cannot add extra property "name" because it is already defined on the type`, + }, + { + desc: "nil", + giveMarshaler: nil, + giveExtraProperties: nil, + wantBytes: []byte(`null`), + }, + { + desc: "empty", + giveMarshaler: map[string]interface{}{}, + giveExtraProperties: map[string]interface{}{}, + wantBytes: []byte(`{}`), + }, + { + desc: "no extra properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{}, + wantBytes: []byte(`{"key":"value"}`), + }, + { + desc: "only extra properties", + giveMarshaler: map[string]interface{}{}, + giveExtraProperties: map[string]interface{}{"key": "value"}, + wantBytes: []byte(`{"key":"value"}`), + }, + { + desc: "single extra property", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{"extra": "property"}, + wantBytes: []byte(`{"key":"value","extra":"property"}`), + }, + { + desc: "multiple extra properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{"one": 1, "two": 2}, + wantBytes: []byte(`{"key":"value","one":1,"two":2}`), + }, + { + desc: "nested properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{ + "user": map[string]interface{}{ + "age": 42, + "name": "alice", + }, + }, + wantBytes: []byte(`{"key":"value","user":{"age":42,"name":"alice"}}`), + }, + { + desc: "multiple nested properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{ + "metadata": map[string]interface{}{ + "ip": "127.0.0.1", + }, + "user": map[string]interface{}{ + "age": 42, + "name": "alice", + }, + }, + wantBytes: []byte(`{"key":"value","metadata":{"ip":"127.0.0.1"},"user":{"age":42,"name":"alice"}}`), + }, + { + desc: "custom marshaler", + giveMarshaler: &testMarshaler{ + Name: "alice", + BirthDate: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), + CreatedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), + }, + giveExtraProperties: map[string]interface{}{ + "extra": "property", + }, + wantBytes: []byte(`{"name":"alice","birthDate":"2000-01-01","created_at":"2024-01-01T00:00:00Z","type":"test","extra":"property"}`), + }, + } + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + bytes, err := MarshalJSONWithExtraProperties(tt.giveMarshaler, tt.giveExtraProperties) + if tt.wantError != "" { + require.EqualError(t, err, tt.wantError) + assert.Nil(t, tt.wantBytes) + return + } + require.NoError(t, err) + assert.Equal(t, tt.wantBytes, bytes) + + value := make(map[string]interface{}) + require.NoError(t, json.Unmarshal(bytes, &value)) + }) + } +} + +func TestExtractExtraProperties(t *testing.T) { + t.Run("none", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := &user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice"}`), value) + require.NoError(t, err) + assert.Nil(t, extraProperties) + }) + + t.Run("non-nil pointer", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := &user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + require.NoError(t, err) + assert.Equal(t, map[string]interface{}{"age": float64(42)}, extraProperties) + }) + + t.Run("nil pointer", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + var value *user + _, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + assert.EqualError(t, err, "value must be non-nil to extract extra properties") + }) + + t.Run("non-zero value", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + require.NoError(t, err) + assert.Equal(t, map[string]interface{}{"age": float64(42)}, extraProperties) + }) + + t.Run("zero value", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + var value user + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + require.NoError(t, err) + assert.Equal(t, map[string]interface{}{"age": float64(42)}, extraProperties) + }) + + t.Run("exclude", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := &user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value, "age") + require.NoError(t, err) + assert.Nil(t, extraProperties) + }) +} diff --git a/seed/go-sdk/grpc-proto/core/query.go b/seed/go-sdk/grpc-proto/core/query.go new file mode 100644 index 00000000000..2670ff7feda --- /dev/null +++ b/seed/go-sdk/grpc-proto/core/query.go @@ -0,0 +1,231 @@ +package core + +import ( + "encoding/base64" + "fmt" + "net/url" + "reflect" + "strings" + "time" + + "github.com/google/uuid" +) + +var ( + bytesType = reflect.TypeOf([]byte{}) + queryEncoderType = reflect.TypeOf(new(QueryEncoder)).Elem() + timeType = reflect.TypeOf(time.Time{}) + uuidType = reflect.TypeOf(uuid.UUID{}) +) + +// QueryEncoder is an interface implemented by any type that wishes to encode +// itself into URL values in a non-standard way. +type QueryEncoder interface { + EncodeQueryValues(key string, v *url.Values) error +} + +// QueryValues encodes url.Values from request objects. +// +// Note: This type is inspired by Google's query encoding library, but +// supports far less customization and is tailored to fit this SDK's use case. +// +// Ref: https://github.com/google/go-querystring +func QueryValues(v interface{}) (url.Values, error) { + values := make(url.Values) + val := reflect.ValueOf(v) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return values, nil + } + val = val.Elem() + } + + if v == nil { + return values, nil + } + + if val.Kind() != reflect.Struct { + return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind()) + } + + err := reflectValue(values, val, "") + return values, err +} + +// reflectValue populates the values parameter from the struct fields in val. +// Embedded structs are followed recursively (using the rules defined in the +// Values function documentation) breadth-first. +func reflectValue(values url.Values, val reflect.Value, scope string) error { + typ := val.Type() + for i := 0; i < typ.NumField(); i++ { + sf := typ.Field(i) + if sf.PkgPath != "" && !sf.Anonymous { + // Skip unexported fields. + continue + } + + sv := val.Field(i) + tag := sf.Tag.Get("url") + if tag == "" || tag == "-" { + continue + } + + name, opts := parseTag(tag) + if name == "" { + name = sf.Name + } + + if scope != "" { + name = scope + "[" + name + "]" + } + + if opts.Contains("omitempty") && isEmptyValue(sv) { + continue + } + + if sv.Type().Implements(queryEncoderType) { + // If sv is a nil pointer and the custom encoder is defined on a non-pointer + // method receiver, set sv to the zero value of the underlying type + if !reflect.Indirect(sv).IsValid() && sv.Type().Elem().Implements(queryEncoderType) { + sv = reflect.New(sv.Type().Elem()) + } + + m := sv.Interface().(QueryEncoder) + if err := m.EncodeQueryValues(name, &values); err != nil { + return err + } + continue + } + + // Recursively dereference pointers, but stop at nil pointers. + for sv.Kind() == reflect.Ptr { + if sv.IsNil() { + break + } + sv = sv.Elem() + } + + if sv.Type() == uuidType || sv.Type() == bytesType || sv.Type() == timeType { + values.Add(name, valueString(sv, opts, sf)) + continue + } + + if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { + if sv.Len() == 0 { + // Skip if slice or array is empty. + continue + } + for i := 0; i < sv.Len(); i++ { + value := sv.Index(i) + if isStructPointer(value) && !value.IsNil() { + if err := reflectValue(values, value.Elem(), name); err != nil { + return err + } + } else { + values.Add(name, valueString(value, opts, sf)) + } + } + continue + } + + if sv.Kind() == reflect.Struct { + if err := reflectValue(values, sv, name); err != nil { + return err + } + continue + } + + values.Add(name, valueString(sv, opts, sf)) + } + + return nil +} + +// valueString returns the string representation of a value. +func valueString(v reflect.Value, opts tagOptions, sf reflect.StructField) string { + for v.Kind() == reflect.Ptr { + if v.IsNil() { + return "" + } + v = v.Elem() + } + + if v.Type() == timeType { + t := v.Interface().(time.Time) + if format := sf.Tag.Get("format"); format == "date" { + return t.Format("2006-01-02") + } + return t.Format(time.RFC3339) + } + + if v.Type() == uuidType { + u := v.Interface().(uuid.UUID) + return u.String() + } + + if v.Type() == bytesType { + b := v.Interface().([]byte) + return base64.StdEncoding.EncodeToString(b) + } + + return fmt.Sprint(v.Interface()) +} + +// isEmptyValue checks if a value should be considered empty for the purposes +// of omitting fields with the "omitempty" option. +func isEmptyValue(v reflect.Value) bool { + type zeroable interface { + IsZero() bool + } + + if !v.IsZero() { + if z, ok := v.Interface().(zeroable); ok { + return z.IsZero() + } + } + + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + case reflect.Invalid, reflect.Complex64, reflect.Complex128, reflect.Chan, reflect.Func, reflect.Struct, reflect.UnsafePointer: + return false + } + + return false +} + +// isStructPointer returns true if the given reflect.Value is a pointer to a struct. +func isStructPointer(v reflect.Value) bool { + return v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct +} + +// tagOptions is the string following a comma in a struct field's "url" tag, or +// the empty string. It does not include the leading comma. +type tagOptions []string + +// parseTag splits a struct field's url tag into its name and comma-separated +// options. +func parseTag(tag string) (string, tagOptions) { + s := strings.Split(tag, ",") + return s[0], s[1:] +} + +// Contains checks whether the tagOptions contains the specified option. +func (o tagOptions) Contains(option string) bool { + for _, s := range o { + if s == option { + return true + } + } + return false +} diff --git a/seed/go-sdk/grpc-proto/core/query_test.go b/seed/go-sdk/grpc-proto/core/query_test.go new file mode 100644 index 00000000000..5498fa92aa5 --- /dev/null +++ b/seed/go-sdk/grpc-proto/core/query_test.go @@ -0,0 +1,187 @@ +package core + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestQueryValues(t *testing.T) { + t.Run("empty optional", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Empty(t, values) + }) + + t.Run("empty required", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Equal(t, "required=", values.Encode()) + }) + + t.Run("allow multiple", func(t *testing.T) { + type example struct { + Values []string `json:"values" url:"values"` + } + + values, err := QueryValues( + &example{ + Values: []string{"foo", "bar", "baz"}, + }, + ) + require.NoError(t, err) + assert.Equal(t, "values=foo&values=bar&values=baz", values.Encode()) + }) + + t.Run("nested object", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + nestedValue := "nestedValue" + values, err := QueryValues( + &example{ + Required: "requiredValue", + Nested: &nested{ + Value: &nestedValue, + }, + }, + ) + require.NoError(t, err) + assert.Equal(t, "nested%5Bvalue%5D=nestedValue&required=requiredValue", values.Encode()) + }) + + t.Run("url unspecified", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("url ignored", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound" url:"-"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("datetime", func(t *testing.T) { + type example struct { + DateTime time.Time `json:"dateTime" url:"dateTime"` + } + + values, err := QueryValues( + &example{ + DateTime: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "dateTime=1994-03-16T12%3A34%3A56Z", values.Encode()) + }) + + t.Run("date", func(t *testing.T) { + type example struct { + Date time.Time `json:"date" url:"date" format:"date"` + } + + values, err := QueryValues( + &example{ + Date: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "date=1994-03-16", values.Encode()) + }) + + t.Run("optional time", func(t *testing.T) { + type example struct { + Date *time.Time `json:"date,omitempty" url:"date,omitempty" format:"date"` + } + + values, err := QueryValues( + &example{}, + ) + require.NoError(t, err) + assert.Empty(t, values.Encode()) + }) + + t.Run("omitempty with non-pointer zero value", func(t *testing.T) { + type enum string + + type example struct { + Enum enum `json:"enum,omitempty" url:"enum,omitempty"` + } + + values, err := QueryValues( + &example{}, + ) + require.NoError(t, err) + assert.Empty(t, values.Encode()) + }) + + t.Run("object array", func(t *testing.T) { + type object struct { + Key string `json:"key" url:"key"` + Value string `json:"value" url:"value"` + } + type example struct { + Objects []*object `json:"objects,omitempty" url:"objects,omitempty"` + } + + values, err := QueryValues( + &example{ + Objects: []*object{ + { + Key: "hello", + Value: "world", + }, + { + Key: "foo", + Value: "bar", + }, + }, + }, + ) + require.NoError(t, err) + assert.Equal(t, "objects%5Bkey%5D=hello&objects%5Bkey%5D=foo&objects%5Bvalue%5D=world&objects%5Bvalue%5D=bar", values.Encode()) + }) +} diff --git a/seed/go-sdk/grpc-proto/core/request_option.go b/seed/go-sdk/grpc-proto/core/request_option.go new file mode 100644 index 00000000000..6edddb1d548 --- /dev/null +++ b/seed/go-sdk/grpc-proto/core/request_option.go @@ -0,0 +1,85 @@ +// This file was auto-generated by Fern from our API Definition. + +package core + +import ( + http "net/http" +) + +// RequestOption adapts the behavior of the client or an individual request. +type RequestOption interface { + applyRequestOptions(*RequestOptions) +} + +// RequestOptions defines all of the possible request options. +// +// This type is primarily used by the generated code and is not meant +// to be used directly; use the option package instead. +type RequestOptions struct { + BaseURL string + HTTPClient HTTPClient + HTTPHeader http.Header + MaxAttempts uint +} + +// NewRequestOptions returns a new *RequestOptions value. +// +// This function is primarily used by the generated code and is not meant +// to be used directly; use RequestOption instead. +func NewRequestOptions(opts ...RequestOption) *RequestOptions { + options := &RequestOptions{ + HTTPHeader: make(http.Header), + } + for _, opt := range opts { + opt.applyRequestOptions(options) + } + return options +} + +// ToHeader maps the configured request options into a http.Header used +// for the request(s). +func (r *RequestOptions) ToHeader() http.Header { return r.cloneHeader() } + +func (r *RequestOptions) cloneHeader() http.Header { + headers := r.HTTPHeader.Clone() + headers.Set("X-Fern-Language", "Go") + headers.Set("X-Fern-SDK-Name", "github.com/grpc-proto/fern") + headers.Set("X-Fern-SDK-Version", "0.0.1") + return headers +} + +// BaseURLOption implements the RequestOption interface. +type BaseURLOption struct { + BaseURL string +} + +func (b *BaseURLOption) applyRequestOptions(opts *RequestOptions) { + opts.BaseURL = b.BaseURL +} + +// HTTPClientOption implements the RequestOption interface. +type HTTPClientOption struct { + HTTPClient HTTPClient +} + +func (h *HTTPClientOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPClient = h.HTTPClient +} + +// HTTPHeaderOption implements the RequestOption interface. +type HTTPHeaderOption struct { + HTTPHeader http.Header +} + +func (h *HTTPHeaderOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPHeader = h.HTTPHeader +} + +// MaxAttemptsOption implements the RequestOption interface. +type MaxAttemptsOption struct { + MaxAttempts uint +} + +func (m *MaxAttemptsOption) applyRequestOptions(opts *RequestOptions) { + opts.MaxAttempts = m.MaxAttempts +} diff --git a/seed/go-sdk/grpc-proto/core/retrier.go b/seed/go-sdk/grpc-proto/core/retrier.go new file mode 100644 index 00000000000..ea24916b786 --- /dev/null +++ b/seed/go-sdk/grpc-proto/core/retrier.go @@ -0,0 +1,166 @@ +package core + +import ( + "crypto/rand" + "math/big" + "net/http" + "time" +) + +const ( + defaultRetryAttempts = 2 + minRetryDelay = 500 * time.Millisecond + maxRetryDelay = 5000 * time.Millisecond +) + +// RetryOption adapts the behavior the *Retrier. +type RetryOption func(*retryOptions) + +// RetryFunc is a retriable HTTP function call (i.e. *http.Client.Do). +type RetryFunc func(*http.Request) (*http.Response, error) + +// WithMaxAttempts configures the maximum number of attempts +// of the *Retrier. +func WithMaxAttempts(attempts uint) RetryOption { + return func(opts *retryOptions) { + opts.attempts = attempts + } +} + +// Retrier retries failed requests a configurable number of times with an +// exponential back-off between each retry. +type Retrier struct { + attempts uint +} + +// NewRetrier constructs a new *Retrier with the given options, if any. +func NewRetrier(opts ...RetryOption) *Retrier { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + attempts := uint(defaultRetryAttempts) + if options.attempts > 0 { + attempts = options.attempts + } + return &Retrier{ + attempts: attempts, + } +} + +// Run issues the request and, upon failure, retries the request if possible. +// +// The request will be retried as long as the request is deemed retriable and the +// number of retry attempts has not grown larger than the configured retry limit. +func (r *Retrier) Run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + opts ...RetryOption, +) (*http.Response, error) { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + maxRetryAttempts := r.attempts + if options.attempts > 0 { + maxRetryAttempts = options.attempts + } + var ( + retryAttempt uint + previousError error + ) + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt, + previousError, + ) +} + +func (r *Retrier) run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + maxRetryAttempts uint, + retryAttempt uint, + previousError error, +) (*http.Response, error) { + if retryAttempt >= maxRetryAttempts { + return nil, previousError + } + + // If the call has been cancelled, don't issue the request. + if err := request.Context().Err(); err != nil { + return nil, err + } + + response, err := fn(request) + if err != nil { + return nil, err + } + + if r.shouldRetry(response) { + defer response.Body.Close() + + delay, err := r.retryDelay(retryAttempt) + if err != nil { + return nil, err + } + + time.Sleep(delay) + + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt+1, + decodeError(response, errorDecoder), + ) + } + + return response, nil +} + +// shouldRetry returns true if the request should be retried based on the given +// response status code. +func (r *Retrier) shouldRetry(response *http.Response) bool { + return response.StatusCode == http.StatusTooManyRequests || + response.StatusCode == http.StatusRequestTimeout || + response.StatusCode == http.StatusConflict || + response.StatusCode >= http.StatusInternalServerError +} + +// retryDelay calculates the delay time in milliseconds based on the retry attempt. +func (r *Retrier) retryDelay(retryAttempt uint) (time.Duration, error) { + // Apply exponential backoff. + delay := minRetryDelay + minRetryDelay*time.Duration(retryAttempt*retryAttempt) + + // Do not allow the number to exceed maxRetryDelay. + if delay > maxRetryDelay { + delay = maxRetryDelay + } + + // Apply some itter by randomizing the value in the range of 75%-100%. + max := big.NewInt(int64(delay / 4)) + jitter, err := rand.Int(rand.Reader, max) + if err != nil { + return 0, err + } + + delay -= time.Duration(jitter.Int64()) + + // Never sleep less than the base sleep seconds. + if delay < minRetryDelay { + delay = minRetryDelay + } + + return delay, nil +} + +type retryOptions struct { + attempts uint +} diff --git a/seed/go-sdk/grpc-proto/core/stringer.go b/seed/go-sdk/grpc-proto/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-sdk/grpc-proto/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-sdk/grpc-proto/core/time.go b/seed/go-sdk/grpc-proto/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-sdk/grpc-proto/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-sdk/grpc-proto/go.mod b/seed/go-sdk/grpc-proto/go.mod new file mode 100644 index 00000000000..77baa44e7b3 --- /dev/null +++ b/seed/go-sdk/grpc-proto/go.mod @@ -0,0 +1,9 @@ +module github.com/grpc-proto/fern + +go 1.13 + +require ( + github.com/google/uuid v1.4.0 + github.com/stretchr/testify v1.7.0 + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/seed/go-sdk/grpc-proto/go.sum b/seed/go-sdk/grpc-proto/go.sum new file mode 100644 index 00000000000..b3766d4366b --- /dev/null +++ b/seed/go-sdk/grpc-proto/go.sum @@ -0,0 +1,14 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/seed/go-sdk/grpc-proto/option/request_option.go b/seed/go-sdk/grpc-proto/option/request_option.go new file mode 100644 index 00000000000..7752c99c501 --- /dev/null +++ b/seed/go-sdk/grpc-proto/option/request_option.go @@ -0,0 +1,41 @@ +// This file was auto-generated by Fern from our API Definition. + +package option + +import ( + core "github.com/grpc-proto/fern/core" + http "net/http" +) + +// RequestOption adapts the behavior of an indivdual request. +type RequestOption = core.RequestOption + +// WithBaseURL sets the base URL, overriding the default +// environment, if any. +func WithBaseURL(baseURL string) *core.BaseURLOption { + return &core.BaseURLOption{ + BaseURL: baseURL, + } +} + +// WithHTTPClient uses the given HTTPClient to issue the request. +func WithHTTPClient(httpClient core.HTTPClient) *core.HTTPClientOption { + return &core.HTTPClientOption{ + HTTPClient: httpClient, + } +} + +// WithHTTPHeader adds the given http.Header to the request. +func WithHTTPHeader(httpHeader http.Header) *core.HTTPHeaderOption { + return &core.HTTPHeaderOption{ + // Clone the headers so they can't be modified after the option call. + HTTPHeader: httpHeader.Clone(), + } +} + +// WithMaxAttempts configures the maximum number of retry attempts. +func WithMaxAttempts(attempts uint) *core.MaxAttemptsOption { + return &core.MaxAttemptsOption{ + MaxAttempts: attempts, + } +} diff --git a/seed/go-sdk/grpc-proto/pointer.go b/seed/go-sdk/grpc-proto/pointer.go new file mode 100644 index 00000000000..faaf462e6d0 --- /dev/null +++ b/seed/go-sdk/grpc-proto/pointer.go @@ -0,0 +1,132 @@ +package api + +import ( + "time" + + "github.com/google/uuid" +) + +// Bool returns a pointer to the given bool value. +func Bool(b bool) *bool { + return &b +} + +// Byte returns a pointer to the given byte value. +func Byte(b byte) *byte { + return &b +} + +// Complex64 returns a pointer to the given complex64 value. +func Complex64(c complex64) *complex64 { + return &c +} + +// Complex128 returns a pointer to the given complex128 value. +func Complex128(c complex128) *complex128 { + return &c +} + +// Float32 returns a pointer to the given float32 value. +func Float32(f float32) *float32 { + return &f +} + +// Float64 returns a pointer to the given float64 value. +func Float64(f float64) *float64 { + return &f +} + +// Int returns a pointer to the given int value. +func Int(i int) *int { + return &i +} + +// Int8 returns a pointer to the given int8 value. +func Int8(i int8) *int8 { + return &i +} + +// Int16 returns a pointer to the given int16 value. +func Int16(i int16) *int16 { + return &i +} + +// Int32 returns a pointer to the given int32 value. +func Int32(i int32) *int32 { + return &i +} + +// Int64 returns a pointer to the given int64 value. +func Int64(i int64) *int64 { + return &i +} + +// Rune returns a pointer to the given rune value. +func Rune(r rune) *rune { + return &r +} + +// String returns a pointer to the given string value. +func String(s string) *string { + return &s +} + +// Uint returns a pointer to the given uint value. +func Uint(u uint) *uint { + return &u +} + +// Uint8 returns a pointer to the given uint8 value. +func Uint8(u uint8) *uint8 { + return &u +} + +// Uint16 returns a pointer to the given uint16 value. +func Uint16(u uint16) *uint16 { + return &u +} + +// Uint32 returns a pointer to the given uint32 value. +func Uint32(u uint32) *uint32 { + return &u +} + +// Uint64 returns a pointer to the given uint64 value. +func Uint64(u uint64) *uint64 { + return &u +} + +// Uintptr returns a pointer to the given uintptr value. +func Uintptr(u uintptr) *uintptr { + return &u +} + +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + +// Time returns a pointer to the given time.Time value. +func Time(t time.Time) *time.Time { + return &t +} + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/seed/go-sdk/grpc-proto/snippet-templates.json b/seed/go-sdk/grpc-proto/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-sdk/grpc-proto/snippet.json b/seed/go-sdk/grpc-proto/snippet.json new file mode 100644 index 00000000000..37438a31560 --- /dev/null +++ b/seed/go-sdk/grpc-proto/snippet.json @@ -0,0 +1,15 @@ +{ + "endpoints": [ + { + "id": { + "path": "/users", + "method": "POST", + "identifier_override": "endpoint_user.create" + }, + "snippet": { + "type": "go", + "client": "import (\n\tcontext \"context\"\n\tfern \"github.com/grpc-proto/fern\"\n\tfernclient \"github.com/grpc-proto/fern/client\"\n)\n\nclient := fernclient.NewClient()\nresponse, err := client.User.Create(\n\tcontext.TODO(),\n\t\u0026fern.CreateRequest{},\n)\n" + } + } + ] +} \ No newline at end of file diff --git a/seed/go-sdk/grpc-proto/types.go b/seed/go-sdk/grpc-proto/types.go new file mode 100644 index 00000000000..8650c1d3049 --- /dev/null +++ b/seed/go-sdk/grpc-proto/types.go @@ -0,0 +1,95 @@ +// This file was auto-generated by Fern from our API Definition. + +package api + +import ( + json "encoding/json" + fmt "fmt" + core "github.com/grpc-proto/fern/core" +) + +type CreateResponse struct { + User *UserModel `json:"user,omitempty" url:"user,omitempty"` + + extraProperties map[string]interface{} + _rawJSON json.RawMessage +} + +func (c *CreateResponse) GetExtraProperties() map[string]interface{} { + return c.extraProperties +} + +func (c *CreateResponse) UnmarshalJSON(data []byte) error { + type unmarshaler CreateResponse + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *c = CreateResponse(value) + + extraProperties, err := core.ExtractExtraProperties(data, *c) + if err != nil { + return err + } + c.extraProperties = extraProperties + + c._rawJSON = json.RawMessage(data) + return nil +} + +func (c *CreateResponse) String() string { + if len(c._rawJSON) > 0 { + if value, err := core.StringifyJSON(c._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(c); err == nil { + return value + } + return fmt.Sprintf("%#v", c) +} + +type UserModel struct { + Username *string `json:"username,omitempty" url:"username,omitempty"` + Email *string `json:"email,omitempty" url:"email,omitempty"` + Age *int `json:"age,omitempty" url:"age,omitempty"` + Weight *float64 `json:"weight,omitempty" url:"weight,omitempty"` + Metadata map[string]interface{} `json:"metadata,omitempty" url:"metadata,omitempty"` + + extraProperties map[string]interface{} + _rawJSON json.RawMessage +} + +func (u *UserModel) GetExtraProperties() map[string]interface{} { + return u.extraProperties +} + +func (u *UserModel) UnmarshalJSON(data []byte) error { + type unmarshaler UserModel + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *u = UserModel(value) + + extraProperties, err := core.ExtractExtraProperties(data, *u) + if err != nil { + return err + } + u.extraProperties = extraProperties + + u._rawJSON = json.RawMessage(data) + return nil +} + +func (u *UserModel) String() string { + if len(u._rawJSON) > 0 { + if value, err := core.StringifyJSON(u._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(u); err == nil { + return value + } + return fmt.Sprintf("%#v", u) +} diff --git a/seed/go-sdk/grpc-proto/user.go b/seed/go-sdk/grpc-proto/user.go new file mode 100644 index 00000000000..f6a496f5d8a --- /dev/null +++ b/seed/go-sdk/grpc-proto/user.go @@ -0,0 +1,11 @@ +// This file was auto-generated by Fern from our API Definition. + +package api + +type CreateRequest struct { + Username *string `json:"username,omitempty" url:"-"` + Email *string `json:"email,omitempty" url:"-"` + Age *int `json:"age,omitempty" url:"-"` + Weight *float64 `json:"weight,omitempty" url:"-"` + Metadata map[string]interface{} `json:"metadata,omitempty" url:"-"` +} diff --git a/seed/go-sdk/grpc-proto/user/client.go b/seed/go-sdk/grpc-proto/user/client.go new file mode 100644 index 00000000000..e161b44f048 --- /dev/null +++ b/seed/go-sdk/grpc-proto/user/client.go @@ -0,0 +1,67 @@ +// This file was auto-generated by Fern from our API Definition. + +package user + +import ( + context "context" + fern "github.com/grpc-proto/fern" + core "github.com/grpc-proto/fern/core" + option "github.com/grpc-proto/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + } +} + +func (c *Client) Create( + ctx context.Context, + request *fern.CreateRequest, + opts ...option.RequestOption, +) (*fern.CreateResponse, error) { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := baseURL + "/users" + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + var response *fern.CreateResponse + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodPost, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Request: request, + Response: &response, + }, + ); err != nil { + return nil, err + } + return response, nil +} diff --git a/seed/go-sdk/grpc/.github/workflows/ci.yml b/seed/go-sdk/grpc/.github/workflows/ci.yml new file mode 100644 index 00000000000..d4c0a5dcd95 --- /dev/null +++ b/seed/go-sdk/grpc/.github/workflows/ci.yml @@ -0,0 +1,27 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Compile + run: go build ./... + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up go + uses: actions/setup-go@v4 + + - name: Test + run: go test ./... diff --git a/seed/go-sdk/grpc/.mock/definition/api.yml b/seed/go-sdk/grpc/.mock/definition/api.yml new file mode 100644 index 00000000000..15a9f823724 --- /dev/null +++ b/seed/go-sdk/grpc/.mock/definition/api.yml @@ -0,0 +1,4 @@ +name: api + +error-discrimination: + strategy: status-code \ No newline at end of file diff --git a/seed/go-sdk/grpc/.mock/definition/user.yml b/seed/go-sdk/grpc/.mock/definition/user.yml new file mode 100644 index 00000000000..80223300afc --- /dev/null +++ b/seed/go-sdk/grpc/.mock/definition/user.yml @@ -0,0 +1,61 @@ +types: + Metadata: + type: map> + encoding: + proto: + type: google.protobuf.Struct + + MetadataValue: + discriminated: false + union: + - double + - string + - boolean + - list + encoding: + proto: + type: google.protobuf.Value + + User: + properties: + id: string + username: string + email: optional + age: optional + weight: optional + metadata: optional + + CreateUserResponse: + properties: + user: User + +service: + auth: false + base-path: / + transport: + grpc: + service-name: UserService + endpoints: + createUser: + method: POST + path: /users + request: + name: CreateUserRequest + body: + properties: + username: string + email: optional + age: optional + weight: optional + response: CreateUserResponse + + getUser: + method: GET + path: /users + request: + name: GetUserRequest + query-parameters: + username: optional + age: optional + weight: optional + response: User diff --git a/seed/go-sdk/grpc/.mock/fern.config.json b/seed/go-sdk/grpc/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/go-sdk/grpc/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/go-sdk/grpc/.mock/generators.yml b/seed/go-sdk/grpc/.mock/generators.yml new file mode 100644 index 00000000000..f62dfba5843 --- /dev/null +++ b/seed/go-sdk/grpc/.mock/generators.yml @@ -0,0 +1,4 @@ +api: + - proto: + root: proto + target: proto/user/v1/user.proto \ No newline at end of file diff --git a/seed/go-sdk/grpc/client/client.go b/seed/go-sdk/grpc/client/client.go new file mode 100644 index 00000000000..ebb69031e27 --- /dev/null +++ b/seed/go-sdk/grpc/client/client.go @@ -0,0 +1,33 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + core "github.com/grpc/fern/core" + option "github.com/grpc/fern/option" + user "github.com/grpc/fern/user" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header + + User *user.Client +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + User: user.NewClient(opts...), + } +} diff --git a/seed/go-sdk/grpc/client/client_test.go b/seed/go-sdk/grpc/client/client_test.go new file mode 100644 index 00000000000..cfb257782c5 --- /dev/null +++ b/seed/go-sdk/grpc/client/client_test.go @@ -0,0 +1,45 @@ +// This file was auto-generated by Fern from our API Definition. + +package client + +import ( + option "github.com/grpc/fern/option" + assert "github.com/stretchr/testify/assert" + http "net/http" + testing "testing" + time "time" +) + +func TestNewClient(t *testing.T) { + t.Run("default", func(t *testing.T) { + c := NewClient() + assert.Empty(t, c.baseURL) + }) + + t.Run("base url", func(t *testing.T) { + c := NewClient( + option.WithBaseURL("test.co"), + ) + assert.Equal(t, "test.co", c.baseURL) + }) + + t.Run("http client", func(t *testing.T) { + httpClient := &http.Client{ + Timeout: 5 * time.Second, + } + c := NewClient( + option.WithHTTPClient(httpClient), + ) + assert.Empty(t, c.baseURL) + }) + + t.Run("http header", func(t *testing.T) { + header := make(http.Header) + header.Set("X-API-Tenancy", "test") + c := NewClient( + option.WithHTTPHeader(header), + ) + assert.Empty(t, c.baseURL) + assert.Equal(t, "test", c.header.Get("X-API-Tenancy")) + }) +} diff --git a/seed/go-sdk/grpc/core/core.go b/seed/go-sdk/grpc/core/core.go new file mode 100644 index 00000000000..14c86c95cb0 --- /dev/null +++ b/seed/go-sdk/grpc/core/core.go @@ -0,0 +1,287 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "mime/multipart" + "net/http" + "net/url" + "reflect" +) + +const ( + // contentType specifies the JSON Content-Type header value. + contentType = "application/json" + contentTypeHeader = "Content-Type" +) + +// HTTPClient is an interface for a subset of the *http.Client. +type HTTPClient interface { + Do(*http.Request) (*http.Response, error) +} + +// EncodeURL encodes the given arguments into the URL, escaping +// values as needed. +func EncodeURL(urlFormat string, args ...interface{}) string { + escapedArgs := make([]interface{}, 0, len(args)) + for _, arg := range args { + escapedArgs = append(escapedArgs, url.PathEscape(fmt.Sprintf("%v", arg))) + } + return fmt.Sprintf(urlFormat, escapedArgs...) +} + +// MergeHeaders merges the given headers together, where the right +// takes precedence over the left. +func MergeHeaders(left, right http.Header) http.Header { + for key, values := range right { + if len(values) > 1 { + left[key] = values + continue + } + if value := right.Get(key); value != "" { + left.Set(key, value) + } + } + return left +} + +// WriteMultipartJSON writes the given value as a JSON part. +// This is used to serialize non-primitive multipart properties +// (i.e. lists, objects, etc). +func WriteMultipartJSON(writer *multipart.Writer, field string, value interface{}) error { + bytes, err := json.Marshal(value) + if err != nil { + return err + } + return writer.WriteField(field, string(bytes)) +} + +// APIError is a lightweight wrapper around the standard error +// interface that preserves the status code from the RPC, if any. +type APIError struct { + err error + + StatusCode int `json:"-"` +} + +// NewAPIError constructs a new API error. +func NewAPIError(statusCode int, err error) *APIError { + return &APIError{ + err: err, + StatusCode: statusCode, + } +} + +// Unwrap returns the underlying error. This also makes the error compatible +// with errors.As and errors.Is. +func (a *APIError) Unwrap() error { + if a == nil { + return nil + } + return a.err +} + +// Error returns the API error's message. +func (a *APIError) Error() string { + if a == nil || (a.err == nil && a.StatusCode == 0) { + return "" + } + if a.err == nil { + return fmt.Sprintf("%d", a.StatusCode) + } + if a.StatusCode == 0 { + return a.err.Error() + } + return fmt.Sprintf("%d: %s", a.StatusCode, a.err.Error()) +} + +// ErrorDecoder decodes *http.Response errors and returns a +// typed API error (e.g. *APIError). +type ErrorDecoder func(statusCode int, body io.Reader) error + +// Caller calls APIs and deserializes their response, if any. +type Caller struct { + client HTTPClient + retrier *Retrier +} + +// CallerParams represents the parameters used to constrcut a new *Caller. +type CallerParams struct { + Client HTTPClient + MaxAttempts uint +} + +// NewCaller returns a new *Caller backed by the given parameters. +func NewCaller(params *CallerParams) *Caller { + var httpClient HTTPClient = http.DefaultClient + if params.Client != nil { + httpClient = params.Client + } + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + return &Caller{ + client: httpClient, + retrier: NewRetrier(retryOptions...), + } +} + +// CallParams represents the parameters used to issue an API call. +type CallParams struct { + URL string + Method string + MaxAttempts uint + Headers http.Header + Client HTTPClient + Request interface{} + Response interface{} + ResponseIsOptional bool + ErrorDecoder ErrorDecoder +} + +// Call issues an API call according to the given call parameters. +func (c *Caller) Call(ctx context.Context, params *CallParams) error { + req, err := newRequest(ctx, params.URL, params.Method, params.Headers, params.Request) + if err != nil { + return err + } + + // If the call has been cancelled, don't issue the request. + if err := ctx.Err(); err != nil { + return err + } + + client := c.client + if params.Client != nil { + // Use the HTTP client scoped to the request. + client = params.Client + } + + var retryOptions []RetryOption + if params.MaxAttempts > 0 { + retryOptions = append(retryOptions, WithMaxAttempts(params.MaxAttempts)) + } + + resp, err := c.retrier.Run( + client.Do, + req, + params.ErrorDecoder, + retryOptions..., + ) + if err != nil { + return err + } + + // Close the response body after we're done. + defer resp.Body.Close() + + // Check if the call was cancelled before we return the error + // associated with the call and/or unmarshal the response data. + if err := ctx.Err(); err != nil { + return err + } + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return decodeError(resp, params.ErrorDecoder) + } + + // Mutate the response parameter in-place. + if params.Response != nil { + if writer, ok := params.Response.(io.Writer); ok { + _, err = io.Copy(writer, resp.Body) + } else { + err = json.NewDecoder(resp.Body).Decode(params.Response) + } + if err != nil { + if err == io.EOF { + if params.ResponseIsOptional { + // The response is optional, so we should ignore the + // io.EOF error + return nil + } + return fmt.Errorf("expected a %T response, but the server responded with nothing", params.Response) + } + return err + } + } + + return nil +} + +// newRequest returns a new *http.Request with all of the fields +// required to issue the call. +func newRequest( + ctx context.Context, + url string, + method string, + endpointHeaders http.Header, + request interface{}, +) (*http.Request, error) { + requestBody, err := newRequestBody(request) + if err != nil { + return nil, err + } + req, err := http.NewRequestWithContext(ctx, method, url, requestBody) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + req.Header.Set(contentTypeHeader, contentType) + for name, values := range endpointHeaders { + req.Header[name] = values + } + return req, nil +} + +// newRequestBody returns a new io.Reader that represents the HTTP request body. +func newRequestBody(request interface{}) (io.Reader, error) { + var requestBody io.Reader + if !isNil(request) { + if body, ok := request.(io.Reader); ok { + requestBody = body + } else { + requestBytes, err := json.Marshal(request) + if err != nil { + return nil, err + } + requestBody = bytes.NewReader(requestBytes) + } + } + return requestBody, nil +} + +// decodeError decodes the error from the given HTTP response. Note that +// it's the caller's responsibility to close the response body. +func decodeError(response *http.Response, errorDecoder ErrorDecoder) error { + if errorDecoder != nil { + // This endpoint has custom errors, so we'll + // attempt to unmarshal the error into a structured + // type based on the status code. + return errorDecoder(response.StatusCode, response.Body) + } + // This endpoint doesn't have any custom error + // types, so we just read the body as-is, and + // put it into a normal error. + bytes, err := io.ReadAll(response.Body) + if err != nil && err != io.EOF { + return err + } + if err == io.EOF { + // The error didn't have a response body, + // so all we can do is return an error + // with the status code. + return NewAPIError(response.StatusCode, nil) + } + return NewAPIError(response.StatusCode, errors.New(string(bytes))) +} + +// isNil is used to determine if the request value is equal to nil (i.e. an interface +// value that holds a nil concrete value is itself non-nil). +func isNil(value interface{}) bool { + return value == nil || reflect.ValueOf(value).IsNil() +} diff --git a/seed/go-sdk/grpc/core/core_test.go b/seed/go-sdk/grpc/core/core_test.go new file mode 100644 index 00000000000..adf9e3112cd --- /dev/null +++ b/seed/go-sdk/grpc/core/core_test.go @@ -0,0 +1,303 @@ +package core + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/http/httptest" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestCase represents a single test case. +type TestCase struct { + description string + + // Server-side assertions. + giveMethod string + giveResponseIsOptional bool + giveHeader http.Header + giveErrorDecoder ErrorDecoder + giveRequest *Request + + // Client-side assertions. + wantResponse *Response + wantError error +} + +// Request a simple request body. +type Request struct { + Id string `json:"id"` +} + +// Response a simple response body. +type Response struct { + Id string `json:"id"` +} + +// NotFoundError represents a 404. +type NotFoundError struct { + *APIError + + Message string `json:"message"` +} + +func TestCall(t *testing.T) { + tests := []*TestCase{ + { + description: "GET success", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + wantResponse: &Response{ + Id: "123", + }, + }, + { + description: "GET not found", + giveMethod: http.MethodGet, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusNotFound), + }, + giveErrorDecoder: newTestErrorDecoder(t), + wantError: &NotFoundError{ + APIError: NewAPIError( + http.StatusNotFound, + errors.New(`{"message":"ID \"404\" not found"}`), + ), + }, + }, + { + description: "POST empty body", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: nil, + wantError: NewAPIError( + http.StatusBadRequest, + errors.New("invalid request"), + ), + }, + { + description: "POST optional response", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"success"}, + }, + giveRequest: &Request{ + Id: "123", + }, + giveResponseIsOptional: true, + }, + { + description: "POST API error", + giveMethod: http.MethodPost, + giveHeader: http.Header{ + "X-API-Status": []string{"fail"}, + }, + giveRequest: &Request{ + Id: strconv.Itoa(http.StatusInternalServerError), + }, + wantError: NewAPIError( + http.StatusInternalServerError, + errors.New("failed to process request"), + ), + }, + } + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + var ( + server = newTestServer(t, test) + client = server.Client() + ) + caller := NewCaller( + &CallerParams{ + Client: client, + }, + ) + var response *Response + err := caller.Call( + context.Background(), + &CallParams{ + URL: server.URL, + Method: test.giveMethod, + Headers: test.giveHeader, + Request: test.giveRequest, + Response: &response, + ResponseIsOptional: test.giveResponseIsOptional, + ErrorDecoder: test.giveErrorDecoder, + }, + ) + if test.wantError != nil { + assert.EqualError(t, err, test.wantError.Error()) + return + } + require.NoError(t, err) + assert.Equal(t, test.wantResponse, response) + }) + } +} + +func TestMergeHeaders(t *testing.T) { + t.Run("both empty", func(t *testing.T) { + merged := MergeHeaders(make(http.Header), make(http.Header)) + assert.Empty(t, merged) + }) + + t.Run("empty left", func(t *testing.T) { + left := make(http.Header) + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("empty right", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.1") + + right := make(http.Header) + + merged := MergeHeaders(left, right) + assert.Equal(t, "0.0.1", merged.Get("X-API-Version")) + }) + + t.Run("single value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Version", "0.0.0") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) + + t.Run("multiple value override", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Versions", "0.0.0") + + right := make(http.Header) + right.Add("X-API-Versions", "0.0.1") + right.Add("X-API-Versions", "0.0.2") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"0.0.1", "0.0.2"}, merged.Values("X-API-Versions")) + }) + + t.Run("disjoint merge", func(t *testing.T) { + left := make(http.Header) + left.Set("X-API-Tenancy", "test") + + right := make(http.Header) + right.Set("X-API-Version", "0.0.1") + + merged := MergeHeaders(left, right) + assert.Equal(t, []string{"test"}, merged.Values("X-API-Tenancy")) + assert.Equal(t, []string{"0.0.1"}, merged.Values("X-API-Version")) + }) +} + +// newTestServer returns a new *httptest.Server configured with the +// given test parameters. +func newTestServer(t *testing.T, tc *TestCase) *httptest.Server { + return httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, tc.giveMethod, r.Method) + assert.Equal(t, contentType, r.Header.Get(contentTypeHeader)) + for header, value := range tc.giveHeader { + assert.Equal(t, value, r.Header.Values(header)) + } + + request := new(Request) + + bytes, err := io.ReadAll(r.Body) + if tc.giveRequest == nil { + require.Empty(t, bytes) + w.WriteHeader(http.StatusBadRequest) + _, err = w.Write([]byte("invalid request")) + require.NoError(t, err) + return + } + require.NoError(t, err) + require.NoError(t, json.Unmarshal(bytes, request)) + + switch request.Id { + case strconv.Itoa(http.StatusNotFound): + notFoundError := &NotFoundError{ + APIError: &APIError{ + StatusCode: http.StatusNotFound, + }, + Message: fmt.Sprintf("ID %q not found", request.Id), + } + bytes, err = json.Marshal(notFoundError) + require.NoError(t, err) + + w.WriteHeader(http.StatusNotFound) + _, err = w.Write(bytes) + require.NoError(t, err) + return + + case strconv.Itoa(http.StatusInternalServerError): + w.WriteHeader(http.StatusInternalServerError) + _, err = w.Write([]byte("failed to process request")) + require.NoError(t, err) + return + } + + if tc.giveResponseIsOptional { + w.WriteHeader(http.StatusOK) + return + } + + response := &Response{ + Id: request.Id, + } + bytes, err = json.Marshal(response) + require.NoError(t, err) + + _, err = w.Write(bytes) + require.NoError(t, err) + }, + ), + ) +} + +// newTestErrorDecoder returns an error decoder suitable for tests. +func newTestErrorDecoder(t *testing.T) func(int, io.Reader) error { + return func(statusCode int, body io.Reader) error { + raw, err := io.ReadAll(body) + require.NoError(t, err) + + var ( + apiError = NewAPIError(statusCode, errors.New(string(raw))) + decoder = json.NewDecoder(bytes.NewReader(raw)) + ) + if statusCode == http.StatusNotFound { + value := new(NotFoundError) + value.APIError = apiError + require.NoError(t, decoder.Decode(value)) + + return value + } + return apiError + } +} diff --git a/seed/go-sdk/grpc/core/extra_properties.go b/seed/go-sdk/grpc/core/extra_properties.go new file mode 100644 index 00000000000..a6af3e12410 --- /dev/null +++ b/seed/go-sdk/grpc/core/extra_properties.go @@ -0,0 +1,141 @@ +package core + +import ( + "bytes" + "encoding/json" + "fmt" + "reflect" + "strings" +) + +// MarshalJSONWithExtraProperty marshals the given value to JSON, including the extra property. +func MarshalJSONWithExtraProperty(marshaler interface{}, key string, value interface{}) ([]byte, error) { + return MarshalJSONWithExtraProperties(marshaler, map[string]interface{}{key: value}) +} + +// MarshalJSONWithExtraProperties marshals the given value to JSON, including any extra properties. +func MarshalJSONWithExtraProperties(marshaler interface{}, extraProperties map[string]interface{}) ([]byte, error) { + bytes, err := json.Marshal(marshaler) + if err != nil { + return nil, err + } + if len(extraProperties) == 0 { + return bytes, nil + } + keys, err := getKeys(marshaler) + if err != nil { + return nil, err + } + for _, key := range keys { + if _, ok := extraProperties[key]; ok { + return nil, fmt.Errorf("cannot add extra property %q because it is already defined on the type", key) + } + } + extraBytes, err := json.Marshal(extraProperties) + if err != nil { + return nil, err + } + if isEmptyJSON(bytes) { + if isEmptyJSON(extraBytes) { + return bytes, nil + } + return extraBytes, nil + } + result := bytes[:len(bytes)-1] + result = append(result, ',') + result = append(result, extraBytes[1:len(extraBytes)-1]...) + result = append(result, '}') + return result, nil +} + +// ExtractExtraProperties extracts any extra properties from the given value. +func ExtractExtraProperties(bytes []byte, value interface{}, exclude ...string) (map[string]interface{}, error) { + val := reflect.ValueOf(value) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return nil, fmt.Errorf("value must be non-nil to extract extra properties") + } + val = val.Elem() + } + if err := json.Unmarshal(bytes, &value); err != nil { + return nil, err + } + var extraProperties map[string]interface{} + if err := json.Unmarshal(bytes, &extraProperties); err != nil { + return nil, err + } + for i := 0; i < val.Type().NumField(); i++ { + key := jsonKey(val.Type().Field(i)) + if key == "" || key == "-" { + continue + } + delete(extraProperties, key) + } + for _, key := range exclude { + delete(extraProperties, key) + } + if len(extraProperties) == 0 { + return nil, nil + } + return extraProperties, nil +} + +// getKeys returns the keys associated with the given value. The value must be a +// a struct or a map with string keys. +func getKeys(value interface{}) ([]string, error) { + val := reflect.ValueOf(value) + if val.Kind() == reflect.Ptr { + val = val.Elem() + } + if !val.IsValid() { + return nil, nil + } + switch val.Kind() { + case reflect.Struct: + return getKeysForStructType(val.Type()), nil + case reflect.Map: + var keys []string + if val.Type().Key().Kind() != reflect.String { + return nil, fmt.Errorf("cannot extract keys from %T; only structs and maps with string keys are supported", value) + } + for _, key := range val.MapKeys() { + keys = append(keys, key.String()) + } + return keys, nil + default: + return nil, fmt.Errorf("cannot extract keys from %T; only structs and maps with string keys are supported", value) + } +} + +// getKeysForStructType returns all the keys associated with the given struct type, +// visiting embedded fields recursively. +func getKeysForStructType(structType reflect.Type) []string { + if structType.Kind() == reflect.Pointer { + structType = structType.Elem() + } + if structType.Kind() != reflect.Struct { + return nil + } + var keys []string + for i := 0; i < structType.NumField(); i++ { + field := structType.Field(i) + if field.Anonymous { + keys = append(keys, getKeysForStructType(field.Type)...) + continue + } + keys = append(keys, jsonKey(field)) + } + return keys +} + +// jsonKey returns the JSON key from the struct tag of the given field, +// excluding the omitempty flag (if any). +func jsonKey(field reflect.StructField) string { + return strings.TrimSuffix(field.Tag.Get("json"), ",omitempty") +} + +// isEmptyJSON returns true if the given data is empty, the empty JSON object, or +// an explicit null. +func isEmptyJSON(data []byte) bool { + return len(data) <= 2 || bytes.Equal(data, []byte("null")) +} diff --git a/seed/go-sdk/grpc/core/extra_properties_test.go b/seed/go-sdk/grpc/core/extra_properties_test.go new file mode 100644 index 00000000000..dc66fccd7f1 --- /dev/null +++ b/seed/go-sdk/grpc/core/extra_properties_test.go @@ -0,0 +1,228 @@ +package core + +import ( + "encoding/json" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type testMarshaler struct { + Name string `json:"name"` + BirthDate time.Time `json:"birthDate"` + CreatedAt time.Time `json:"created_at"` +} + +func (t *testMarshaler) MarshalJSON() ([]byte, error) { + type embed testMarshaler + var marshaler = struct { + embed + BirthDate string `json:"birthDate"` + CreatedAt string `json:"created_at"` + }{ + embed: embed(*t), + BirthDate: t.BirthDate.Format("2006-01-02"), + CreatedAt: t.CreatedAt.Format(time.RFC3339), + } + return MarshalJSONWithExtraProperty(marshaler, "type", "test") +} + +func TestMarshalJSONWithExtraProperties(t *testing.T) { + tests := []struct { + desc string + giveMarshaler interface{} + giveExtraProperties map[string]interface{} + wantBytes []byte + wantError string + }{ + { + desc: "invalid type", + giveMarshaler: []string{"invalid"}, + giveExtraProperties: map[string]interface{}{"key": "overwrite"}, + wantError: `cannot extract keys from []string; only structs and maps with string keys are supported`, + }, + { + desc: "invalid key type", + giveMarshaler: map[int]interface{}{42: "value"}, + giveExtraProperties: map[string]interface{}{"key": "overwrite"}, + wantError: `cannot extract keys from map[int]interface {}; only structs and maps with string keys are supported`, + }, + { + desc: "invalid map overwrite", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{"key": "overwrite"}, + wantError: `cannot add extra property "key" because it is already defined on the type`, + }, + { + desc: "invalid struct overwrite", + giveMarshaler: new(testMarshaler), + giveExtraProperties: map[string]interface{}{"birthDate": "2000-01-01"}, + wantError: `cannot add extra property "birthDate" because it is already defined on the type`, + }, + { + desc: "invalid struct overwrite embedded type", + giveMarshaler: new(testMarshaler), + giveExtraProperties: map[string]interface{}{"name": "bob"}, + wantError: `cannot add extra property "name" because it is already defined on the type`, + }, + { + desc: "nil", + giveMarshaler: nil, + giveExtraProperties: nil, + wantBytes: []byte(`null`), + }, + { + desc: "empty", + giveMarshaler: map[string]interface{}{}, + giveExtraProperties: map[string]interface{}{}, + wantBytes: []byte(`{}`), + }, + { + desc: "no extra properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{}, + wantBytes: []byte(`{"key":"value"}`), + }, + { + desc: "only extra properties", + giveMarshaler: map[string]interface{}{}, + giveExtraProperties: map[string]interface{}{"key": "value"}, + wantBytes: []byte(`{"key":"value"}`), + }, + { + desc: "single extra property", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{"extra": "property"}, + wantBytes: []byte(`{"key":"value","extra":"property"}`), + }, + { + desc: "multiple extra properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{"one": 1, "two": 2}, + wantBytes: []byte(`{"key":"value","one":1,"two":2}`), + }, + { + desc: "nested properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{ + "user": map[string]interface{}{ + "age": 42, + "name": "alice", + }, + }, + wantBytes: []byte(`{"key":"value","user":{"age":42,"name":"alice"}}`), + }, + { + desc: "multiple nested properties", + giveMarshaler: map[string]interface{}{"key": "value"}, + giveExtraProperties: map[string]interface{}{ + "metadata": map[string]interface{}{ + "ip": "127.0.0.1", + }, + "user": map[string]interface{}{ + "age": 42, + "name": "alice", + }, + }, + wantBytes: []byte(`{"key":"value","metadata":{"ip":"127.0.0.1"},"user":{"age":42,"name":"alice"}}`), + }, + { + desc: "custom marshaler", + giveMarshaler: &testMarshaler{ + Name: "alice", + BirthDate: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), + CreatedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), + }, + giveExtraProperties: map[string]interface{}{ + "extra": "property", + }, + wantBytes: []byte(`{"name":"alice","birthDate":"2000-01-01","created_at":"2024-01-01T00:00:00Z","type":"test","extra":"property"}`), + }, + } + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + bytes, err := MarshalJSONWithExtraProperties(tt.giveMarshaler, tt.giveExtraProperties) + if tt.wantError != "" { + require.EqualError(t, err, tt.wantError) + assert.Nil(t, tt.wantBytes) + return + } + require.NoError(t, err) + assert.Equal(t, tt.wantBytes, bytes) + + value := make(map[string]interface{}) + require.NoError(t, json.Unmarshal(bytes, &value)) + }) + } +} + +func TestExtractExtraProperties(t *testing.T) { + t.Run("none", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := &user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice"}`), value) + require.NoError(t, err) + assert.Nil(t, extraProperties) + }) + + t.Run("non-nil pointer", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := &user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + require.NoError(t, err) + assert.Equal(t, map[string]interface{}{"age": float64(42)}, extraProperties) + }) + + t.Run("nil pointer", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + var value *user + _, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + assert.EqualError(t, err, "value must be non-nil to extract extra properties") + }) + + t.Run("non-zero value", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + require.NoError(t, err) + assert.Equal(t, map[string]interface{}{"age": float64(42)}, extraProperties) + }) + + t.Run("zero value", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + var value user + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value) + require.NoError(t, err) + assert.Equal(t, map[string]interface{}{"age": float64(42)}, extraProperties) + }) + + t.Run("exclude", func(t *testing.T) { + type user struct { + Name string `json:"name"` + } + value := &user{ + Name: "alice", + } + extraProperties, err := ExtractExtraProperties([]byte(`{"name": "alice", "age": 42}`), value, "age") + require.NoError(t, err) + assert.Nil(t, extraProperties) + }) +} diff --git a/seed/go-sdk/grpc/core/query.go b/seed/go-sdk/grpc/core/query.go new file mode 100644 index 00000000000..2670ff7feda --- /dev/null +++ b/seed/go-sdk/grpc/core/query.go @@ -0,0 +1,231 @@ +package core + +import ( + "encoding/base64" + "fmt" + "net/url" + "reflect" + "strings" + "time" + + "github.com/google/uuid" +) + +var ( + bytesType = reflect.TypeOf([]byte{}) + queryEncoderType = reflect.TypeOf(new(QueryEncoder)).Elem() + timeType = reflect.TypeOf(time.Time{}) + uuidType = reflect.TypeOf(uuid.UUID{}) +) + +// QueryEncoder is an interface implemented by any type that wishes to encode +// itself into URL values in a non-standard way. +type QueryEncoder interface { + EncodeQueryValues(key string, v *url.Values) error +} + +// QueryValues encodes url.Values from request objects. +// +// Note: This type is inspired by Google's query encoding library, but +// supports far less customization and is tailored to fit this SDK's use case. +// +// Ref: https://github.com/google/go-querystring +func QueryValues(v interface{}) (url.Values, error) { + values := make(url.Values) + val := reflect.ValueOf(v) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return values, nil + } + val = val.Elem() + } + + if v == nil { + return values, nil + } + + if val.Kind() != reflect.Struct { + return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind()) + } + + err := reflectValue(values, val, "") + return values, err +} + +// reflectValue populates the values parameter from the struct fields in val. +// Embedded structs are followed recursively (using the rules defined in the +// Values function documentation) breadth-first. +func reflectValue(values url.Values, val reflect.Value, scope string) error { + typ := val.Type() + for i := 0; i < typ.NumField(); i++ { + sf := typ.Field(i) + if sf.PkgPath != "" && !sf.Anonymous { + // Skip unexported fields. + continue + } + + sv := val.Field(i) + tag := sf.Tag.Get("url") + if tag == "" || tag == "-" { + continue + } + + name, opts := parseTag(tag) + if name == "" { + name = sf.Name + } + + if scope != "" { + name = scope + "[" + name + "]" + } + + if opts.Contains("omitempty") && isEmptyValue(sv) { + continue + } + + if sv.Type().Implements(queryEncoderType) { + // If sv is a nil pointer and the custom encoder is defined on a non-pointer + // method receiver, set sv to the zero value of the underlying type + if !reflect.Indirect(sv).IsValid() && sv.Type().Elem().Implements(queryEncoderType) { + sv = reflect.New(sv.Type().Elem()) + } + + m := sv.Interface().(QueryEncoder) + if err := m.EncodeQueryValues(name, &values); err != nil { + return err + } + continue + } + + // Recursively dereference pointers, but stop at nil pointers. + for sv.Kind() == reflect.Ptr { + if sv.IsNil() { + break + } + sv = sv.Elem() + } + + if sv.Type() == uuidType || sv.Type() == bytesType || sv.Type() == timeType { + values.Add(name, valueString(sv, opts, sf)) + continue + } + + if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { + if sv.Len() == 0 { + // Skip if slice or array is empty. + continue + } + for i := 0; i < sv.Len(); i++ { + value := sv.Index(i) + if isStructPointer(value) && !value.IsNil() { + if err := reflectValue(values, value.Elem(), name); err != nil { + return err + } + } else { + values.Add(name, valueString(value, opts, sf)) + } + } + continue + } + + if sv.Kind() == reflect.Struct { + if err := reflectValue(values, sv, name); err != nil { + return err + } + continue + } + + values.Add(name, valueString(sv, opts, sf)) + } + + return nil +} + +// valueString returns the string representation of a value. +func valueString(v reflect.Value, opts tagOptions, sf reflect.StructField) string { + for v.Kind() == reflect.Ptr { + if v.IsNil() { + return "" + } + v = v.Elem() + } + + if v.Type() == timeType { + t := v.Interface().(time.Time) + if format := sf.Tag.Get("format"); format == "date" { + return t.Format("2006-01-02") + } + return t.Format(time.RFC3339) + } + + if v.Type() == uuidType { + u := v.Interface().(uuid.UUID) + return u.String() + } + + if v.Type() == bytesType { + b := v.Interface().([]byte) + return base64.StdEncoding.EncodeToString(b) + } + + return fmt.Sprint(v.Interface()) +} + +// isEmptyValue checks if a value should be considered empty for the purposes +// of omitting fields with the "omitempty" option. +func isEmptyValue(v reflect.Value) bool { + type zeroable interface { + IsZero() bool + } + + if !v.IsZero() { + if z, ok := v.Interface().(zeroable); ok { + return z.IsZero() + } + } + + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + case reflect.Invalid, reflect.Complex64, reflect.Complex128, reflect.Chan, reflect.Func, reflect.Struct, reflect.UnsafePointer: + return false + } + + return false +} + +// isStructPointer returns true if the given reflect.Value is a pointer to a struct. +func isStructPointer(v reflect.Value) bool { + return v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct +} + +// tagOptions is the string following a comma in a struct field's "url" tag, or +// the empty string. It does not include the leading comma. +type tagOptions []string + +// parseTag splits a struct field's url tag into its name and comma-separated +// options. +func parseTag(tag string) (string, tagOptions) { + s := strings.Split(tag, ",") + return s[0], s[1:] +} + +// Contains checks whether the tagOptions contains the specified option. +func (o tagOptions) Contains(option string) bool { + for _, s := range o { + if s == option { + return true + } + } + return false +} diff --git a/seed/go-sdk/grpc/core/query_test.go b/seed/go-sdk/grpc/core/query_test.go new file mode 100644 index 00000000000..5498fa92aa5 --- /dev/null +++ b/seed/go-sdk/grpc/core/query_test.go @@ -0,0 +1,187 @@ +package core + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestQueryValues(t *testing.T) { + t.Run("empty optional", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Empty(t, values) + }) + + t.Run("empty required", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + values, err := QueryValues(&example{}) + require.NoError(t, err) + assert.Equal(t, "required=", values.Encode()) + }) + + t.Run("allow multiple", func(t *testing.T) { + type example struct { + Values []string `json:"values" url:"values"` + } + + values, err := QueryValues( + &example{ + Values: []string{"foo", "bar", "baz"}, + }, + ) + require.NoError(t, err) + assert.Equal(t, "values=foo&values=bar&values=baz", values.Encode()) + }) + + t.Run("nested object", func(t *testing.T) { + type nested struct { + Value *string `json:"value,omitempty" url:"value,omitempty"` + } + type example struct { + Required string `json:"required" url:"required"` + Nested *nested `json:"nested,omitempty" url:"nested,omitempty"` + } + + nestedValue := "nestedValue" + values, err := QueryValues( + &example{ + Required: "requiredValue", + Nested: &nested{ + Value: &nestedValue, + }, + }, + ) + require.NoError(t, err) + assert.Equal(t, "nested%5Bvalue%5D=nestedValue&required=requiredValue", values.Encode()) + }) + + t.Run("url unspecified", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("url ignored", func(t *testing.T) { + type example struct { + Required string `json:"required" url:"required"` + NotFound string `json:"notFound" url:"-"` + } + + values, err := QueryValues( + &example{ + Required: "requiredValue", + NotFound: "notFound", + }, + ) + require.NoError(t, err) + assert.Equal(t, "required=requiredValue", values.Encode()) + }) + + t.Run("datetime", func(t *testing.T) { + type example struct { + DateTime time.Time `json:"dateTime" url:"dateTime"` + } + + values, err := QueryValues( + &example{ + DateTime: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "dateTime=1994-03-16T12%3A34%3A56Z", values.Encode()) + }) + + t.Run("date", func(t *testing.T) { + type example struct { + Date time.Time `json:"date" url:"date" format:"date"` + } + + values, err := QueryValues( + &example{ + Date: time.Date(1994, 3, 16, 12, 34, 56, 0, time.UTC), + }, + ) + require.NoError(t, err) + assert.Equal(t, "date=1994-03-16", values.Encode()) + }) + + t.Run("optional time", func(t *testing.T) { + type example struct { + Date *time.Time `json:"date,omitempty" url:"date,omitempty" format:"date"` + } + + values, err := QueryValues( + &example{}, + ) + require.NoError(t, err) + assert.Empty(t, values.Encode()) + }) + + t.Run("omitempty with non-pointer zero value", func(t *testing.T) { + type enum string + + type example struct { + Enum enum `json:"enum,omitempty" url:"enum,omitempty"` + } + + values, err := QueryValues( + &example{}, + ) + require.NoError(t, err) + assert.Empty(t, values.Encode()) + }) + + t.Run("object array", func(t *testing.T) { + type object struct { + Key string `json:"key" url:"key"` + Value string `json:"value" url:"value"` + } + type example struct { + Objects []*object `json:"objects,omitempty" url:"objects,omitempty"` + } + + values, err := QueryValues( + &example{ + Objects: []*object{ + { + Key: "hello", + Value: "world", + }, + { + Key: "foo", + Value: "bar", + }, + }, + }, + ) + require.NoError(t, err) + assert.Equal(t, "objects%5Bkey%5D=hello&objects%5Bkey%5D=foo&objects%5Bvalue%5D=world&objects%5Bvalue%5D=bar", values.Encode()) + }) +} diff --git a/seed/go-sdk/grpc/core/request_option.go b/seed/go-sdk/grpc/core/request_option.go new file mode 100644 index 00000000000..1db1b6629cf --- /dev/null +++ b/seed/go-sdk/grpc/core/request_option.go @@ -0,0 +1,85 @@ +// This file was auto-generated by Fern from our API Definition. + +package core + +import ( + http "net/http" +) + +// RequestOption adapts the behavior of the client or an individual request. +type RequestOption interface { + applyRequestOptions(*RequestOptions) +} + +// RequestOptions defines all of the possible request options. +// +// This type is primarily used by the generated code and is not meant +// to be used directly; use the option package instead. +type RequestOptions struct { + BaseURL string + HTTPClient HTTPClient + HTTPHeader http.Header + MaxAttempts uint +} + +// NewRequestOptions returns a new *RequestOptions value. +// +// This function is primarily used by the generated code and is not meant +// to be used directly; use RequestOption instead. +func NewRequestOptions(opts ...RequestOption) *RequestOptions { + options := &RequestOptions{ + HTTPHeader: make(http.Header), + } + for _, opt := range opts { + opt.applyRequestOptions(options) + } + return options +} + +// ToHeader maps the configured request options into a http.Header used +// for the request(s). +func (r *RequestOptions) ToHeader() http.Header { return r.cloneHeader() } + +func (r *RequestOptions) cloneHeader() http.Header { + headers := r.HTTPHeader.Clone() + headers.Set("X-Fern-Language", "Go") + headers.Set("X-Fern-SDK-Name", "github.com/grpc/fern") + headers.Set("X-Fern-SDK-Version", "0.0.1") + return headers +} + +// BaseURLOption implements the RequestOption interface. +type BaseURLOption struct { + BaseURL string +} + +func (b *BaseURLOption) applyRequestOptions(opts *RequestOptions) { + opts.BaseURL = b.BaseURL +} + +// HTTPClientOption implements the RequestOption interface. +type HTTPClientOption struct { + HTTPClient HTTPClient +} + +func (h *HTTPClientOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPClient = h.HTTPClient +} + +// HTTPHeaderOption implements the RequestOption interface. +type HTTPHeaderOption struct { + HTTPHeader http.Header +} + +func (h *HTTPHeaderOption) applyRequestOptions(opts *RequestOptions) { + opts.HTTPHeader = h.HTTPHeader +} + +// MaxAttemptsOption implements the RequestOption interface. +type MaxAttemptsOption struct { + MaxAttempts uint +} + +func (m *MaxAttemptsOption) applyRequestOptions(opts *RequestOptions) { + opts.MaxAttempts = m.MaxAttempts +} diff --git a/seed/go-sdk/grpc/core/retrier.go b/seed/go-sdk/grpc/core/retrier.go new file mode 100644 index 00000000000..ea24916b786 --- /dev/null +++ b/seed/go-sdk/grpc/core/retrier.go @@ -0,0 +1,166 @@ +package core + +import ( + "crypto/rand" + "math/big" + "net/http" + "time" +) + +const ( + defaultRetryAttempts = 2 + minRetryDelay = 500 * time.Millisecond + maxRetryDelay = 5000 * time.Millisecond +) + +// RetryOption adapts the behavior the *Retrier. +type RetryOption func(*retryOptions) + +// RetryFunc is a retriable HTTP function call (i.e. *http.Client.Do). +type RetryFunc func(*http.Request) (*http.Response, error) + +// WithMaxAttempts configures the maximum number of attempts +// of the *Retrier. +func WithMaxAttempts(attempts uint) RetryOption { + return func(opts *retryOptions) { + opts.attempts = attempts + } +} + +// Retrier retries failed requests a configurable number of times with an +// exponential back-off between each retry. +type Retrier struct { + attempts uint +} + +// NewRetrier constructs a new *Retrier with the given options, if any. +func NewRetrier(opts ...RetryOption) *Retrier { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + attempts := uint(defaultRetryAttempts) + if options.attempts > 0 { + attempts = options.attempts + } + return &Retrier{ + attempts: attempts, + } +} + +// Run issues the request and, upon failure, retries the request if possible. +// +// The request will be retried as long as the request is deemed retriable and the +// number of retry attempts has not grown larger than the configured retry limit. +func (r *Retrier) Run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + opts ...RetryOption, +) (*http.Response, error) { + options := new(retryOptions) + for _, opt := range opts { + opt(options) + } + maxRetryAttempts := r.attempts + if options.attempts > 0 { + maxRetryAttempts = options.attempts + } + var ( + retryAttempt uint + previousError error + ) + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt, + previousError, + ) +} + +func (r *Retrier) run( + fn RetryFunc, + request *http.Request, + errorDecoder ErrorDecoder, + maxRetryAttempts uint, + retryAttempt uint, + previousError error, +) (*http.Response, error) { + if retryAttempt >= maxRetryAttempts { + return nil, previousError + } + + // If the call has been cancelled, don't issue the request. + if err := request.Context().Err(); err != nil { + return nil, err + } + + response, err := fn(request) + if err != nil { + return nil, err + } + + if r.shouldRetry(response) { + defer response.Body.Close() + + delay, err := r.retryDelay(retryAttempt) + if err != nil { + return nil, err + } + + time.Sleep(delay) + + return r.run( + fn, + request, + errorDecoder, + maxRetryAttempts, + retryAttempt+1, + decodeError(response, errorDecoder), + ) + } + + return response, nil +} + +// shouldRetry returns true if the request should be retried based on the given +// response status code. +func (r *Retrier) shouldRetry(response *http.Response) bool { + return response.StatusCode == http.StatusTooManyRequests || + response.StatusCode == http.StatusRequestTimeout || + response.StatusCode == http.StatusConflict || + response.StatusCode >= http.StatusInternalServerError +} + +// retryDelay calculates the delay time in milliseconds based on the retry attempt. +func (r *Retrier) retryDelay(retryAttempt uint) (time.Duration, error) { + // Apply exponential backoff. + delay := minRetryDelay + minRetryDelay*time.Duration(retryAttempt*retryAttempt) + + // Do not allow the number to exceed maxRetryDelay. + if delay > maxRetryDelay { + delay = maxRetryDelay + } + + // Apply some itter by randomizing the value in the range of 75%-100%. + max := big.NewInt(int64(delay / 4)) + jitter, err := rand.Int(rand.Reader, max) + if err != nil { + return 0, err + } + + delay -= time.Duration(jitter.Int64()) + + // Never sleep less than the base sleep seconds. + if delay < minRetryDelay { + delay = minRetryDelay + } + + return delay, nil +} + +type retryOptions struct { + attempts uint +} diff --git a/seed/go-sdk/grpc/core/stringer.go b/seed/go-sdk/grpc/core/stringer.go new file mode 100644 index 00000000000..000cf448641 --- /dev/null +++ b/seed/go-sdk/grpc/core/stringer.go @@ -0,0 +1,13 @@ +package core + +import "encoding/json" + +// StringifyJSON returns a pretty JSON string representation of +// the given value. +func StringifyJSON(value interface{}) (string, error) { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + return "", err + } + return string(bytes), nil +} diff --git a/seed/go-sdk/grpc/core/time.go b/seed/go-sdk/grpc/core/time.go new file mode 100644 index 00000000000..d009ab30c90 --- /dev/null +++ b/seed/go-sdk/grpc/core/time.go @@ -0,0 +1,137 @@ +package core + +import ( + "encoding/json" + "time" +) + +const dateFormat = "2006-01-02" + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date (e.g. 2006-01-02). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type Date struct { + t *time.Time +} + +// NewDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewDate(t time.Time) *Date { + return &Date{t: &t} +} + +// NewOptionalDate returns a new *Date. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDate(t *time.Time) *Date { + if t == nil { + return nil + } + return &Date{t: t} +} + +// Time returns the Date's underlying time, if any. If the +// date is nil, the zero value is returned. +func (d *Date) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the Date's underlying time.Time, if any. +func (d *Date) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *Date) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(dateFormat)) +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(dateFormat, raw) + if err != nil { + return err + } + + *d = Date{t: &parsedTime} + return nil +} + +// DateTime wraps time.Time and adapts its JSON representation +// to conform to a RFC3339 date-time (e.g. 2017-07-21T17:32:28Z). +// +// Ref: https://ijmacd.github.io/rfc3339-iso8601 +type DateTime struct { + t *time.Time +} + +// NewDateTime returns a new *DateTime. +func NewDateTime(t time.Time) *DateTime { + return &DateTime{t: &t} +} + +// NewOptionalDateTime returns a new *DateTime. If the given time.Time +// is nil, nil will be returned. +func NewOptionalDateTime(t *time.Time) *DateTime { + if t == nil { + return nil + } + return &DateTime{t: t} +} + +// Time returns the DateTime's underlying time, if any. If the +// date-time is nil, the zero value is returned. +func (d *DateTime) Time() time.Time { + if d == nil || d.t == nil { + return time.Time{} + } + return *d.t +} + +// TimePtr returns a pointer to the DateTime's underlying time.Time, if any. +func (d *DateTime) TimePtr() *time.Time { + if d == nil || d.t == nil { + return nil + } + if d.t.IsZero() { + return nil + } + return d.t +} + +func (d *DateTime) MarshalJSON() ([]byte, error) { + if d == nil || d.t == nil { + return nil, nil + } + return json.Marshal(d.t.Format(time.RFC3339)) +} + +func (d *DateTime) UnmarshalJSON(data []byte) error { + var raw string + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + parsedTime, err := time.Parse(time.RFC3339, raw) + if err != nil { + return err + } + + *d = DateTime{t: &parsedTime} + return nil +} diff --git a/seed/go-sdk/grpc/go.mod b/seed/go-sdk/grpc/go.mod new file mode 100644 index 00000000000..e4377514b34 --- /dev/null +++ b/seed/go-sdk/grpc/go.mod @@ -0,0 +1,9 @@ +module github.com/grpc/fern + +go 1.13 + +require ( + github.com/google/uuid v1.4.0 + github.com/stretchr/testify v1.7.0 + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/seed/go-sdk/grpc/go.sum b/seed/go-sdk/grpc/go.sum new file mode 100644 index 00000000000..b3766d4366b --- /dev/null +++ b/seed/go-sdk/grpc/go.sum @@ -0,0 +1,14 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/seed/go-sdk/grpc/option/request_option.go b/seed/go-sdk/grpc/option/request_option.go new file mode 100644 index 00000000000..c64533f7205 --- /dev/null +++ b/seed/go-sdk/grpc/option/request_option.go @@ -0,0 +1,41 @@ +// This file was auto-generated by Fern from our API Definition. + +package option + +import ( + core "github.com/grpc/fern/core" + http "net/http" +) + +// RequestOption adapts the behavior of an indivdual request. +type RequestOption = core.RequestOption + +// WithBaseURL sets the base URL, overriding the default +// environment, if any. +func WithBaseURL(baseURL string) *core.BaseURLOption { + return &core.BaseURLOption{ + BaseURL: baseURL, + } +} + +// WithHTTPClient uses the given HTTPClient to issue the request. +func WithHTTPClient(httpClient core.HTTPClient) *core.HTTPClientOption { + return &core.HTTPClientOption{ + HTTPClient: httpClient, + } +} + +// WithHTTPHeader adds the given http.Header to the request. +func WithHTTPHeader(httpHeader http.Header) *core.HTTPHeaderOption { + return &core.HTTPHeaderOption{ + // Clone the headers so they can't be modified after the option call. + HTTPHeader: httpHeader.Clone(), + } +} + +// WithMaxAttempts configures the maximum number of retry attempts. +func WithMaxAttempts(attempts uint) *core.MaxAttemptsOption { + return &core.MaxAttemptsOption{ + MaxAttempts: attempts, + } +} diff --git a/seed/go-sdk/grpc/pointer.go b/seed/go-sdk/grpc/pointer.go new file mode 100644 index 00000000000..faaf462e6d0 --- /dev/null +++ b/seed/go-sdk/grpc/pointer.go @@ -0,0 +1,132 @@ +package api + +import ( + "time" + + "github.com/google/uuid" +) + +// Bool returns a pointer to the given bool value. +func Bool(b bool) *bool { + return &b +} + +// Byte returns a pointer to the given byte value. +func Byte(b byte) *byte { + return &b +} + +// Complex64 returns a pointer to the given complex64 value. +func Complex64(c complex64) *complex64 { + return &c +} + +// Complex128 returns a pointer to the given complex128 value. +func Complex128(c complex128) *complex128 { + return &c +} + +// Float32 returns a pointer to the given float32 value. +func Float32(f float32) *float32 { + return &f +} + +// Float64 returns a pointer to the given float64 value. +func Float64(f float64) *float64 { + return &f +} + +// Int returns a pointer to the given int value. +func Int(i int) *int { + return &i +} + +// Int8 returns a pointer to the given int8 value. +func Int8(i int8) *int8 { + return &i +} + +// Int16 returns a pointer to the given int16 value. +func Int16(i int16) *int16 { + return &i +} + +// Int32 returns a pointer to the given int32 value. +func Int32(i int32) *int32 { + return &i +} + +// Int64 returns a pointer to the given int64 value. +func Int64(i int64) *int64 { + return &i +} + +// Rune returns a pointer to the given rune value. +func Rune(r rune) *rune { + return &r +} + +// String returns a pointer to the given string value. +func String(s string) *string { + return &s +} + +// Uint returns a pointer to the given uint value. +func Uint(u uint) *uint { + return &u +} + +// Uint8 returns a pointer to the given uint8 value. +func Uint8(u uint8) *uint8 { + return &u +} + +// Uint16 returns a pointer to the given uint16 value. +func Uint16(u uint16) *uint16 { + return &u +} + +// Uint32 returns a pointer to the given uint32 value. +func Uint32(u uint32) *uint32 { + return &u +} + +// Uint64 returns a pointer to the given uint64 value. +func Uint64(u uint64) *uint64 { + return &u +} + +// Uintptr returns a pointer to the given uintptr value. +func Uintptr(u uintptr) *uintptr { + return &u +} + +// UUID returns a pointer to the given uuid.UUID value. +func UUID(u uuid.UUID) *uuid.UUID { + return &u +} + +// Time returns a pointer to the given time.Time value. +func Time(t time.Time) *time.Time { + return &t +} + +// MustParseDate attempts to parse the given string as a +// date time.Time, and panics upon failure. +func MustParseDate(date string) time.Time { + t, err := time.Parse("2006-01-02", date) + if err != nil { + panic(err) + } + return t +} + +// MustParseDateTime attempts to parse the given string as a +// datetime time.Time, and panics upon failure. +func MustParseDateTime(datetime string) time.Time { + t, err := time.Parse(time.RFC3339, datetime) + if err != nil { + panic(err) + } + return t +} diff --git a/seed/go-sdk/grpc/snippet-templates.json b/seed/go-sdk/grpc/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/go-sdk/grpc/snippet.json b/seed/go-sdk/grpc/snippet.json new file mode 100644 index 00000000000..41334b6c0fb --- /dev/null +++ b/seed/go-sdk/grpc/snippet.json @@ -0,0 +1,26 @@ +{ + "endpoints": [ + { + "id": { + "path": "/users", + "method": "GET", + "identifier_override": "endpoint_user.getUser" + }, + "snippet": { + "type": "go", + "client": "import (\n\tcontext \"context\"\n\tfern \"github.com/grpc/fern\"\n\tfernclient \"github.com/grpc/fern/client\"\n)\n\nclient := fernclient.NewClient()\nresponse, err := client.User.GetUser(\n\tcontext.TODO(),\n\t\u0026fern.GetUserRequest{\n\t\tUsername: fern.String(\n\t\t\t\"string\",\n\t\t),\n\t\tAge: fern.Int(\n\t\t\t1,\n\t\t),\n\t\tWeight: fern.Float64(\n\t\t\t1.1,\n\t\t),\n\t},\n)\n" + } + }, + { + "id": { + "path": "/users", + "method": "POST", + "identifier_override": "endpoint_user.createUser" + }, + "snippet": { + "type": "go", + "client": "import (\n\tcontext \"context\"\n\tfern \"github.com/grpc/fern\"\n\tfernclient \"github.com/grpc/fern/client\"\n)\n\nclient := fernclient.NewClient()\nresponse, err := client.User.CreateUser(\n\tcontext.TODO(),\n\t\u0026fern.CreateUserRequest{\n\t\tUsername: \"string\",\n\t\tEmail: fern.String(\n\t\t\t\"string\",\n\t\t),\n\t\tAge: fern.Int(\n\t\t\t1,\n\t\t),\n\t\tWeight: fern.Float64(\n\t\t\t1.1,\n\t\t),\n\t},\n)\n" + } + } + ] +} \ No newline at end of file diff --git a/seed/go-sdk/grpc/types.go b/seed/go-sdk/grpc/types.go new file mode 100644 index 00000000000..52c62abb64a --- /dev/null +++ b/seed/go-sdk/grpc/types.go @@ -0,0 +1,96 @@ +// This file was auto-generated by Fern from our API Definition. + +package api + +import ( + json "encoding/json" + fmt "fmt" +) + +type Metadata = map[string]*MetadataValue + +type MetadataValue struct { + Double float64 + String string + Boolean bool + MetadataValueList []*MetadataValue +} + +func NewMetadataValueFromDouble(value float64) *MetadataValue { + return &MetadataValue{Double: value} +} + +func NewMetadataValueFromString(value string) *MetadataValue { + return &MetadataValue{String: value} +} + +func NewMetadataValueFromBoolean(value bool) *MetadataValue { + return &MetadataValue{Boolean: value} +} + +func NewMetadataValueFromMetadataValueList(value []*MetadataValue) *MetadataValue { + return &MetadataValue{MetadataValueList: value} +} + +func (m *MetadataValue) UnmarshalJSON(data []byte) error { + var valueDouble float64 + if err := json.Unmarshal(data, &valueDouble); err == nil { + m.Double = valueDouble + return nil + } + var valueString string + if err := json.Unmarshal(data, &valueString); err == nil { + m.String = valueString + return nil + } + var valueBoolean bool + if err := json.Unmarshal(data, &valueBoolean); err == nil { + m.Boolean = valueBoolean + return nil + } + var valueMetadataValueList []*MetadataValue + if err := json.Unmarshal(data, &valueMetadataValueList); err == nil { + m.MetadataValueList = valueMetadataValueList + return nil + } + return fmt.Errorf("%s cannot be deserialized as a %T", data, m) +} + +func (m MetadataValue) MarshalJSON() ([]byte, error) { + if m.Double != 0 { + return json.Marshal(m.Double) + } + if m.String != "" { + return json.Marshal(m.String) + } + if m.Boolean != false { + return json.Marshal(m.Boolean) + } + if m.MetadataValueList != nil { + return json.Marshal(m.MetadataValueList) + } + return nil, fmt.Errorf("type %T does not include a non-empty union type", m) +} + +type MetadataValueVisitor interface { + VisitDouble(float64) error + VisitString(string) error + VisitBoolean(bool) error + VisitMetadataValueList([]*MetadataValue) error +} + +func (m *MetadataValue) Accept(visitor MetadataValueVisitor) error { + if m.Double != 0 { + return visitor.VisitDouble(m.Double) + } + if m.String != "" { + return visitor.VisitString(m.String) + } + if m.Boolean != false { + return visitor.VisitBoolean(m.Boolean) + } + if m.MetadataValueList != nil { + return visitor.VisitMetadataValueList(m.MetadataValueList) + } + return fmt.Errorf("type %T does not include a non-empty union type", m) +} diff --git a/seed/go-sdk/grpc/user.go b/seed/go-sdk/grpc/user.go new file mode 100644 index 00000000000..5d6876ced5b --- /dev/null +++ b/seed/go-sdk/grpc/user.go @@ -0,0 +1,109 @@ +// This file was auto-generated by Fern from our API Definition. + +package api + +import ( + json "encoding/json" + fmt "fmt" + core "github.com/grpc/fern/core" +) + +type CreateUserRequest struct { + Username string `json:"username" url:"-"` + Email *string `json:"email,omitempty" url:"-"` + Age *int `json:"age,omitempty" url:"-"` + Weight *float64 `json:"weight,omitempty" url:"-"` +} + +type GetUserRequest struct { + Username *string `json:"-" url:"username,omitempty"` + Age *int `json:"-" url:"age,omitempty"` + Weight *float64 `json:"-" url:"weight,omitempty"` +} + +type CreateUserResponse struct { + User *User `json:"user,omitempty" url:"user,omitempty"` + + extraProperties map[string]interface{} + _rawJSON json.RawMessage +} + +func (c *CreateUserResponse) GetExtraProperties() map[string]interface{} { + return c.extraProperties +} + +func (c *CreateUserResponse) UnmarshalJSON(data []byte) error { + type unmarshaler CreateUserResponse + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *c = CreateUserResponse(value) + + extraProperties, err := core.ExtractExtraProperties(data, *c) + if err != nil { + return err + } + c.extraProperties = extraProperties + + c._rawJSON = json.RawMessage(data) + return nil +} + +func (c *CreateUserResponse) String() string { + if len(c._rawJSON) > 0 { + if value, err := core.StringifyJSON(c._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(c); err == nil { + return value + } + return fmt.Sprintf("%#v", c) +} + +type User struct { + Id string `json:"id" url:"id"` + Username string `json:"username" url:"username"` + Email *string `json:"email,omitempty" url:"email,omitempty"` + Age *int `json:"age,omitempty" url:"age,omitempty"` + Weight *float64 `json:"weight,omitempty" url:"weight,omitempty"` + Metadata *Metadata `json:"metadata,omitempty" url:"metadata,omitempty"` + + extraProperties map[string]interface{} + _rawJSON json.RawMessage +} + +func (u *User) GetExtraProperties() map[string]interface{} { + return u.extraProperties +} + +func (u *User) UnmarshalJSON(data []byte) error { + type unmarshaler User + var value unmarshaler + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *u = User(value) + + extraProperties, err := core.ExtractExtraProperties(data, *u) + if err != nil { + return err + } + u.extraProperties = extraProperties + + u._rawJSON = json.RawMessage(data) + return nil +} + +func (u *User) String() string { + if len(u._rawJSON) > 0 { + if value, err := core.StringifyJSON(u._rawJSON); err == nil { + return value + } + } + if value, err := core.StringifyJSON(u); err == nil { + return value + } + return fmt.Sprintf("%#v", u) +} diff --git a/seed/go-sdk/grpc/user/client.go b/seed/go-sdk/grpc/user/client.go new file mode 100644 index 00000000000..4220d03fb13 --- /dev/null +++ b/seed/go-sdk/grpc/user/client.go @@ -0,0 +1,110 @@ +// This file was auto-generated by Fern from our API Definition. + +package user + +import ( + context "context" + fern "github.com/grpc/fern" + core "github.com/grpc/fern/core" + option "github.com/grpc/fern/option" + http "net/http" +) + +type Client struct { + baseURL string + caller *core.Caller + header http.Header +} + +func NewClient(opts ...option.RequestOption) *Client { + options := core.NewRequestOptions(opts...) + return &Client{ + baseURL: options.BaseURL, + caller: core.NewCaller( + &core.CallerParams{ + Client: options.HTTPClient, + MaxAttempts: options.MaxAttempts, + }, + ), + header: options.ToHeader(), + } +} + +func (c *Client) CreateUser( + ctx context.Context, + request *fern.CreateUserRequest, + opts ...option.RequestOption, +) (*fern.CreateUserResponse, error) { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := baseURL + "/users" + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + var response *fern.CreateUserResponse + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodPost, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Request: request, + Response: &response, + }, + ); err != nil { + return nil, err + } + return response, nil +} + +func (c *Client) GetUser( + ctx context.Context, + request *fern.GetUserRequest, + opts ...option.RequestOption, +) (*fern.User, error) { + options := core.NewRequestOptions(opts...) + + baseURL := "" + if c.baseURL != "" { + baseURL = c.baseURL + } + if options.BaseURL != "" { + baseURL = options.BaseURL + } + endpointURL := baseURL + "/users" + + queryParams, err := core.QueryValues(request) + if err != nil { + return nil, err + } + if len(queryParams) > 0 { + endpointURL += "?" + queryParams.Encode() + } + + headers := core.MergeHeaders(c.header.Clone(), options.ToHeader()) + + var response *fern.User + if err := c.caller.Call( + ctx, + &core.CallParams{ + URL: endpointURL, + Method: http.MethodGet, + MaxAttempts: options.MaxAttempts, + Headers: headers, + Client: options.HTTPClient, + Response: &response, + }, + ); err != nil { + return nil, err + } + return response, nil +} diff --git a/seed/go-sdk/seed.yml b/seed/go-sdk/seed.yml index c77c0e42066..3132e8d9f50 100644 --- a/seed/go-sdk/seed.yml +++ b/seed/go-sdk/seed.yml @@ -5,6 +5,12 @@ language: go generatorType: sdk defaultOutputMode: github fixtures: + examples: + - outputFolder: no-custom-config + customConfig: null + - outputFolder: always-send-required-properties + customConfig: + alwaysSendRequiredProperties: true undiscriminated-unions: - outputFolder: . outputVersion: 0.0.1 diff --git a/seed/java-model/grpc-proto/.github/workflows/ci.yml b/seed/java-model/grpc-proto/.github/workflows/ci.yml new file mode 100644 index 00000000000..8598a73092a --- /dev/null +++ b/seed/java-model/grpc-proto/.github/workflows/ci.yml @@ -0,0 +1,61 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up Java + id: setup-jre + uses: actions/setup-java@v1 + with: + java-version: "11" + architecture: x64 + + - name: Compile + run: ./gradlew compileJava + + test: + needs: [ compile ] + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up Java + id: setup-jre + uses: actions/setup-java@v1 + with: + java-version: "11" + architecture: x64 + + - name: Test + run: ./gradlew test + publish: + needs: [ compile, test ] + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up Java + id: setup-jre + uses: actions/setup-java@v1 + with: + java-version: "11" + architecture: x64 + + - name: Publish to maven + run: | + ./gradlew publish + env: + MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} + MAVEN_PUBLISH_REGISTRY_URL: "" diff --git a/seed/java-model/grpc-proto/.gitignore b/seed/java-model/grpc-proto/.gitignore new file mode 100644 index 00000000000..d4199abc2cd --- /dev/null +++ b/seed/java-model/grpc-proto/.gitignore @@ -0,0 +1,24 @@ +*.class +.project +.gradle +? +.classpath +.checkstyle +.settings +.node +build + +# IntelliJ +*.iml +*.ipr +*.iws +.idea/ +out/ + +# Eclipse/IntelliJ APT +generated_src/ +generated_testSrc/ +generated/ + +bin +build \ No newline at end of file diff --git a/seed/java-model/grpc-proto/.mock/fern.config.json b/seed/java-model/grpc-proto/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/java-model/grpc-proto/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/java-model/grpc-proto/.mock/generators.yml b/seed/java-model/grpc-proto/.mock/generators.yml new file mode 100644 index 00000000000..902c4f50168 --- /dev/null +++ b/seed/java-model/grpc-proto/.mock/generators.yml @@ -0,0 +1,5 @@ +api: + - proto: + root: proto + target: proto/user/v1/user.proto + local-generation: true \ No newline at end of file diff --git a/seed/java-model/grpc-proto/.mock/proto/google/api/annotations.proto b/seed/java-model/grpc-proto/.mock/proto/google/api/annotations.proto new file mode 100644 index 00000000000..8ff42098404 --- /dev/null +++ b/seed/java-model/grpc-proto/.mock/proto/google/api/annotations.proto @@ -0,0 +1,31 @@ +// Copyright 2015 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/api/http.proto"; +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "AnnotationsProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // See `HttpRule`. + HttpRule http = 72295728; +} \ No newline at end of file diff --git a/seed/java-model/grpc-proto/.mock/proto/google/api/field_behavior.proto b/seed/java-model/grpc-proto/.mock/proto/google/api/field_behavior.proto new file mode 100644 index 00000000000..128799c558d --- /dev/null +++ b/seed/java-model/grpc-proto/.mock/proto/google/api/field_behavior.proto @@ -0,0 +1,104 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "FieldBehaviorProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.FieldOptions { + // A designation of a specific field behavior (required, output only, etc.) + // in protobuf messages. + // + // Examples: + // + // string name = 1 [(google.api.field_behavior) = REQUIRED]; + // State state = 1 [(google.api.field_behavior) = OUTPUT_ONLY]; + // google.protobuf.Duration ttl = 1 + // [(google.api.field_behavior) = INPUT_ONLY]; + // google.protobuf.Timestamp expire_time = 1 + // [(google.api.field_behavior) = OUTPUT_ONLY, + // (google.api.field_behavior) = IMMUTABLE]; + repeated google.api.FieldBehavior field_behavior = 1052; +} + +// An indicator of the behavior of a given field (for example, that a field +// is required in requests, or given as output but ignored as input). +// This **does not** change the behavior in protocol buffers itself; it only +// denotes the behavior and may affect how API tooling handles the field. +// +// Note: This enum **may** receive new values in the future. +enum FieldBehavior { + // Conventional default for enums. Do not use this. + FIELD_BEHAVIOR_UNSPECIFIED = 0; + + // Specifically denotes a field as optional. + // While all fields in protocol buffers are optional, this may be specified + // for emphasis if appropriate. + OPTIONAL = 1; + + // Denotes a field as required. + // This indicates that the field **must** be provided as part of the request, + // and failure to do so will cause an error (usually `INVALID_ARGUMENT`). + REQUIRED = 2; + + // Denotes a field as output only. + // This indicates that the field is provided in responses, but including the + // field in a request does nothing (the server *must* ignore it and + // *must not* throw an error as a result of the field's presence). + OUTPUT_ONLY = 3; + + // Denotes a field as input only. + // This indicates that the field is provided in requests, and the + // corresponding field is not included in output. + INPUT_ONLY = 4; + + // Denotes a field as immutable. + // This indicates that the field may be set once in a request to create a + // resource, but may not be changed thereafter. + IMMUTABLE = 5; + + // Denotes that a (repeated) field is an unordered list. + // This indicates that the service may provide the elements of the list + // in any arbitrary order, rather than the order the user originally + // provided. Additionally, the list's order may or may not be stable. + UNORDERED_LIST = 6; + + // Denotes that this field returns a non-empty default value if not set. + // This indicates that if the user provides the empty value in a request, + // a non-empty value will be returned. The user will not be aware of what + // non-empty value to expect. + NON_EMPTY_DEFAULT = 7; + + // Denotes that the field in a resource (a message annotated with + // google.api.resource) is used in the resource name to uniquely identify the + // resource. For AIP-compliant APIs, this should only be applied to the + // `name` field on the resource. + // + // This behavior should not be applied to references to other resources within + // the message. + // + // The identifier field of resources often have different field behavior + // depending on the request it is embedded in (e.g. for Create methods name + // is optional and unused, while for Update methods it is required). Instead + // of method-specific annotations, only `IDENTIFIER` is required. + IDENTIFIER = 8; +} \ No newline at end of file diff --git a/seed/java-model/grpc-proto/.mock/proto/google/api/http.proto b/seed/java-model/grpc-proto/.mock/proto/google/api/http.proto new file mode 100644 index 00000000000..c8392381eb9 --- /dev/null +++ b/seed/java-model/grpc-proto/.mock/proto/google/api/http.proto @@ -0,0 +1,379 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "HttpProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Defines the HTTP configuration for an API service. It contains a list of +// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method +// to one or more HTTP REST API methods. +message Http { + // A list of HTTP configuration rules that apply to individual API methods. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated HttpRule rules = 1; + + // When set to true, URL path parameters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // + // The default behavior is to not decode RFC 6570 reserved characters in multi + // segment matches. + bool fully_decode_reserved_expansion = 2; +} + +// # gRPC Transcoding +// +// gRPC Transcoding is a feature for mapping between a gRPC method and one or +// more HTTP REST endpoints. It allows developers to build a single API service +// that supports both gRPC APIs and REST APIs. Many systems, including [Google +// APIs](https://github.com/googleapis/googleapis), +// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC +// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), +// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature +// and use it for large scale production services. +// +// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies +// how different portions of the gRPC request message are mapped to the URL +// path, URL query parameters, and HTTP request body. It also controls how the +// gRPC response message is mapped to the HTTP response body. `HttpRule` is +// typically specified as an `google.api.http` annotation on the gRPC method. +// +// Each mapping specifies a URL path template and an HTTP method. The path +// template may refer to one or more fields in the gRPC request message, as long +// as each field is a non-repeated field with a primitive (non-message) type. +// The path template controls how fields of the request message are mapped to +// the URL path. +// +// Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/{name=messages/*}" +// }; +// } +// } +// message GetMessageRequest { +// string name = 1; // Mapped to URL path. +// } +// message Message { +// string text = 1; // The resource content. +// } +// +// This enables an HTTP REST to gRPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` +// +// Any fields in the request message which are not bound by the path template +// automatically become HTTP query parameters if there is no HTTP request body. +// For example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get:"/v1/messages/{message_id}" +// }; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // Mapped to URL path. +// int64 revision = 2; // Mapped to URL query parameter `revision`. +// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. +// } +// +// This enables a HTTP JSON to RPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | +// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: +// "foo"))` +// +// Note that fields which are mapped to URL query parameters must have a +// primitive type or a repeated primitive type or a non-repeated message type. +// In the case of a repeated type, the parameter can be repeated in the URL +// as `...?param=A¶m=B`. In the case of a message type, each field of the +// message is mapped to a separate parameter, such as +// `...?foo.a=A&foo.b=B&foo.c=C`. +// +// For HTTP methods that allow a request body, the `body` field +// specifies the mapping. Consider a REST update method on the +// message resource collection: +// +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } +// +// The following HTTP JSON to RPC mapping is enabled, where the +// representation of the JSON in the request body is determined by +// protos JSON encoding: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" message { text: "Hi!" })` +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. This enables the following alternative definition of +// the update method: +// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } +// +// +// The following HTTP JSON to RPC mapping is enabled: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" text: "Hi!")` +// +// Note that when using `*` in the body mapping, it is not possible to +// have HTTP parameters, as all fields not bound by the path end in +// the body. This makes this option more rarely used in practice when +// defining REST APIs. The common usage of `*` is in custom methods +// which don't use the URL at all for transferring data. +// +// It is possible to define multiple HTTP methods for one RPC by using +// the `additional_bindings` option. Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } +// +// This enables the following two alternative HTTP JSON to RPC mappings: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` +// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: +// "123456")` +// +// ## Rules for HTTP mapping +// +// 1. Leaf request fields (recursive expansion nested messages in the request +// message) are classified into three categories: +// - Fields referred by the path template. They are passed via the URL path. +// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They +// are passed via the HTTP +// request body. +// - All other fields are passed via the URL query parameters, and the +// parameter name is the field path in the request message. A repeated +// field can be represented as multiple query parameters under the same +// name. +// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL +// query parameter, all fields +// are passed via URL path and HTTP request body. +// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP +// request body, all +// fields are passed via URL path and URL query parameters. +// +// ### Path template syntax +// +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +// +// The syntax `*` matches a single URL path segment. The syntax `**` matches +// zero or more URL path segments, which must be the last part of the URL path +// except the `Verb`. +// +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable +// matches a single path segment, its template may be omitted, e.g. `{var}` +// is equivalent to `{var=*}`. +// +// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` +// contains any reserved character, such characters should be percent-encoded +// before the matching. +// +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path on the client +// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The +// server side does the reverse decoding. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{var}`. +// +// If a variable contains multiple path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path on the +// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. +// The server side does the reverse decoding, except "%2F" and "%2f" are left +// unchanged. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{+var}`. +// +// ## Using gRPC API Service Configuration +// +// gRPC API Service Configuration (service config) is a configuration language +// for configuring a gRPC service to become a user-facing product. The +// service config is simply the YAML representation of the `google.api.Service` +// proto message. +// +// As an alternative to annotating your proto file, you can configure gRPC +// transcoding in your service config YAML files. You do this by specifying a +// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same +// effect as the proto annotation. This can be particularly useful if you +// have a proto that is reused in multiple services. Note that any transcoding +// specified in the service config will override any matching transcoding +// configuration in the proto. +// +// Example: +// +// http: +// rules: +// # Selects a gRPC method and applies HttpRule to it. +// - selector: example.v1.Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} +// +// ## Special notes +// +// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the +// proto to JSON conversion must follow the [proto3 +// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). +// +// While the single segment variable follows the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String +// Expansion, the multi segment variable **does not** follow RFC 6570 Section +// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding +// for multi segment variables. +// +// The path variables **must not** refer to any repeated or mapped field, +// because client libraries are not capable of handling such variable expansion. +// +// The path variables **must not** capture the leading "/" character. The reason +// is that the most common use case "{var}" does not capture the leading "/" +// character. For consistency, all path variables must share the same behavior. +// +// Repeated message fields must not be mapped to URL query parameters, because +// no client library can support such complicated mapping. +// +// If an API needs to use a JSON array for request or response body, it can map +// the request or response body to a repeated field. However, some gRPC +// Transcoding implementations may not support this feature. +message HttpRule { + // Selects a method to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax + // details. + string selector = 1; + + // Determines the URL pattern is matched by this rules. This pattern can be + // used with any of the {get|put|post|delete|patch} methods. A custom method + // can be defined using the 'custom' field. + oneof pattern { + // Maps to HTTP GET. Used for listing and getting information about + // resources. + string get = 2; + + // Maps to HTTP PUT. Used for replacing a resource. + string put = 3; + + // Maps to HTTP POST. Used for creating a resource or performing an action. + string post = 4; + + // Maps to HTTP DELETE. Used for deleting a resource. + string delete = 5; + + // Maps to HTTP PATCH. Used for updating a resource. + string patch = 6; + + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. + CustomHttpPattern custom = 8; + } + + // The name of the request field whose value is mapped to the HTTP request + // body, or `*` for mapping all request fields not captured by the path + // pattern to the HTTP body, or omitted for not having any HTTP request body. + // + // NOTE: the referred field must be present at the top-level of the request + // message type. + string body = 7; + + // Optional. The name of the response field whose value is mapped to the HTTP + // response body. When omitted, the entire response message will be used + // as the HTTP response body. + // + // NOTE: The referred field must be present at the top-level of the response + // message type. + string response_body = 12; + + // Additional HTTP bindings for the selector. Nested bindings must + // not contain an `additional_bindings` field themselves (that is, + // the nesting may only be one level deep). + repeated HttpRule additional_bindings = 11; +} + +// A custom pattern is used for defining custom HTTP verb. +message CustomHttpPattern { + // The name of this custom HTTP verb. + string kind = 1; + + // The path matched by this custom verb. + string path = 2; +} \ No newline at end of file diff --git a/seed/java-model/grpc-proto/.mock/proto/user/v1/user.proto b/seed/java-model/grpc-proto/.mock/proto/user/v1/user.proto new file mode 100644 index 00000000000..a3ad0195bd4 --- /dev/null +++ b/seed/java-model/grpc-proto/.mock/proto/user/v1/user.proto @@ -0,0 +1,39 @@ +syntax = "proto3"; + +package user.v1; + +import "google/api/annotations.proto"; +import "google/api/field_behavior.proto"; +import "google/protobuf/struct.proto"; + +option go_package = "user/v1"; +option csharp_namespace = "User.V1"; + +message UserModel { + string username = 1; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateRequest { + string username = 1; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateResponse { + UserModel user = 1; +} + +service User { + rpc Create(CreateRequest) returns (CreateResponse) { + option (google.api.http) = { + post: "/users" + body: "*" + }; + } +} \ No newline at end of file diff --git a/seed/java-model/grpc-proto/build.gradle b/seed/java-model/grpc-proto/build.gradle new file mode 100644 index 00000000000..b5e43b33174 --- /dev/null +++ b/seed/java-model/grpc-proto/build.gradle @@ -0,0 +1,67 @@ +plugins { + id 'java-library' + id 'maven-publish' + id 'com.diffplug.spotless' version '6.11.0' +} + +repositories { + mavenCentral() + maven { + url 'https://s01.oss.sonatype.org/content/repositories/releases/' + } +} + +dependencies { + api 'com.fasterxml.jackson.core:jackson-databind:2.13.0' + api 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.12.3' + api 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.12.3' +} + + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +spotless { + java { + palantirJavaFormat() + } +} + +java { + withSourcesJar() + withJavadocJar() +} + +test { + useJUnitPlatform() + testLogging { + showStandardStreams = true + } +} +publishing { + publications { + maven(MavenPublication) { + groupId = 'com.fern' + artifactId = 'grpc-proto' + version = '0.0.1' + from components.java + pom { + scm { + connection = 'scm:git:git://github.com/grpc-proto/fern.git' + developerConnection = 'scm:git:git://github.com/grpc-proto/fern.git' + url = 'https://github.com/grpc-proto/fern' + } + } + } + } + repositories { + maven { + url "$System.env.MAVEN_PUBLISH_REGISTRY_URL" + credentials { + username "$System.env.MAVEN_USERNAME" + password "$System.env.MAVEN_PASSWORD" + } + } + } +} + diff --git a/seed/java-model/grpc-proto/settings.gradle b/seed/java-model/grpc-proto/settings.gradle new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/java-model/grpc-proto/snippet-templates.json b/seed/java-model/grpc-proto/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/java-model/grpc-proto/snippet.json b/seed/java-model/grpc-proto/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/java-model/grpc-proto/src/main/java/com/seed/api/core/DateTimeDeserializer.java b/seed/java-model/grpc-proto/src/main/java/com/seed/api/core/DateTimeDeserializer.java new file mode 100644 index 00000000000..eac7d50c71a --- /dev/null +++ b/seed/java-model/grpc-proto/src/main/java/com/seed/api/core/DateTimeDeserializer.java @@ -0,0 +1,55 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.module.SimpleModule; +import java.io.IOException; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalQueries; + +/** + * Custom deserializer that handles converting ISO8601 dates into {@link OffsetDateTime} objects. + */ +class DateTimeDeserializer extends JsonDeserializer { + private static final SimpleModule MODULE; + + static { + MODULE = new SimpleModule().addDeserializer(OffsetDateTime.class, new DateTimeDeserializer()); + } + + /** + * Gets a module wrapping this deserializer as an adapter for the Jackson ObjectMapper. + * + * @return A {@link SimpleModule} to be plugged onto Jackson ObjectMapper. + */ + public static SimpleModule getModule() { + return MODULE; + } + + @Override + public OffsetDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException { + JsonToken token = parser.currentToken(); + if (token == JsonToken.VALUE_NUMBER_INT) { + return OffsetDateTime.ofInstant(Instant.ofEpochSecond(parser.getValueAsLong()), ZoneOffset.UTC); + } else { + TemporalAccessor temporal = DateTimeFormatter.ISO_DATE_TIME.parseBest( + parser.getValueAsString(), OffsetDateTime::from, LocalDateTime::from); + + if (temporal.query(TemporalQueries.offset()) == null) { + return LocalDateTime.from(temporal).atOffset(ZoneOffset.UTC); + } else { + return OffsetDateTime.from(temporal); + } + } + } +} diff --git a/seed/java-model/grpc-proto/src/main/java/com/seed/api/core/ObjectMappers.java b/seed/java-model/grpc-proto/src/main/java/com/seed/api/core/ObjectMappers.java new file mode 100644 index 00000000000..0b16d472dca --- /dev/null +++ b/seed/java-model/grpc-proto/src/main/java/com/seed/api/core/ObjectMappers.java @@ -0,0 +1,36 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import java.io.IOException; + +public final class ObjectMappers { + public static final ObjectMapper JSON_MAPPER = JsonMapper.builder() + .addModule(new Jdk8Module()) + .addModule(new JavaTimeModule()) + .addModule(DateTimeDeserializer.getModule()) + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .build(); + + private ObjectMappers() {} + + public static String stringify(Object o) { + try { + return JSON_MAPPER + .setSerializationInclusion(JsonInclude.Include.ALWAYS) + .writerWithDefaultPrettyPrinter() + .writeValueAsString(o); + } catch (IOException e) { + return o.getClass().getName() + "@" + Integer.toHexString(o.hashCode()); + } + } +} diff --git a/seed/java-model/grpc-proto/src/main/java/com/seed/api/model/CreateResponse.java b/seed/java-model/grpc-proto/src/main/java/com/seed/api/model/CreateResponse.java new file mode 100644 index 00000000000..74762392847 --- /dev/null +++ b/seed/java-model/grpc-proto/src/main/java/com/seed/api/model/CreateResponse.java @@ -0,0 +1,80 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = CreateResponse.Builder.class) +public final class CreateResponse { + private final Optional user; + + private CreateResponse(Optional user) { + this.user = user; + } + + @JsonProperty("user") + public Optional getUser() { + return user; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof CreateResponse && equalTo((CreateResponse) other); + } + + private boolean equalTo(CreateResponse other) { + return user.equals(other.user); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.user); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + private Optional user = Optional.empty(); + + private Builder() {} + + public Builder from(CreateResponse other) { + user(other.getUser()); + return this; + } + + @JsonSetter(value = "user", nulls = Nulls.SKIP) + public Builder user(Optional user) { + this.user = user; + return this; + } + + public Builder user(UserModel user) { + this.user = Optional.ofNullable(user); + return this; + } + + public CreateResponse build() { + return new CreateResponse(user); + } + } +} diff --git a/seed/java-model/grpc-proto/src/main/java/com/seed/api/model/UserModel.java b/seed/java-model/grpc-proto/src/main/java/com/seed/api/model/UserModel.java new file mode 100644 index 00000000000..8639e6a8654 --- /dev/null +++ b/seed/java-model/grpc-proto/src/main/java/com/seed/api/model/UserModel.java @@ -0,0 +1,178 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = UserModel.Builder.class) +public final class UserModel { + private final Optional username; + + private final Optional email; + + private final Optional age; + + private final Optional weight; + + private final Optional> metadata; + + private UserModel( + Optional username, + Optional email, + Optional age, + Optional weight, + Optional> metadata) { + this.username = username; + this.email = email; + this.age = age; + this.weight = weight; + this.metadata = metadata; + } + + @JsonProperty("username") + public Optional getUsername() { + return username; + } + + @JsonProperty("email") + public Optional getEmail() { + return email; + } + + @JsonProperty("age") + public Optional getAge() { + return age; + } + + @JsonProperty("weight") + public Optional getWeight() { + return weight; + } + + @JsonProperty("metadata") + public Optional> getMetadata() { + return metadata; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof UserModel && equalTo((UserModel) other); + } + + private boolean equalTo(UserModel other) { + return username.equals(other.username) + && email.equals(other.email) + && age.equals(other.age) + && weight.equals(other.weight) + && metadata.equals(other.metadata); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.username, this.email, this.age, this.weight, this.metadata); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + private Optional username = Optional.empty(); + + private Optional email = Optional.empty(); + + private Optional age = Optional.empty(); + + private Optional weight = Optional.empty(); + + private Optional> metadata = Optional.empty(); + + private Builder() {} + + public Builder from(UserModel other) { + username(other.getUsername()); + email(other.getEmail()); + age(other.getAge()); + weight(other.getWeight()); + metadata(other.getMetadata()); + return this; + } + + @JsonSetter(value = "username", nulls = Nulls.SKIP) + public Builder username(Optional username) { + this.username = username; + return this; + } + + public Builder username(String username) { + this.username = Optional.ofNullable(username); + return this; + } + + @JsonSetter(value = "email", nulls = Nulls.SKIP) + public Builder email(Optional email) { + this.email = email; + return this; + } + + public Builder email(String email) { + this.email = Optional.ofNullable(email); + return this; + } + + @JsonSetter(value = "age", nulls = Nulls.SKIP) + public Builder age(Optional age) { + this.age = age; + return this; + } + + public Builder age(Integer age) { + this.age = Optional.ofNullable(age); + return this; + } + + @JsonSetter(value = "weight", nulls = Nulls.SKIP) + public Builder weight(Optional weight) { + this.weight = weight; + return this; + } + + public Builder weight(Double weight) { + this.weight = Optional.ofNullable(weight); + return this; + } + + @JsonSetter(value = "metadata", nulls = Nulls.SKIP) + public Builder metadata(Optional> metadata) { + this.metadata = metadata; + return this; + } + + public Builder metadata(Map metadata) { + this.metadata = Optional.ofNullable(metadata); + return this; + } + + public UserModel build() { + return new UserModel(username, email, age, weight, metadata); + } + } +} diff --git a/seed/java-model/grpc/.github/workflows/ci.yml b/seed/java-model/grpc/.github/workflows/ci.yml new file mode 100644 index 00000000000..8598a73092a --- /dev/null +++ b/seed/java-model/grpc/.github/workflows/ci.yml @@ -0,0 +1,61 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up Java + id: setup-jre + uses: actions/setup-java@v1 + with: + java-version: "11" + architecture: x64 + + - name: Compile + run: ./gradlew compileJava + + test: + needs: [ compile ] + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up Java + id: setup-jre + uses: actions/setup-java@v1 + with: + java-version: "11" + architecture: x64 + + - name: Test + run: ./gradlew test + publish: + needs: [ compile, test ] + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up Java + id: setup-jre + uses: actions/setup-java@v1 + with: + java-version: "11" + architecture: x64 + + - name: Publish to maven + run: | + ./gradlew publish + env: + MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} + MAVEN_PUBLISH_REGISTRY_URL: "" diff --git a/seed/java-model/grpc/.gitignore b/seed/java-model/grpc/.gitignore new file mode 100644 index 00000000000..d4199abc2cd --- /dev/null +++ b/seed/java-model/grpc/.gitignore @@ -0,0 +1,24 @@ +*.class +.project +.gradle +? +.classpath +.checkstyle +.settings +.node +build + +# IntelliJ +*.iml +*.ipr +*.iws +.idea/ +out/ + +# Eclipse/IntelliJ APT +generated_src/ +generated_testSrc/ +generated/ + +bin +build \ No newline at end of file diff --git a/seed/java-model/grpc/.mock/definition/api.yml b/seed/java-model/grpc/.mock/definition/api.yml new file mode 100644 index 00000000000..15a9f823724 --- /dev/null +++ b/seed/java-model/grpc/.mock/definition/api.yml @@ -0,0 +1,4 @@ +name: api + +error-discrimination: + strategy: status-code \ No newline at end of file diff --git a/seed/java-model/grpc/.mock/definition/user.yml b/seed/java-model/grpc/.mock/definition/user.yml new file mode 100644 index 00000000000..80223300afc --- /dev/null +++ b/seed/java-model/grpc/.mock/definition/user.yml @@ -0,0 +1,61 @@ +types: + Metadata: + type: map> + encoding: + proto: + type: google.protobuf.Struct + + MetadataValue: + discriminated: false + union: + - double + - string + - boolean + - list + encoding: + proto: + type: google.protobuf.Value + + User: + properties: + id: string + username: string + email: optional + age: optional + weight: optional + metadata: optional + + CreateUserResponse: + properties: + user: User + +service: + auth: false + base-path: / + transport: + grpc: + service-name: UserService + endpoints: + createUser: + method: POST + path: /users + request: + name: CreateUserRequest + body: + properties: + username: string + email: optional + age: optional + weight: optional + response: CreateUserResponse + + getUser: + method: GET + path: /users + request: + name: GetUserRequest + query-parameters: + username: optional + age: optional + weight: optional + response: User diff --git a/seed/java-model/grpc/.mock/fern.config.json b/seed/java-model/grpc/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/java-model/grpc/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/java-model/grpc/.mock/generators.yml b/seed/java-model/grpc/.mock/generators.yml new file mode 100644 index 00000000000..f62dfba5843 --- /dev/null +++ b/seed/java-model/grpc/.mock/generators.yml @@ -0,0 +1,4 @@ +api: + - proto: + root: proto + target: proto/user/v1/user.proto \ No newline at end of file diff --git a/seed/java-model/grpc/.mock/proto/google/api/annotations.proto b/seed/java-model/grpc/.mock/proto/google/api/annotations.proto new file mode 100644 index 00000000000..efdab3db6ca --- /dev/null +++ b/seed/java-model/grpc/.mock/proto/google/api/annotations.proto @@ -0,0 +1,31 @@ +// Copyright 2015 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/api/http.proto"; +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "AnnotationsProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // See `HttpRule`. + HttpRule http = 72295728; +} diff --git a/seed/java-model/grpc/.mock/proto/google/api/field_behavior.proto b/seed/java-model/grpc/.mock/proto/google/api/field_behavior.proto new file mode 100644 index 00000000000..344cb0b1fc2 --- /dev/null +++ b/seed/java-model/grpc/.mock/proto/google/api/field_behavior.proto @@ -0,0 +1,104 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "FieldBehaviorProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.FieldOptions { + // A designation of a specific field behavior (required, output only, etc.) + // in protobuf messages. + // + // Examples: + // + // string name = 1 [(google.api.field_behavior) = REQUIRED]; + // State state = 1 [(google.api.field_behavior) = OUTPUT_ONLY]; + // google.protobuf.Duration ttl = 1 + // [(google.api.field_behavior) = INPUT_ONLY]; + // google.protobuf.Timestamp expire_time = 1 + // [(google.api.field_behavior) = OUTPUT_ONLY, + // (google.api.field_behavior) = IMMUTABLE]; + repeated google.api.FieldBehavior field_behavior = 1052; +} + +// An indicator of the behavior of a given field (for example, that a field +// is required in requests, or given as output but ignored as input). +// This **does not** change the behavior in protocol buffers itself; it only +// denotes the behavior and may affect how API tooling handles the field. +// +// Note: This enum **may** receive new values in the future. +enum FieldBehavior { + // Conventional default for enums. Do not use this. + FIELD_BEHAVIOR_UNSPECIFIED = 0; + + // Specifically denotes a field as optional. + // While all fields in protocol buffers are optional, this may be specified + // for emphasis if appropriate. + OPTIONAL = 1; + + // Denotes a field as required. + // This indicates that the field **must** be provided as part of the request, + // and failure to do so will cause an error (usually `INVALID_ARGUMENT`). + REQUIRED = 2; + + // Denotes a field as output only. + // This indicates that the field is provided in responses, but including the + // field in a request does nothing (the server *must* ignore it and + // *must not* throw an error as a result of the field's presence). + OUTPUT_ONLY = 3; + + // Denotes a field as input only. + // This indicates that the field is provided in requests, and the + // corresponding field is not included in output. + INPUT_ONLY = 4; + + // Denotes a field as immutable. + // This indicates that the field may be set once in a request to create a + // resource, but may not be changed thereafter. + IMMUTABLE = 5; + + // Denotes that a (repeated) field is an unordered list. + // This indicates that the service may provide the elements of the list + // in any arbitrary order, rather than the order the user originally + // provided. Additionally, the list's order may or may not be stable. + UNORDERED_LIST = 6; + + // Denotes that this field returns a non-empty default value if not set. + // This indicates that if the user provides the empty value in a request, + // a non-empty value will be returned. The user will not be aware of what + // non-empty value to expect. + NON_EMPTY_DEFAULT = 7; + + // Denotes that the field in a resource (a message annotated with + // google.api.resource) is used in the resource name to uniquely identify the + // resource. For AIP-compliant APIs, this should only be applied to the + // `name` field on the resource. + // + // This behavior should not be applied to references to other resources within + // the message. + // + // The identifier field of resources often have different field behavior + // depending on the request it is embedded in (e.g. for Create methods name + // is optional and unused, while for Update methods it is required). Instead + // of method-specific annotations, only `IDENTIFIER` is required. + IDENTIFIER = 8; +} diff --git a/seed/java-model/grpc/.mock/proto/google/api/http.proto b/seed/java-model/grpc/.mock/proto/google/api/http.proto new file mode 100644 index 00000000000..31d867a27d5 --- /dev/null +++ b/seed/java-model/grpc/.mock/proto/google/api/http.proto @@ -0,0 +1,379 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "HttpProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Defines the HTTP configuration for an API service. It contains a list of +// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method +// to one or more HTTP REST API methods. +message Http { + // A list of HTTP configuration rules that apply to individual API methods. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated HttpRule rules = 1; + + // When set to true, URL path parameters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // + // The default behavior is to not decode RFC 6570 reserved characters in multi + // segment matches. + bool fully_decode_reserved_expansion = 2; +} + +// # gRPC Transcoding +// +// gRPC Transcoding is a feature for mapping between a gRPC method and one or +// more HTTP REST endpoints. It allows developers to build a single API service +// that supports both gRPC APIs and REST APIs. Many systems, including [Google +// APIs](https://github.com/googleapis/googleapis), +// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC +// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), +// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature +// and use it for large scale production services. +// +// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies +// how different portions of the gRPC request message are mapped to the URL +// path, URL query parameters, and HTTP request body. It also controls how the +// gRPC response message is mapped to the HTTP response body. `HttpRule` is +// typically specified as an `google.api.http` annotation on the gRPC method. +// +// Each mapping specifies a URL path template and an HTTP method. The path +// template may refer to one or more fields in the gRPC request message, as long +// as each field is a non-repeated field with a primitive (non-message) type. +// The path template controls how fields of the request message are mapped to +// the URL path. +// +// Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/{name=messages/*}" +// }; +// } +// } +// message GetMessageRequest { +// string name = 1; // Mapped to URL path. +// } +// message Message { +// string text = 1; // The resource content. +// } +// +// This enables an HTTP REST to gRPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` +// +// Any fields in the request message which are not bound by the path template +// automatically become HTTP query parameters if there is no HTTP request body. +// For example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get:"/v1/messages/{message_id}" +// }; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // Mapped to URL path. +// int64 revision = 2; // Mapped to URL query parameter `revision`. +// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. +// } +// +// This enables a HTTP JSON to RPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | +// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: +// "foo"))` +// +// Note that fields which are mapped to URL query parameters must have a +// primitive type or a repeated primitive type or a non-repeated message type. +// In the case of a repeated type, the parameter can be repeated in the URL +// as `...?param=A¶m=B`. In the case of a message type, each field of the +// message is mapped to a separate parameter, such as +// `...?foo.a=A&foo.b=B&foo.c=C`. +// +// For HTTP methods that allow a request body, the `body` field +// specifies the mapping. Consider a REST update method on the +// message resource collection: +// +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } +// +// The following HTTP JSON to RPC mapping is enabled, where the +// representation of the JSON in the request body is determined by +// protos JSON encoding: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" message { text: "Hi!" })` +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. This enables the following alternative definition of +// the update method: +// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } +// +// +// The following HTTP JSON to RPC mapping is enabled: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" text: "Hi!")` +// +// Note that when using `*` in the body mapping, it is not possible to +// have HTTP parameters, as all fields not bound by the path end in +// the body. This makes this option more rarely used in practice when +// defining REST APIs. The common usage of `*` is in custom methods +// which don't use the URL at all for transferring data. +// +// It is possible to define multiple HTTP methods for one RPC by using +// the `additional_bindings` option. Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } +// +// This enables the following two alternative HTTP JSON to RPC mappings: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` +// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: +// "123456")` +// +// ## Rules for HTTP mapping +// +// 1. Leaf request fields (recursive expansion nested messages in the request +// message) are classified into three categories: +// - Fields referred by the path template. They are passed via the URL path. +// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They +// are passed via the HTTP +// request body. +// - All other fields are passed via the URL query parameters, and the +// parameter name is the field path in the request message. A repeated +// field can be represented as multiple query parameters under the same +// name. +// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL +// query parameter, all fields +// are passed via URL path and HTTP request body. +// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP +// request body, all +// fields are passed via URL path and URL query parameters. +// +// ### Path template syntax +// +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +// +// The syntax `*` matches a single URL path segment. The syntax `**` matches +// zero or more URL path segments, which must be the last part of the URL path +// except the `Verb`. +// +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable +// matches a single path segment, its template may be omitted, e.g. `{var}` +// is equivalent to `{var=*}`. +// +// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` +// contains any reserved character, such characters should be percent-encoded +// before the matching. +// +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path on the client +// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The +// server side does the reverse decoding. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{var}`. +// +// If a variable contains multiple path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path on the +// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. +// The server side does the reverse decoding, except "%2F" and "%2f" are left +// unchanged. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{+var}`. +// +// ## Using gRPC API Service Configuration +// +// gRPC API Service Configuration (service config) is a configuration language +// for configuring a gRPC service to become a user-facing product. The +// service config is simply the YAML representation of the `google.api.Service` +// proto message. +// +// As an alternative to annotating your proto file, you can configure gRPC +// transcoding in your service config YAML files. You do this by specifying a +// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same +// effect as the proto annotation. This can be particularly useful if you +// have a proto that is reused in multiple services. Note that any transcoding +// specified in the service config will override any matching transcoding +// configuration in the proto. +// +// Example: +// +// http: +// rules: +// # Selects a gRPC method and applies HttpRule to it. +// - selector: example.v1.Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} +// +// ## Special notes +// +// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the +// proto to JSON conversion must follow the [proto3 +// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). +// +// While the single segment variable follows the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String +// Expansion, the multi segment variable **does not** follow RFC 6570 Section +// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding +// for multi segment variables. +// +// The path variables **must not** refer to any repeated or mapped field, +// because client libraries are not capable of handling such variable expansion. +// +// The path variables **must not** capture the leading "/" character. The reason +// is that the most common use case "{var}" does not capture the leading "/" +// character. For consistency, all path variables must share the same behavior. +// +// Repeated message fields must not be mapped to URL query parameters, because +// no client library can support such complicated mapping. +// +// If an API needs to use a JSON array for request or response body, it can map +// the request or response body to a repeated field. However, some gRPC +// Transcoding implementations may not support this feature. +message HttpRule { + // Selects a method to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax + // details. + string selector = 1; + + // Determines the URL pattern is matched by this rules. This pattern can be + // used with any of the {get|put|post|delete|patch} methods. A custom method + // can be defined using the 'custom' field. + oneof pattern { + // Maps to HTTP GET. Used for listing and getting information about + // resources. + string get = 2; + + // Maps to HTTP PUT. Used for replacing a resource. + string put = 3; + + // Maps to HTTP POST. Used for creating a resource or performing an action. + string post = 4; + + // Maps to HTTP DELETE. Used for deleting a resource. + string delete = 5; + + // Maps to HTTP PATCH. Used for updating a resource. + string patch = 6; + + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. + CustomHttpPattern custom = 8; + } + + // The name of the request field whose value is mapped to the HTTP request + // body, or `*` for mapping all request fields not captured by the path + // pattern to the HTTP body, or omitted for not having any HTTP request body. + // + // NOTE: the referred field must be present at the top-level of the request + // message type. + string body = 7; + + // Optional. The name of the response field whose value is mapped to the HTTP + // response body. When omitted, the entire response message will be used + // as the HTTP response body. + // + // NOTE: The referred field must be present at the top-level of the response + // message type. + string response_body = 12; + + // Additional HTTP bindings for the selector. Nested bindings must + // not contain an `additional_bindings` field themselves (that is, + // the nesting may only be one level deep). + repeated HttpRule additional_bindings = 11; +} + +// A custom pattern is used for defining custom HTTP verb. +message CustomHttpPattern { + // The name of this custom HTTP verb. + string kind = 1; + + // The path matched by this custom verb. + string path = 2; +} diff --git a/seed/java-model/grpc/.mock/proto/user/v1/user.proto b/seed/java-model/grpc/.mock/proto/user/v1/user.proto new file mode 100644 index 00000000000..adab9ceefb9 --- /dev/null +++ b/seed/java-model/grpc/.mock/proto/user/v1/user.proto @@ -0,0 +1,55 @@ +syntax = "proto3"; + +package user.v1; + +import "google/api/annotations.proto"; +import "google/api/field_behavior.proto"; +import "google/protobuf/struct.proto"; + +option csharp_namespace = "User.V1"; + +message User { + string username = 1 [ + (google.api.field_behavior) = REQUIRED + ]; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateUserRequest { + string username = 1 [ + (google.api.field_behavior) = REQUIRED + ]; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateUserResponse { + User user = 1; +} + +message GetUserRequest { + string username = 1; + string email = 2; + uint32 age = 3; + float weight = 4; +} + +service UserService { + rpc CreateUser(CreateUserRequest) returns (CreateUserResponse) { + option (google.api.http) = { + post: "/users" + body: "*" + }; + } + + rpc GetUser(GetUserRequest) returns (User) { + option (google.api.http) = { + get: "/users" + }; + } +} diff --git a/seed/java-model/grpc/build.gradle b/seed/java-model/grpc/build.gradle new file mode 100644 index 00000000000..011f7da6734 --- /dev/null +++ b/seed/java-model/grpc/build.gradle @@ -0,0 +1,67 @@ +plugins { + id 'java-library' + id 'maven-publish' + id 'com.diffplug.spotless' version '6.11.0' +} + +repositories { + mavenCentral() + maven { + url 'https://s01.oss.sonatype.org/content/repositories/releases/' + } +} + +dependencies { + api 'com.fasterxml.jackson.core:jackson-databind:2.13.0' + api 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.12.3' + api 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.12.3' +} + + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +spotless { + java { + palantirJavaFormat() + } +} + +java { + withSourcesJar() + withJavadocJar() +} + +test { + useJUnitPlatform() + testLogging { + showStandardStreams = true + } +} +publishing { + publications { + maven(MavenPublication) { + groupId = 'com.fern' + artifactId = 'grpc' + version = '0.0.1' + from components.java + pom { + scm { + connection = 'scm:git:git://github.com/grpc/fern.git' + developerConnection = 'scm:git:git://github.com/grpc/fern.git' + url = 'https://github.com/grpc/fern' + } + } + } + } + repositories { + maven { + url "$System.env.MAVEN_PUBLISH_REGISTRY_URL" + credentials { + username "$System.env.MAVEN_USERNAME" + password "$System.env.MAVEN_PASSWORD" + } + } + } +} + diff --git a/seed/java-model/grpc/settings.gradle b/seed/java-model/grpc/settings.gradle new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/java-model/grpc/snippet-templates.json b/seed/java-model/grpc/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/java-model/grpc/snippet.json b/seed/java-model/grpc/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/java-model/grpc/src/main/java/com/seed/api/core/DateTimeDeserializer.java b/seed/java-model/grpc/src/main/java/com/seed/api/core/DateTimeDeserializer.java new file mode 100644 index 00000000000..eac7d50c71a --- /dev/null +++ b/seed/java-model/grpc/src/main/java/com/seed/api/core/DateTimeDeserializer.java @@ -0,0 +1,55 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.module.SimpleModule; +import java.io.IOException; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalQueries; + +/** + * Custom deserializer that handles converting ISO8601 dates into {@link OffsetDateTime} objects. + */ +class DateTimeDeserializer extends JsonDeserializer { + private static final SimpleModule MODULE; + + static { + MODULE = new SimpleModule().addDeserializer(OffsetDateTime.class, new DateTimeDeserializer()); + } + + /** + * Gets a module wrapping this deserializer as an adapter for the Jackson ObjectMapper. + * + * @return A {@link SimpleModule} to be plugged onto Jackson ObjectMapper. + */ + public static SimpleModule getModule() { + return MODULE; + } + + @Override + public OffsetDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException { + JsonToken token = parser.currentToken(); + if (token == JsonToken.VALUE_NUMBER_INT) { + return OffsetDateTime.ofInstant(Instant.ofEpochSecond(parser.getValueAsLong()), ZoneOffset.UTC); + } else { + TemporalAccessor temporal = DateTimeFormatter.ISO_DATE_TIME.parseBest( + parser.getValueAsString(), OffsetDateTime::from, LocalDateTime::from); + + if (temporal.query(TemporalQueries.offset()) == null) { + return LocalDateTime.from(temporal).atOffset(ZoneOffset.UTC); + } else { + return OffsetDateTime.from(temporal); + } + } + } +} diff --git a/seed/java-model/grpc/src/main/java/com/seed/api/core/ObjectMappers.java b/seed/java-model/grpc/src/main/java/com/seed/api/core/ObjectMappers.java new file mode 100644 index 00000000000..0b16d472dca --- /dev/null +++ b/seed/java-model/grpc/src/main/java/com/seed/api/core/ObjectMappers.java @@ -0,0 +1,36 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import java.io.IOException; + +public final class ObjectMappers { + public static final ObjectMapper JSON_MAPPER = JsonMapper.builder() + .addModule(new Jdk8Module()) + .addModule(new JavaTimeModule()) + .addModule(DateTimeDeserializer.getModule()) + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .build(); + + private ObjectMappers() {} + + public static String stringify(Object o) { + try { + return JSON_MAPPER + .setSerializationInclusion(JsonInclude.Include.ALWAYS) + .writerWithDefaultPrettyPrinter() + .writeValueAsString(o); + } catch (IOException e) { + return o.getClass().getName() + "@" + Integer.toHexString(o.hashCode()); + } + } +} diff --git a/seed/java-model/grpc/src/main/java/com/seed/api/model/user/CreateUserResponse.java b/seed/java-model/grpc/src/main/java/com/seed/api/model/user/CreateUserResponse.java new file mode 100644 index 00000000000..77a9231b6ea --- /dev/null +++ b/seed/java-model/grpc/src/main/java/com/seed/api/model/user/CreateUserResponse.java @@ -0,0 +1,86 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.model.user; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.util.Objects; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = CreateUserResponse.Builder.class) +public final class CreateUserResponse { + private final User user; + + private CreateUserResponse(User user) { + this.user = user; + } + + @JsonProperty("user") + public User getUser() { + return user; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof CreateUserResponse && equalTo((CreateUserResponse) other); + } + + private boolean equalTo(CreateUserResponse other) { + return user.equals(other.user); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.user); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static UserStage builder() { + return new Builder(); + } + + public interface UserStage { + _FinalStage user(User user); + + Builder from(CreateUserResponse other); + } + + public interface _FinalStage { + CreateUserResponse build(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements UserStage, _FinalStage { + private User user; + + private Builder() {} + + @java.lang.Override + public Builder from(CreateUserResponse other) { + user(other.getUser()); + return this; + } + + @java.lang.Override + @JsonSetter("user") + public _FinalStage user(User user) { + this.user = user; + return this; + } + + @java.lang.Override + public CreateUserResponse build() { + return new CreateUserResponse(user); + } + } +} diff --git a/seed/java-model/grpc/src/main/java/com/seed/api/model/user/MetadataValue.java b/seed/java-model/grpc/src/main/java/com/seed/api/model/user/MetadataValue.java new file mode 100644 index 00000000000..55b98e5dd37 --- /dev/null +++ b/seed/java-model/grpc/src/main/java/com/seed/api/model/user/MetadataValue.java @@ -0,0 +1,118 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.model.user; + +import com.fasterxml.jackson.annotation.JsonValue; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.seed.api.core.ObjectMappers; +import java.io.IOException; +import java.util.List; +import java.util.Objects; + +@JsonDeserialize(using = MetadataValue.Deserializer.class) +public final class MetadataValue { + private final Object value; + + private final int type; + + private MetadataValue(Object value, int type) { + this.value = value; + this.type = type; + } + + @JsonValue + public Object get() { + return this.value; + } + + public T visit(Visitor visitor) { + if (this.type == 0) { + return visitor.visit((double) this.value); + } else if (this.type == 1) { + return visitor.visit((String) this.value); + } else if (this.type == 2) { + return visitor.visit((boolean) this.value); + } else if (this.type == 3) { + return visitor.visit((List) this.value); + } + throw new IllegalStateException("Failed to visit value. This should never happen."); + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof MetadataValue && equalTo((MetadataValue) other); + } + + private boolean equalTo(MetadataValue other) { + return value.equals(other.value); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.value); + } + + @java.lang.Override + public String toString() { + return this.value.toString(); + } + + public static MetadataValue of(double value) { + return new MetadataValue(value, 0); + } + + public static MetadataValue of(String value) { + return new MetadataValue(value, 1); + } + + public static MetadataValue of(boolean value) { + return new MetadataValue(value, 2); + } + + public static MetadataValue of(List value) { + return new MetadataValue(value, 3); + } + + public interface Visitor { + T visit(double value); + + T visit(String value); + + T visit(boolean value); + + T visit(List value); + } + + static final class Deserializer extends StdDeserializer { + Deserializer() { + super(MetadataValue.class); + } + + @java.lang.Override + public MetadataValue deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + Object value = p.readValueAs(Object.class); + if (value instanceof Double) { + return of((Double) value); + } + try { + return of(ObjectMappers.JSON_MAPPER.convertValue(value, String.class)); + } catch (IllegalArgumentException e) { + } + if (value instanceof Boolean) { + return of((Boolean) value); + } + try { + return of(ObjectMappers.JSON_MAPPER.convertValue(value, new TypeReference>() {})); + } catch (IllegalArgumentException e) { + } + throw new JsonParseException(p, "Failed to deserialize"); + } + } +} diff --git a/seed/java-model/grpc/src/main/java/com/seed/api/model/user/User.java b/seed/java-model/grpc/src/main/java/com/seed/api/model/user/User.java new file mode 100644 index 00000000000..c81d516b0b0 --- /dev/null +++ b/seed/java-model/grpc/src/main/java/com/seed/api/model/user/User.java @@ -0,0 +1,234 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.model.user; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = User.Builder.class) +public final class User { + private final String id; + + private final String username; + + private final Optional email; + + private final Optional age; + + private final Optional weight; + + private final Optional>> metadata; + + private User( + String id, + String username, + Optional email, + Optional age, + Optional weight, + Optional>> metadata) { + this.id = id; + this.username = username; + this.email = email; + this.age = age; + this.weight = weight; + this.metadata = metadata; + } + + @JsonProperty("id") + public String getId() { + return id; + } + + @JsonProperty("username") + public String getUsername() { + return username; + } + + @JsonProperty("email") + public Optional getEmail() { + return email; + } + + @JsonProperty("age") + public Optional getAge() { + return age; + } + + @JsonProperty("weight") + public Optional getWeight() { + return weight; + } + + @JsonProperty("metadata") + public Optional>> getMetadata() { + return metadata; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof User && equalTo((User) other); + } + + private boolean equalTo(User other) { + return id.equals(other.id) + && username.equals(other.username) + && email.equals(other.email) + && age.equals(other.age) + && weight.equals(other.weight) + && metadata.equals(other.metadata); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.id, this.username, this.email, this.age, this.weight, this.metadata); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static IdStage builder() { + return new Builder(); + } + + public interface IdStage { + UsernameStage id(String id); + + Builder from(User other); + } + + public interface UsernameStage { + _FinalStage username(String username); + } + + public interface _FinalStage { + User build(); + + _FinalStage email(Optional email); + + _FinalStage email(String email); + + _FinalStage age(Optional age); + + _FinalStage age(Integer age); + + _FinalStage weight(Optional weight); + + _FinalStage weight(Double weight); + + _FinalStage metadata(Optional>> metadata); + + _FinalStage metadata(Map> metadata); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements IdStage, UsernameStage, _FinalStage { + private String id; + + private String username; + + private Optional>> metadata = Optional.empty(); + + private Optional weight = Optional.empty(); + + private Optional age = Optional.empty(); + + private Optional email = Optional.empty(); + + private Builder() {} + + @java.lang.Override + public Builder from(User other) { + id(other.getId()); + username(other.getUsername()); + email(other.getEmail()); + age(other.getAge()); + weight(other.getWeight()); + metadata(other.getMetadata()); + return this; + } + + @java.lang.Override + @JsonSetter("id") + public UsernameStage id(String id) { + this.id = id; + return this; + } + + @java.lang.Override + @JsonSetter("username") + public _FinalStage username(String username) { + this.username = username; + return this; + } + + @java.lang.Override + public _FinalStage metadata(Map> metadata) { + this.metadata = Optional.ofNullable(metadata); + return this; + } + + @java.lang.Override + @JsonSetter(value = "metadata", nulls = Nulls.SKIP) + public _FinalStage metadata(Optional>> metadata) { + this.metadata = metadata; + return this; + } + + @java.lang.Override + public _FinalStage weight(Double weight) { + this.weight = Optional.ofNullable(weight); + return this; + } + + @java.lang.Override + @JsonSetter(value = "weight", nulls = Nulls.SKIP) + public _FinalStage weight(Optional weight) { + this.weight = weight; + return this; + } + + @java.lang.Override + public _FinalStage age(Integer age) { + this.age = Optional.ofNullable(age); + return this; + } + + @java.lang.Override + @JsonSetter(value = "age", nulls = Nulls.SKIP) + public _FinalStage age(Optional age) { + this.age = age; + return this; + } + + @java.lang.Override + public _FinalStage email(String email) { + this.email = Optional.ofNullable(email); + return this; + } + + @java.lang.Override + @JsonSetter(value = "email", nulls = Nulls.SKIP) + public _FinalStage email(Optional email) { + this.email = email; + return this; + } + + @java.lang.Override + public User build() { + return new User(id, username, email, age, weight, metadata); + } + } +} diff --git a/seed/java-sdk/grpc-proto/.github/workflows/ci.yml b/seed/java-sdk/grpc-proto/.github/workflows/ci.yml new file mode 100644 index 00000000000..8598a73092a --- /dev/null +++ b/seed/java-sdk/grpc-proto/.github/workflows/ci.yml @@ -0,0 +1,61 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up Java + id: setup-jre + uses: actions/setup-java@v1 + with: + java-version: "11" + architecture: x64 + + - name: Compile + run: ./gradlew compileJava + + test: + needs: [ compile ] + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up Java + id: setup-jre + uses: actions/setup-java@v1 + with: + java-version: "11" + architecture: x64 + + - name: Test + run: ./gradlew test + publish: + needs: [ compile, test ] + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up Java + id: setup-jre + uses: actions/setup-java@v1 + with: + java-version: "11" + architecture: x64 + + - name: Publish to maven + run: | + ./gradlew publish + env: + MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} + MAVEN_PUBLISH_REGISTRY_URL: "" diff --git a/seed/java-sdk/grpc-proto/.gitignore b/seed/java-sdk/grpc-proto/.gitignore new file mode 100644 index 00000000000..d4199abc2cd --- /dev/null +++ b/seed/java-sdk/grpc-proto/.gitignore @@ -0,0 +1,24 @@ +*.class +.project +.gradle +? +.classpath +.checkstyle +.settings +.node +build + +# IntelliJ +*.iml +*.ipr +*.iws +.idea/ +out/ + +# Eclipse/IntelliJ APT +generated_src/ +generated_testSrc/ +generated/ + +bin +build \ No newline at end of file diff --git a/seed/java-sdk/grpc-proto/.mock/fern.config.json b/seed/java-sdk/grpc-proto/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/java-sdk/grpc-proto/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/java-sdk/grpc-proto/.mock/generators.yml b/seed/java-sdk/grpc-proto/.mock/generators.yml new file mode 100644 index 00000000000..902c4f50168 --- /dev/null +++ b/seed/java-sdk/grpc-proto/.mock/generators.yml @@ -0,0 +1,5 @@ +api: + - proto: + root: proto + target: proto/user/v1/user.proto + local-generation: true \ No newline at end of file diff --git a/seed/java-sdk/grpc-proto/.mock/proto/google/api/annotations.proto b/seed/java-sdk/grpc-proto/.mock/proto/google/api/annotations.proto new file mode 100644 index 00000000000..8ff42098404 --- /dev/null +++ b/seed/java-sdk/grpc-proto/.mock/proto/google/api/annotations.proto @@ -0,0 +1,31 @@ +// Copyright 2015 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/api/http.proto"; +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "AnnotationsProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // See `HttpRule`. + HttpRule http = 72295728; +} \ No newline at end of file diff --git a/seed/java-sdk/grpc-proto/.mock/proto/google/api/field_behavior.proto b/seed/java-sdk/grpc-proto/.mock/proto/google/api/field_behavior.proto new file mode 100644 index 00000000000..128799c558d --- /dev/null +++ b/seed/java-sdk/grpc-proto/.mock/proto/google/api/field_behavior.proto @@ -0,0 +1,104 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "FieldBehaviorProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.FieldOptions { + // A designation of a specific field behavior (required, output only, etc.) + // in protobuf messages. + // + // Examples: + // + // string name = 1 [(google.api.field_behavior) = REQUIRED]; + // State state = 1 [(google.api.field_behavior) = OUTPUT_ONLY]; + // google.protobuf.Duration ttl = 1 + // [(google.api.field_behavior) = INPUT_ONLY]; + // google.protobuf.Timestamp expire_time = 1 + // [(google.api.field_behavior) = OUTPUT_ONLY, + // (google.api.field_behavior) = IMMUTABLE]; + repeated google.api.FieldBehavior field_behavior = 1052; +} + +// An indicator of the behavior of a given field (for example, that a field +// is required in requests, or given as output but ignored as input). +// This **does not** change the behavior in protocol buffers itself; it only +// denotes the behavior and may affect how API tooling handles the field. +// +// Note: This enum **may** receive new values in the future. +enum FieldBehavior { + // Conventional default for enums. Do not use this. + FIELD_BEHAVIOR_UNSPECIFIED = 0; + + // Specifically denotes a field as optional. + // While all fields in protocol buffers are optional, this may be specified + // for emphasis if appropriate. + OPTIONAL = 1; + + // Denotes a field as required. + // This indicates that the field **must** be provided as part of the request, + // and failure to do so will cause an error (usually `INVALID_ARGUMENT`). + REQUIRED = 2; + + // Denotes a field as output only. + // This indicates that the field is provided in responses, but including the + // field in a request does nothing (the server *must* ignore it and + // *must not* throw an error as a result of the field's presence). + OUTPUT_ONLY = 3; + + // Denotes a field as input only. + // This indicates that the field is provided in requests, and the + // corresponding field is not included in output. + INPUT_ONLY = 4; + + // Denotes a field as immutable. + // This indicates that the field may be set once in a request to create a + // resource, but may not be changed thereafter. + IMMUTABLE = 5; + + // Denotes that a (repeated) field is an unordered list. + // This indicates that the service may provide the elements of the list + // in any arbitrary order, rather than the order the user originally + // provided. Additionally, the list's order may or may not be stable. + UNORDERED_LIST = 6; + + // Denotes that this field returns a non-empty default value if not set. + // This indicates that if the user provides the empty value in a request, + // a non-empty value will be returned. The user will not be aware of what + // non-empty value to expect. + NON_EMPTY_DEFAULT = 7; + + // Denotes that the field in a resource (a message annotated with + // google.api.resource) is used in the resource name to uniquely identify the + // resource. For AIP-compliant APIs, this should only be applied to the + // `name` field on the resource. + // + // This behavior should not be applied to references to other resources within + // the message. + // + // The identifier field of resources often have different field behavior + // depending on the request it is embedded in (e.g. for Create methods name + // is optional and unused, while for Update methods it is required). Instead + // of method-specific annotations, only `IDENTIFIER` is required. + IDENTIFIER = 8; +} \ No newline at end of file diff --git a/seed/java-sdk/grpc-proto/.mock/proto/google/api/http.proto b/seed/java-sdk/grpc-proto/.mock/proto/google/api/http.proto new file mode 100644 index 00000000000..c8392381eb9 --- /dev/null +++ b/seed/java-sdk/grpc-proto/.mock/proto/google/api/http.proto @@ -0,0 +1,379 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "HttpProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Defines the HTTP configuration for an API service. It contains a list of +// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method +// to one or more HTTP REST API methods. +message Http { + // A list of HTTP configuration rules that apply to individual API methods. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated HttpRule rules = 1; + + // When set to true, URL path parameters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // + // The default behavior is to not decode RFC 6570 reserved characters in multi + // segment matches. + bool fully_decode_reserved_expansion = 2; +} + +// # gRPC Transcoding +// +// gRPC Transcoding is a feature for mapping between a gRPC method and one or +// more HTTP REST endpoints. It allows developers to build a single API service +// that supports both gRPC APIs and REST APIs. Many systems, including [Google +// APIs](https://github.com/googleapis/googleapis), +// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC +// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), +// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature +// and use it for large scale production services. +// +// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies +// how different portions of the gRPC request message are mapped to the URL +// path, URL query parameters, and HTTP request body. It also controls how the +// gRPC response message is mapped to the HTTP response body. `HttpRule` is +// typically specified as an `google.api.http` annotation on the gRPC method. +// +// Each mapping specifies a URL path template and an HTTP method. The path +// template may refer to one or more fields in the gRPC request message, as long +// as each field is a non-repeated field with a primitive (non-message) type. +// The path template controls how fields of the request message are mapped to +// the URL path. +// +// Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/{name=messages/*}" +// }; +// } +// } +// message GetMessageRequest { +// string name = 1; // Mapped to URL path. +// } +// message Message { +// string text = 1; // The resource content. +// } +// +// This enables an HTTP REST to gRPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` +// +// Any fields in the request message which are not bound by the path template +// automatically become HTTP query parameters if there is no HTTP request body. +// For example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get:"/v1/messages/{message_id}" +// }; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // Mapped to URL path. +// int64 revision = 2; // Mapped to URL query parameter `revision`. +// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. +// } +// +// This enables a HTTP JSON to RPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | +// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: +// "foo"))` +// +// Note that fields which are mapped to URL query parameters must have a +// primitive type or a repeated primitive type or a non-repeated message type. +// In the case of a repeated type, the parameter can be repeated in the URL +// as `...?param=A¶m=B`. In the case of a message type, each field of the +// message is mapped to a separate parameter, such as +// `...?foo.a=A&foo.b=B&foo.c=C`. +// +// For HTTP methods that allow a request body, the `body` field +// specifies the mapping. Consider a REST update method on the +// message resource collection: +// +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } +// +// The following HTTP JSON to RPC mapping is enabled, where the +// representation of the JSON in the request body is determined by +// protos JSON encoding: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" message { text: "Hi!" })` +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. This enables the following alternative definition of +// the update method: +// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } +// +// +// The following HTTP JSON to RPC mapping is enabled: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" text: "Hi!")` +// +// Note that when using `*` in the body mapping, it is not possible to +// have HTTP parameters, as all fields not bound by the path end in +// the body. This makes this option more rarely used in practice when +// defining REST APIs. The common usage of `*` is in custom methods +// which don't use the URL at all for transferring data. +// +// It is possible to define multiple HTTP methods for one RPC by using +// the `additional_bindings` option. Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } +// +// This enables the following two alternative HTTP JSON to RPC mappings: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` +// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: +// "123456")` +// +// ## Rules for HTTP mapping +// +// 1. Leaf request fields (recursive expansion nested messages in the request +// message) are classified into three categories: +// - Fields referred by the path template. They are passed via the URL path. +// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They +// are passed via the HTTP +// request body. +// - All other fields are passed via the URL query parameters, and the +// parameter name is the field path in the request message. A repeated +// field can be represented as multiple query parameters under the same +// name. +// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL +// query parameter, all fields +// are passed via URL path and HTTP request body. +// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP +// request body, all +// fields are passed via URL path and URL query parameters. +// +// ### Path template syntax +// +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +// +// The syntax `*` matches a single URL path segment. The syntax `**` matches +// zero or more URL path segments, which must be the last part of the URL path +// except the `Verb`. +// +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable +// matches a single path segment, its template may be omitted, e.g. `{var}` +// is equivalent to `{var=*}`. +// +// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` +// contains any reserved character, such characters should be percent-encoded +// before the matching. +// +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path on the client +// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The +// server side does the reverse decoding. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{var}`. +// +// If a variable contains multiple path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path on the +// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. +// The server side does the reverse decoding, except "%2F" and "%2f" are left +// unchanged. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{+var}`. +// +// ## Using gRPC API Service Configuration +// +// gRPC API Service Configuration (service config) is a configuration language +// for configuring a gRPC service to become a user-facing product. The +// service config is simply the YAML representation of the `google.api.Service` +// proto message. +// +// As an alternative to annotating your proto file, you can configure gRPC +// transcoding in your service config YAML files. You do this by specifying a +// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same +// effect as the proto annotation. This can be particularly useful if you +// have a proto that is reused in multiple services. Note that any transcoding +// specified in the service config will override any matching transcoding +// configuration in the proto. +// +// Example: +// +// http: +// rules: +// # Selects a gRPC method and applies HttpRule to it. +// - selector: example.v1.Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} +// +// ## Special notes +// +// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the +// proto to JSON conversion must follow the [proto3 +// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). +// +// While the single segment variable follows the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String +// Expansion, the multi segment variable **does not** follow RFC 6570 Section +// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding +// for multi segment variables. +// +// The path variables **must not** refer to any repeated or mapped field, +// because client libraries are not capable of handling such variable expansion. +// +// The path variables **must not** capture the leading "/" character. The reason +// is that the most common use case "{var}" does not capture the leading "/" +// character. For consistency, all path variables must share the same behavior. +// +// Repeated message fields must not be mapped to URL query parameters, because +// no client library can support such complicated mapping. +// +// If an API needs to use a JSON array for request or response body, it can map +// the request or response body to a repeated field. However, some gRPC +// Transcoding implementations may not support this feature. +message HttpRule { + // Selects a method to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax + // details. + string selector = 1; + + // Determines the URL pattern is matched by this rules. This pattern can be + // used with any of the {get|put|post|delete|patch} methods. A custom method + // can be defined using the 'custom' field. + oneof pattern { + // Maps to HTTP GET. Used for listing and getting information about + // resources. + string get = 2; + + // Maps to HTTP PUT. Used for replacing a resource. + string put = 3; + + // Maps to HTTP POST. Used for creating a resource or performing an action. + string post = 4; + + // Maps to HTTP DELETE. Used for deleting a resource. + string delete = 5; + + // Maps to HTTP PATCH. Used for updating a resource. + string patch = 6; + + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. + CustomHttpPattern custom = 8; + } + + // The name of the request field whose value is mapped to the HTTP request + // body, or `*` for mapping all request fields not captured by the path + // pattern to the HTTP body, or omitted for not having any HTTP request body. + // + // NOTE: the referred field must be present at the top-level of the request + // message type. + string body = 7; + + // Optional. The name of the response field whose value is mapped to the HTTP + // response body. When omitted, the entire response message will be used + // as the HTTP response body. + // + // NOTE: The referred field must be present at the top-level of the response + // message type. + string response_body = 12; + + // Additional HTTP bindings for the selector. Nested bindings must + // not contain an `additional_bindings` field themselves (that is, + // the nesting may only be one level deep). + repeated HttpRule additional_bindings = 11; +} + +// A custom pattern is used for defining custom HTTP verb. +message CustomHttpPattern { + // The name of this custom HTTP verb. + string kind = 1; + + // The path matched by this custom verb. + string path = 2; +} \ No newline at end of file diff --git a/seed/java-sdk/grpc-proto/.mock/proto/user/v1/user.proto b/seed/java-sdk/grpc-proto/.mock/proto/user/v1/user.proto new file mode 100644 index 00000000000..a3ad0195bd4 --- /dev/null +++ b/seed/java-sdk/grpc-proto/.mock/proto/user/v1/user.proto @@ -0,0 +1,39 @@ +syntax = "proto3"; + +package user.v1; + +import "google/api/annotations.proto"; +import "google/api/field_behavior.proto"; +import "google/protobuf/struct.proto"; + +option go_package = "user/v1"; +option csharp_namespace = "User.V1"; + +message UserModel { + string username = 1; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateRequest { + string username = 1; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateResponse { + UserModel user = 1; +} + +service User { + rpc Create(CreateRequest) returns (CreateResponse) { + option (google.api.http) = { + post: "/users" + body: "*" + }; + } +} \ No newline at end of file diff --git a/seed/java-sdk/grpc-proto/build.gradle b/seed/java-sdk/grpc-proto/build.gradle new file mode 100644 index 00000000000..e8da3626f05 --- /dev/null +++ b/seed/java-sdk/grpc-proto/build.gradle @@ -0,0 +1,70 @@ +plugins { + id 'java-library' + id 'maven-publish' + id 'com.diffplug.spotless' version '6.11.0' +} + +repositories { + mavenCentral() + maven { + url 'https://s01.oss.sonatype.org/content/repositories/releases/' + } +} + +dependencies { + api 'com.squareup.okhttp3:okhttp:4.12.0' + api 'com.fasterxml.jackson.core:jackson-databind:2.13.0' + api 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.12.3' + api 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.12.3' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' + testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.8.2' +} + + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +spotless { + java { + palantirJavaFormat() + } +} + +java { + withSourcesJar() + withJavadocJar() +} + +test { + useJUnitPlatform() + testLogging { + showStandardStreams = true + } +} +publishing { + publications { + maven(MavenPublication) { + groupId = 'com.fern' + artifactId = 'grpc-proto' + version = '0.0.1' + from components.java + pom { + scm { + connection = 'scm:git:git://github.com/grpc-proto/fern.git' + developerConnection = 'scm:git:git://github.com/grpc-proto/fern.git' + url = 'https://github.com/grpc-proto/fern' + } + } + } + } + repositories { + maven { + url "$System.env.MAVEN_PUBLISH_REGISTRY_URL" + credentials { + username "$System.env.MAVEN_USERNAME" + password "$System.env.MAVEN_PASSWORD" + } + } + } +} + diff --git a/seed/java-sdk/grpc-proto/sample-app/build.gradle b/seed/java-sdk/grpc-proto/sample-app/build.gradle new file mode 100644 index 00000000000..4ee8f227b7a --- /dev/null +++ b/seed/java-sdk/grpc-proto/sample-app/build.gradle @@ -0,0 +1,19 @@ +plugins { + id 'java-library' +} + +repositories { + mavenCentral() + maven { + url 'https://s01.oss.sonatype.org/content/repositories/releases/' + } +} + +dependencies { + implementation rootProject +} + + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + diff --git a/seed/java-sdk/grpc-proto/sample-app/src/main/java/sample/App.java b/seed/java-sdk/grpc-proto/sample-app/src/main/java/sample/App.java new file mode 100644 index 00000000000..1211d90cc01 --- /dev/null +++ b/seed/java-sdk/grpc-proto/sample-app/src/main/java/sample/App.java @@ -0,0 +1,13 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package sample; + +import java.lang.String; + +public final class App { + public static void main(String[] args) { + // import com.seed.api.SeedApiClient + } +} diff --git a/seed/java-sdk/grpc-proto/settings.gradle b/seed/java-sdk/grpc-proto/settings.gradle new file mode 100644 index 00000000000..aed36fec10b --- /dev/null +++ b/seed/java-sdk/grpc-proto/settings.gradle @@ -0,0 +1 @@ +include 'sample-app' \ No newline at end of file diff --git a/seed/java-sdk/grpc-proto/snippet-templates.json b/seed/java-sdk/grpc-proto/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/java-sdk/grpc-proto/snippet.json b/seed/java-sdk/grpc-proto/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/SeedApiClient.java b/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/SeedApiClient.java new file mode 100644 index 00000000000..bdd13c80319 --- /dev/null +++ b/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/SeedApiClient.java @@ -0,0 +1,28 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +import com.seed.api.core.ClientOptions; +import com.seed.api.core.Suppliers; +import com.seed.api.resources.user.UserClient; +import java.util.function.Supplier; + +public class SeedApiClient { + protected final ClientOptions clientOptions; + + protected final Supplier userClient; + + public SeedApiClient(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + this.userClient = Suppliers.memoize(() -> new UserClient(clientOptions)); + } + + public UserClient user() { + return this.userClient.get(); + } + + public static SeedApiClientBuilder builder() { + return new SeedApiClientBuilder(); + } +} diff --git a/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/SeedApiClientBuilder.java b/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/SeedApiClientBuilder.java new file mode 100644 index 00000000000..993a5726b6e --- /dev/null +++ b/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/SeedApiClientBuilder.java @@ -0,0 +1,23 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +import com.seed.api.core.ClientOptions; +import com.seed.api.core.Environment; + +public final class SeedApiClientBuilder { + private ClientOptions.Builder clientOptionsBuilder = ClientOptions.builder(); + + private Environment environment; + + public SeedApiClientBuilder url(String url) { + this.environment = Environment.custom(url); + return this; + } + + public SeedApiClient build() { + clientOptionsBuilder.environment(this.environment); + return new SeedApiClient(clientOptionsBuilder.build()); + } +} diff --git a/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/core/ClientOptions.java b/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/core/ClientOptions.java new file mode 100644 index 00000000000..4968c2526f8 --- /dev/null +++ b/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/core/ClientOptions.java @@ -0,0 +1,103 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; +import okhttp3.OkHttpClient; + +public final class ClientOptions { + private final Environment environment; + + private final Map headers; + + private final Map> headerSuppliers; + + private final OkHttpClient httpClient; + + private ClientOptions( + Environment environment, + Map headers, + Map> headerSuppliers, + OkHttpClient httpClient) { + this.environment = environment; + this.headers = new HashMap<>(); + this.headers.putAll(headers); + this.headers.putAll(new HashMap() { + { + put("X-Fern-Language", "JAVA"); + } + }); + this.headerSuppliers = headerSuppliers; + this.httpClient = httpClient; + } + + public Environment environment() { + return this.environment; + } + + public Map headers(RequestOptions requestOptions) { + Map values = new HashMap<>(this.headers); + headerSuppliers.forEach((key, supplier) -> { + values.put(key, supplier.get()); + }); + if (requestOptions != null) { + values.putAll(requestOptions.getHeaders()); + } + return values; + } + + public OkHttpClient httpClient() { + return this.httpClient; + } + + public OkHttpClient httpClientWithTimeout(RequestOptions requestOptions) { + if (requestOptions == null) { + return this.httpClient; + } + return this.httpClient + .newBuilder() + .callTimeout(requestOptions.getTimeout().get(), requestOptions.getTimeoutTimeUnit()) + .connectTimeout(0, TimeUnit.SECONDS) + .writeTimeout(0, TimeUnit.SECONDS) + .readTimeout(0, TimeUnit.SECONDS) + .build(); + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private Environment environment; + + private final Map headers = new HashMap<>(); + + private final Map> headerSuppliers = new HashMap<>(); + + public Builder environment(Environment environment) { + this.environment = environment; + return this; + } + + public Builder addHeader(String key, String value) { + this.headers.put(key, value); + return this; + } + + public Builder addHeader(String key, Supplier value) { + this.headerSuppliers.put(key, value); + return this; + } + + public ClientOptions build() { + OkHttpClient okhttpClient = new OkHttpClient.Builder() + .addInterceptor(new RetryInterceptor(3)) + .build(); + return new ClientOptions(environment, headers, headerSuppliers, okhttpClient); + } + } +} diff --git a/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/core/DateTimeDeserializer.java b/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/core/DateTimeDeserializer.java new file mode 100644 index 00000000000..eac7d50c71a --- /dev/null +++ b/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/core/DateTimeDeserializer.java @@ -0,0 +1,55 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.module.SimpleModule; +import java.io.IOException; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalQueries; + +/** + * Custom deserializer that handles converting ISO8601 dates into {@link OffsetDateTime} objects. + */ +class DateTimeDeserializer extends JsonDeserializer { + private static final SimpleModule MODULE; + + static { + MODULE = new SimpleModule().addDeserializer(OffsetDateTime.class, new DateTimeDeserializer()); + } + + /** + * Gets a module wrapping this deserializer as an adapter for the Jackson ObjectMapper. + * + * @return A {@link SimpleModule} to be plugged onto Jackson ObjectMapper. + */ + public static SimpleModule getModule() { + return MODULE; + } + + @Override + public OffsetDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException { + JsonToken token = parser.currentToken(); + if (token == JsonToken.VALUE_NUMBER_INT) { + return OffsetDateTime.ofInstant(Instant.ofEpochSecond(parser.getValueAsLong()), ZoneOffset.UTC); + } else { + TemporalAccessor temporal = DateTimeFormatter.ISO_DATE_TIME.parseBest( + parser.getValueAsString(), OffsetDateTime::from, LocalDateTime::from); + + if (temporal.query(TemporalQueries.offset()) == null) { + return LocalDateTime.from(temporal).atOffset(ZoneOffset.UTC); + } else { + return OffsetDateTime.from(temporal); + } + } + } +} diff --git a/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/core/Environment.java b/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/core/Environment.java new file mode 100644 index 00000000000..8a286722bb2 --- /dev/null +++ b/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/core/Environment.java @@ -0,0 +1,20 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +public final class Environment { + private final String url; + + private Environment(String url) { + this.url = url; + } + + public String getUrl() { + return this.url; + } + + public static Environment custom(String url) { + return new Environment(url); + } +} diff --git a/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/core/MediaTypes.java b/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/core/MediaTypes.java new file mode 100644 index 00000000000..4a8d1cf301d --- /dev/null +++ b/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/core/MediaTypes.java @@ -0,0 +1,13 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import okhttp3.MediaType; + +public final class MediaTypes { + + public static final MediaType APPLICATION_JSON = MediaType.parse("application/json"); + + private MediaTypes() {} +} diff --git a/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/core/ObjectMappers.java b/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/core/ObjectMappers.java new file mode 100644 index 00000000000..0b16d472dca --- /dev/null +++ b/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/core/ObjectMappers.java @@ -0,0 +1,36 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import java.io.IOException; + +public final class ObjectMappers { + public static final ObjectMapper JSON_MAPPER = JsonMapper.builder() + .addModule(new Jdk8Module()) + .addModule(new JavaTimeModule()) + .addModule(DateTimeDeserializer.getModule()) + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .build(); + + private ObjectMappers() {} + + public static String stringify(Object o) { + try { + return JSON_MAPPER + .setSerializationInclusion(JsonInclude.Include.ALWAYS) + .writerWithDefaultPrettyPrinter() + .writeValueAsString(o); + } catch (IOException e) { + return o.getClass().getName() + "@" + Integer.toHexString(o.hashCode()); + } + } +} diff --git a/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/core/RequestOptions.java b/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/core/RequestOptions.java new file mode 100644 index 00000000000..e74606352e4 --- /dev/null +++ b/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/core/RequestOptions.java @@ -0,0 +1,58 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +public final class RequestOptions { + private final Optional timeout; + + private final TimeUnit timeoutTimeUnit; + + private RequestOptions(Optional timeout, TimeUnit timeoutTimeUnit) { + this.timeout = timeout; + this.timeoutTimeUnit = timeoutTimeUnit; + } + + public Optional getTimeout() { + return timeout; + } + + public TimeUnit getTimeoutTimeUnit() { + return timeoutTimeUnit; + } + + public Map getHeaders() { + Map headers = new HashMap<>(); + return headers; + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private Optional timeout = Optional.empty(); + + private TimeUnit timeoutTimeUnit = TimeUnit.SECONDS; + + public Builder timeout(Integer timeout) { + this.timeout = Optional.of(timeout); + return this; + } + + public Builder timeout(Integer timeout, TimeUnit timeoutTimeUnit) { + this.timeout = Optional.of(timeout); + this.timeoutTimeUnit = timeoutTimeUnit; + return this; + } + + public RequestOptions build() { + return new RequestOptions(timeout, timeoutTimeUnit); + } + } +} diff --git a/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/core/ResponseBodyInputStream.java b/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/core/ResponseBodyInputStream.java new file mode 100644 index 00000000000..db05d538255 --- /dev/null +++ b/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/core/ResponseBodyInputStream.java @@ -0,0 +1,45 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.io.FilterInputStream; +import java.io.IOException; +import okhttp3.Response; + +/** + * A custom InputStream that wraps the InputStream from the OkHttp Response and ensures that the + * OkHttp Response object is properly closed when the stream is closed. + * + * This class extends FilterInputStream and takes an OkHttp Response object as a parameter. + * It retrieves the InputStream from the Response and overrides the close method to close + * both the InputStream and the Response object, ensuring proper resource management and preventing + * premature closure of the underlying HTTP connection. + */ +public class ResponseBodyInputStream extends FilterInputStream { + private final Response response; + + /** + * Constructs a ResponseBodyInputStream that wraps the InputStream from the given OkHttp + * Response object. + * + * @param response the OkHttp Response object from which the InputStream is retrieved + * @throws IOException if an I/O error occurs while retrieving the InputStream + */ + public ResponseBodyInputStream(Response response) throws IOException { + super(response.body().byteStream()); + this.response = response; + } + + /** + * Closes the InputStream and the associated OkHttp Response object. This ensures that the + * underlying HTTP connection is properly closed after the stream is no longer needed. + * + * @throws IOException if an I/O error occurs + */ + @Override + public void close() throws IOException { + super.close(); + response.close(); // Ensure the response is closed when the stream is closed + } +} diff --git a/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/core/ResponseBodyReader.java b/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/core/ResponseBodyReader.java new file mode 100644 index 00000000000..97fcf7a0efb --- /dev/null +++ b/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/core/ResponseBodyReader.java @@ -0,0 +1,44 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.io.FilterReader; +import java.io.IOException; +import okhttp3.Response; + +/** + * A custom Reader that wraps the Reader from the OkHttp Response and ensures that the + * OkHttp Response object is properly closed when the reader is closed. + * + * This class extends FilterReader and takes an OkHttp Response object as a parameter. + * It retrieves the Reader from the Response and overrides the close method to close + * both the Reader and the Response object, ensuring proper resource management and preventing + * premature closure of the underlying HTTP connection. + */ +public class ResponseBodyReader extends FilterReader { + private final Response response; + + /** + * Constructs a ResponseBodyReader that wraps the Reader from the given OkHttp Response object. + * + * @param response the OkHttp Response object from which the Reader is retrieved + * @throws IOException if an I/O error occurs while retrieving the Reader + */ + public ResponseBodyReader(Response response) throws IOException { + super(response.body().charStream()); + this.response = response; + } + + /** + * Closes the Reader and the associated OkHttp Response object. This ensures that the + * underlying HTTP connection is properly closed after the reader is no longer needed. + * + * @throws IOException if an I/O error occurs + */ + @Override + public void close() throws IOException { + super.close(); + response.close(); // Ensure the response is closed when the reader is closed + } +} diff --git a/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/core/RetryInterceptor.java b/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/core/RetryInterceptor.java new file mode 100644 index 00000000000..e60c078b7dc --- /dev/null +++ b/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/core/RetryInterceptor.java @@ -0,0 +1,78 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.io.IOException; +import java.time.Duration; +import java.util.Optional; +import java.util.Random; +import okhttp3.Interceptor; +import okhttp3.Response; + +public class RetryInterceptor implements Interceptor { + + private static final Duration ONE_SECOND = Duration.ofSeconds(1); + private final ExponentialBackoff backoff; + private final Random random = new Random(); + + public RetryInterceptor(int maxRetries) { + this.backoff = new ExponentialBackoff(maxRetries); + } + + @Override + public Response intercept(Chain chain) throws IOException { + Response response = chain.proceed(chain.request()); + + if (shouldRetry(response.code())) { + return retryChain(response, chain); + } + + return response; + } + + private Response retryChain(Response response, Chain chain) throws IOException { + Optional nextBackoff = this.backoff.nextBackoff(); + while (nextBackoff.isPresent()) { + try { + Thread.sleep(nextBackoff.get().toMillis()); + } catch (InterruptedException e) { + throw new IOException("Interrupted while trying request", e); + } + response.close(); + response = chain.proceed(chain.request()); + if (shouldRetry(response.code())) { + nextBackoff = this.backoff.nextBackoff(); + } else { + return response; + } + } + + return response; + } + + private static boolean shouldRetry(int statusCode) { + return statusCode == 408 || statusCode == 409 || statusCode == 429 || statusCode >= 500; + } + + private final class ExponentialBackoff { + + private final int maxNumRetries; + + private int retryNumber = 0; + + ExponentialBackoff(int maxNumRetries) { + this.maxNumRetries = maxNumRetries; + } + + public Optional nextBackoff() { + retryNumber += 1; + if (retryNumber > maxNumRetries) { + return Optional.empty(); + } + + int upperBound = (int) Math.pow(2, retryNumber); + return Optional.of(ONE_SECOND.multipliedBy(random.nextInt(upperBound))); + } + } +} diff --git a/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/core/SeedApiApiException.java b/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/core/SeedApiApiException.java new file mode 100644 index 00000000000..338c0b876e5 --- /dev/null +++ b/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/core/SeedApiApiException.java @@ -0,0 +1,45 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +/** + * This exception type will be thrown for any non-2XX API responses. + */ +public class SeedApiApiException extends SeedApiException { + /** + * The error code of the response that triggered the exception. + */ + private final int statusCode; + + /** + * The body of the response that triggered the exception. + */ + private final Object body; + + public SeedApiApiException(String message, int statusCode, Object body) { + super(message); + this.statusCode = statusCode; + this.body = body; + } + + /** + * @return the statusCode + */ + public int statusCode() { + return this.statusCode; + } + + /** + * @return the body + */ + public Object body() { + return this.body; + } + + @java.lang.Override + public String toString() { + return "SeedApiApiException{" + "message: " + getMessage() + ", statusCode: " + statusCode + ", body: " + body + + "}"; + } +} diff --git a/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/core/SeedApiException.java b/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/core/SeedApiException.java new file mode 100644 index 00000000000..cf823606667 --- /dev/null +++ b/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/core/SeedApiException.java @@ -0,0 +1,17 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +/** + * This class serves as the base exception for all errors in the SDK. + */ +public class SeedApiException extends RuntimeException { + public SeedApiException(String message) { + super(message); + } + + public SeedApiException(String message, Exception e) { + super(message, e); + } +} diff --git a/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/core/Stream.java b/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/core/Stream.java new file mode 100644 index 00000000000..96bf492958e --- /dev/null +++ b/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/core/Stream.java @@ -0,0 +1,97 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.io.Reader; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Scanner; + +/** + * The {@code Stream} class implmenets {@link Iterable} to provide a simple mechanism for reading and parsing + * objects of a given type from data streamed via a {@link Reader} using a specified delimiter. + *

+ * {@code Stream} assumes that data is being pushed to the provided {@link Reader} asynchronously and utilizes a + * {@code Scanner} to block during iteration if the next object is not available. + * + * @param The type of objects in the stream. + */ +public final class Stream implements Iterable { + /** + * The {@link Class} of the objects in the stream. + */ + private final Class valueType; + /** + * The {@link Scanner} used for reading from the input stream and blocking when neede during iteration. + */ + private final Scanner scanner; + + /** + * Constructs a new {@code Stream} with the specified value type, reader, and delimiter. + * + * @param valueType The class of the objects in the stream. + * @param reader The reader that provides the streamed data. + * @param delimiter The delimiter used to separate elements in the stream. + */ + public Stream(Class valueType, Reader reader, String delimiter) { + this.scanner = new Scanner(reader).useDelimiter(delimiter); + this.valueType = valueType; + } + + /** + * Returns an iterator over the elements in this stream that blocks during iteration when the next object is + * not yet available. + * + * @return An iterator that can be used to traverse the elements in the stream. + */ + @Override + public Iterator iterator() { + return new Iterator() { + /** + * Returns {@code true} if there are more elements in the stream. + *

+ * Will block and wait for input if the stream has not ended and the next object is not yet available. + * + * @return {@code true} if there are more elements, {@code false} otherwise. + */ + @Override + public boolean hasNext() { + return scanner.hasNext(); + } + + /** + * Returns the next element in the stream. + *

+ * Will block and wait for input if the stream has not ended and the next object is not yet available. + * + * @return The next element in the stream. + * @throws NoSuchElementException If there are no more elements in the stream. + */ + @Override + public T next() { + if (!scanner.hasNext()) { + throw new NoSuchElementException(); + } else { + try { + T parsedResponse = ObjectMappers.JSON_MAPPER.readValue( + scanner.next().trim(), valueType); + return parsedResponse; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + + /** + * Removing elements from {@code Stream} is not supported. + * + * @throws UnsupportedOperationException Always, as removal is not supported. + */ + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } +} diff --git a/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/core/Suppliers.java b/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/core/Suppliers.java new file mode 100644 index 00000000000..a3c24e96857 --- /dev/null +++ b/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/core/Suppliers.java @@ -0,0 +1,23 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; + +public final class Suppliers { + private Suppliers() {} + + public static Supplier memoize(Supplier delegate) { + AtomicReference value = new AtomicReference<>(); + return () -> { + T val = value.get(); + if (val == null) { + val = value.updateAndGet(cur -> cur == null ? Objects.requireNonNull(delegate.get()) : cur); + } + return val; + }; + } +} diff --git a/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/resources/user/UserClient.java b/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/resources/user/UserClient.java new file mode 100644 index 00000000000..f7870360866 --- /dev/null +++ b/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/resources/user/UserClient.java @@ -0,0 +1,75 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.resources.user; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.seed.api.core.ClientOptions; +import com.seed.api.core.MediaTypes; +import com.seed.api.core.ObjectMappers; +import com.seed.api.core.RequestOptions; +import com.seed.api.core.SeedApiApiException; +import com.seed.api.core.SeedApiException; +import com.seed.api.resources.user.requests.CreateRequest; +import com.seed.api.types.CreateResponse; +import java.io.IOException; +import okhttp3.Headers; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import okhttp3.ResponseBody; + +public class UserClient { + protected final ClientOptions clientOptions; + + public UserClient(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + } + + public CreateResponse create() { + return create(CreateRequest.builder().build()); + } + + public CreateResponse create(CreateRequest request) { + return create(request, null); + } + + public CreateResponse create(CreateRequest request, RequestOptions requestOptions) { + HttpUrl httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("users") + .build(); + RequestBody body; + try { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); + } catch (JsonProcessingException e) { + throw new SeedApiException("Failed to serialize request", e); + } + Request okhttpRequest = new Request.Builder() + .url(httpUrl) + .method("POST", body) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Content-Type", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + try (Response response = client.newCall(okhttpRequest).execute()) { + ResponseBody responseBody = response.body(); + if (response.isSuccessful()) { + return ObjectMappers.JSON_MAPPER.readValue(responseBody.string(), CreateResponse.class); + } + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + throw new SeedApiApiException( + "Error with status code " + response.code(), + response.code(), + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class)); + } catch (IOException e) { + throw new SeedApiException("Network error executing HTTP request", e); + } + } +} diff --git a/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/resources/user/requests/CreateRequest.java b/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/resources/user/requests/CreateRequest.java new file mode 100644 index 00000000000..df9bf23f83b --- /dev/null +++ b/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/resources/user/requests/CreateRequest.java @@ -0,0 +1,193 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.resources.user.requests; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = CreateRequest.Builder.class) +public final class CreateRequest { + private final Optional username; + + private final Optional email; + + private final Optional age; + + private final Optional weight; + + private final Optional> metadata; + + private final Map additionalProperties; + + private CreateRequest( + Optional username, + Optional email, + Optional age, + Optional weight, + Optional> metadata, + Map additionalProperties) { + this.username = username; + this.email = email; + this.age = age; + this.weight = weight; + this.metadata = metadata; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("username") + public Optional getUsername() { + return username; + } + + @JsonProperty("email") + public Optional getEmail() { + return email; + } + + @JsonProperty("age") + public Optional getAge() { + return age; + } + + @JsonProperty("weight") + public Optional getWeight() { + return weight; + } + + @JsonProperty("metadata") + public Optional> getMetadata() { + return metadata; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof CreateRequest && equalTo((CreateRequest) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(CreateRequest other) { + return username.equals(other.username) + && email.equals(other.email) + && age.equals(other.age) + && weight.equals(other.weight) + && metadata.equals(other.metadata); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.username, this.email, this.age, this.weight, this.metadata); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + private Optional username = Optional.empty(); + + private Optional email = Optional.empty(); + + private Optional age = Optional.empty(); + + private Optional weight = Optional.empty(); + + private Optional> metadata = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(CreateRequest other) { + username(other.getUsername()); + email(other.getEmail()); + age(other.getAge()); + weight(other.getWeight()); + metadata(other.getMetadata()); + return this; + } + + @JsonSetter(value = "username", nulls = Nulls.SKIP) + public Builder username(Optional username) { + this.username = username; + return this; + } + + public Builder username(String username) { + this.username = Optional.ofNullable(username); + return this; + } + + @JsonSetter(value = "email", nulls = Nulls.SKIP) + public Builder email(Optional email) { + this.email = email; + return this; + } + + public Builder email(String email) { + this.email = Optional.ofNullable(email); + return this; + } + + @JsonSetter(value = "age", nulls = Nulls.SKIP) + public Builder age(Optional age) { + this.age = age; + return this; + } + + public Builder age(Integer age) { + this.age = Optional.ofNullable(age); + return this; + } + + @JsonSetter(value = "weight", nulls = Nulls.SKIP) + public Builder weight(Optional weight) { + this.weight = weight; + return this; + } + + public Builder weight(Double weight) { + this.weight = Optional.ofNullable(weight); + return this; + } + + @JsonSetter(value = "metadata", nulls = Nulls.SKIP) + public Builder metadata(Optional> metadata) { + this.metadata = metadata; + return this; + } + + public Builder metadata(Map metadata) { + this.metadata = Optional.ofNullable(metadata); + return this; + } + + public CreateRequest build() { + return new CreateRequest(username, email, age, weight, metadata, additionalProperties); + } + } +} diff --git a/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/types/CreateResponse.java b/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/types/CreateResponse.java new file mode 100644 index 00000000000..d424fc73ad8 --- /dev/null +++ b/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/types/CreateResponse.java @@ -0,0 +1,95 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = CreateResponse.Builder.class) +public final class CreateResponse { + private final Optional user; + + private final Map additionalProperties; + + private CreateResponse(Optional user, Map additionalProperties) { + this.user = user; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("user") + public Optional getUser() { + return user; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof CreateResponse && equalTo((CreateResponse) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(CreateResponse other) { + return user.equals(other.user); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.user); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + private Optional user = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(CreateResponse other) { + user(other.getUser()); + return this; + } + + @JsonSetter(value = "user", nulls = Nulls.SKIP) + public Builder user(Optional user) { + this.user = user; + return this; + } + + public Builder user(UserModel user) { + this.user = Optional.ofNullable(user); + return this; + } + + public CreateResponse build() { + return new CreateResponse(user, additionalProperties); + } + } +} diff --git a/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/types/UserModel.java b/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/types/UserModel.java new file mode 100644 index 00000000000..31f587ba0e1 --- /dev/null +++ b/seed/java-sdk/grpc-proto/src/main/java/com/seed/api/types/UserModel.java @@ -0,0 +1,193 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = UserModel.Builder.class) +public final class UserModel { + private final Optional username; + + private final Optional email; + + private final Optional age; + + private final Optional weight; + + private final Optional> metadata; + + private final Map additionalProperties; + + private UserModel( + Optional username, + Optional email, + Optional age, + Optional weight, + Optional> metadata, + Map additionalProperties) { + this.username = username; + this.email = email; + this.age = age; + this.weight = weight; + this.metadata = metadata; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("username") + public Optional getUsername() { + return username; + } + + @JsonProperty("email") + public Optional getEmail() { + return email; + } + + @JsonProperty("age") + public Optional getAge() { + return age; + } + + @JsonProperty("weight") + public Optional getWeight() { + return weight; + } + + @JsonProperty("metadata") + public Optional> getMetadata() { + return metadata; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof UserModel && equalTo((UserModel) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(UserModel other) { + return username.equals(other.username) + && email.equals(other.email) + && age.equals(other.age) + && weight.equals(other.weight) + && metadata.equals(other.metadata); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.username, this.email, this.age, this.weight, this.metadata); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + private Optional username = Optional.empty(); + + private Optional email = Optional.empty(); + + private Optional age = Optional.empty(); + + private Optional weight = Optional.empty(); + + private Optional> metadata = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(UserModel other) { + username(other.getUsername()); + email(other.getEmail()); + age(other.getAge()); + weight(other.getWeight()); + metadata(other.getMetadata()); + return this; + } + + @JsonSetter(value = "username", nulls = Nulls.SKIP) + public Builder username(Optional username) { + this.username = username; + return this; + } + + public Builder username(String username) { + this.username = Optional.ofNullable(username); + return this; + } + + @JsonSetter(value = "email", nulls = Nulls.SKIP) + public Builder email(Optional email) { + this.email = email; + return this; + } + + public Builder email(String email) { + this.email = Optional.ofNullable(email); + return this; + } + + @JsonSetter(value = "age", nulls = Nulls.SKIP) + public Builder age(Optional age) { + this.age = age; + return this; + } + + public Builder age(Integer age) { + this.age = Optional.ofNullable(age); + return this; + } + + @JsonSetter(value = "weight", nulls = Nulls.SKIP) + public Builder weight(Optional weight) { + this.weight = weight; + return this; + } + + public Builder weight(Double weight) { + this.weight = Optional.ofNullable(weight); + return this; + } + + @JsonSetter(value = "metadata", nulls = Nulls.SKIP) + public Builder metadata(Optional> metadata) { + this.metadata = metadata; + return this; + } + + public Builder metadata(Map metadata) { + this.metadata = Optional.ofNullable(metadata); + return this; + } + + public UserModel build() { + return new UserModel(username, email, age, weight, metadata, additionalProperties); + } + } +} diff --git a/seed/java-sdk/grpc-proto/src/test/java/com/seed/api/TestClient.java b/seed/java-sdk/grpc-proto/src/test/java/com/seed/api/TestClient.java new file mode 100644 index 00000000000..1686cfd803c --- /dev/null +++ b/seed/java-sdk/grpc-proto/src/test/java/com/seed/api/TestClient.java @@ -0,0 +1,11 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +public final class TestClient { + public void test() { + // Add tests here and mark this file in .fernignore + assert true; + } +} diff --git a/seed/java-sdk/grpc/.github/workflows/ci.yml b/seed/java-sdk/grpc/.github/workflows/ci.yml new file mode 100644 index 00000000000..8598a73092a --- /dev/null +++ b/seed/java-sdk/grpc/.github/workflows/ci.yml @@ -0,0 +1,61 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up Java + id: setup-jre + uses: actions/setup-java@v1 + with: + java-version: "11" + architecture: x64 + + - name: Compile + run: ./gradlew compileJava + + test: + needs: [ compile ] + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up Java + id: setup-jre + uses: actions/setup-java@v1 + with: + java-version: "11" + architecture: x64 + + - name: Test + run: ./gradlew test + publish: + needs: [ compile, test ] + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up Java + id: setup-jre + uses: actions/setup-java@v1 + with: + java-version: "11" + architecture: x64 + + - name: Publish to maven + run: | + ./gradlew publish + env: + MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} + MAVEN_PUBLISH_REGISTRY_URL: "" diff --git a/seed/java-sdk/grpc/.gitignore b/seed/java-sdk/grpc/.gitignore new file mode 100644 index 00000000000..d4199abc2cd --- /dev/null +++ b/seed/java-sdk/grpc/.gitignore @@ -0,0 +1,24 @@ +*.class +.project +.gradle +? +.classpath +.checkstyle +.settings +.node +build + +# IntelliJ +*.iml +*.ipr +*.iws +.idea/ +out/ + +# Eclipse/IntelliJ APT +generated_src/ +generated_testSrc/ +generated/ + +bin +build \ No newline at end of file diff --git a/seed/java-sdk/grpc/.mock/definition/api.yml b/seed/java-sdk/grpc/.mock/definition/api.yml new file mode 100644 index 00000000000..15a9f823724 --- /dev/null +++ b/seed/java-sdk/grpc/.mock/definition/api.yml @@ -0,0 +1,4 @@ +name: api + +error-discrimination: + strategy: status-code \ No newline at end of file diff --git a/seed/java-sdk/grpc/.mock/definition/user.yml b/seed/java-sdk/grpc/.mock/definition/user.yml new file mode 100644 index 00000000000..80223300afc --- /dev/null +++ b/seed/java-sdk/grpc/.mock/definition/user.yml @@ -0,0 +1,61 @@ +types: + Metadata: + type: map> + encoding: + proto: + type: google.protobuf.Struct + + MetadataValue: + discriminated: false + union: + - double + - string + - boolean + - list + encoding: + proto: + type: google.protobuf.Value + + User: + properties: + id: string + username: string + email: optional + age: optional + weight: optional + metadata: optional + + CreateUserResponse: + properties: + user: User + +service: + auth: false + base-path: / + transport: + grpc: + service-name: UserService + endpoints: + createUser: + method: POST + path: /users + request: + name: CreateUserRequest + body: + properties: + username: string + email: optional + age: optional + weight: optional + response: CreateUserResponse + + getUser: + method: GET + path: /users + request: + name: GetUserRequest + query-parameters: + username: optional + age: optional + weight: optional + response: User diff --git a/seed/java-sdk/grpc/.mock/fern.config.json b/seed/java-sdk/grpc/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/java-sdk/grpc/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/java-sdk/grpc/.mock/generators.yml b/seed/java-sdk/grpc/.mock/generators.yml new file mode 100644 index 00000000000..f62dfba5843 --- /dev/null +++ b/seed/java-sdk/grpc/.mock/generators.yml @@ -0,0 +1,4 @@ +api: + - proto: + root: proto + target: proto/user/v1/user.proto \ No newline at end of file diff --git a/seed/java-sdk/grpc/.mock/proto/google/api/annotations.proto b/seed/java-sdk/grpc/.mock/proto/google/api/annotations.proto new file mode 100644 index 00000000000..efdab3db6ca --- /dev/null +++ b/seed/java-sdk/grpc/.mock/proto/google/api/annotations.proto @@ -0,0 +1,31 @@ +// Copyright 2015 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/api/http.proto"; +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "AnnotationsProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // See `HttpRule`. + HttpRule http = 72295728; +} diff --git a/seed/java-sdk/grpc/.mock/proto/google/api/field_behavior.proto b/seed/java-sdk/grpc/.mock/proto/google/api/field_behavior.proto new file mode 100644 index 00000000000..344cb0b1fc2 --- /dev/null +++ b/seed/java-sdk/grpc/.mock/proto/google/api/field_behavior.proto @@ -0,0 +1,104 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "FieldBehaviorProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.FieldOptions { + // A designation of a specific field behavior (required, output only, etc.) + // in protobuf messages. + // + // Examples: + // + // string name = 1 [(google.api.field_behavior) = REQUIRED]; + // State state = 1 [(google.api.field_behavior) = OUTPUT_ONLY]; + // google.protobuf.Duration ttl = 1 + // [(google.api.field_behavior) = INPUT_ONLY]; + // google.protobuf.Timestamp expire_time = 1 + // [(google.api.field_behavior) = OUTPUT_ONLY, + // (google.api.field_behavior) = IMMUTABLE]; + repeated google.api.FieldBehavior field_behavior = 1052; +} + +// An indicator of the behavior of a given field (for example, that a field +// is required in requests, or given as output but ignored as input). +// This **does not** change the behavior in protocol buffers itself; it only +// denotes the behavior and may affect how API tooling handles the field. +// +// Note: This enum **may** receive new values in the future. +enum FieldBehavior { + // Conventional default for enums. Do not use this. + FIELD_BEHAVIOR_UNSPECIFIED = 0; + + // Specifically denotes a field as optional. + // While all fields in protocol buffers are optional, this may be specified + // for emphasis if appropriate. + OPTIONAL = 1; + + // Denotes a field as required. + // This indicates that the field **must** be provided as part of the request, + // and failure to do so will cause an error (usually `INVALID_ARGUMENT`). + REQUIRED = 2; + + // Denotes a field as output only. + // This indicates that the field is provided in responses, but including the + // field in a request does nothing (the server *must* ignore it and + // *must not* throw an error as a result of the field's presence). + OUTPUT_ONLY = 3; + + // Denotes a field as input only. + // This indicates that the field is provided in requests, and the + // corresponding field is not included in output. + INPUT_ONLY = 4; + + // Denotes a field as immutable. + // This indicates that the field may be set once in a request to create a + // resource, but may not be changed thereafter. + IMMUTABLE = 5; + + // Denotes that a (repeated) field is an unordered list. + // This indicates that the service may provide the elements of the list + // in any arbitrary order, rather than the order the user originally + // provided. Additionally, the list's order may or may not be stable. + UNORDERED_LIST = 6; + + // Denotes that this field returns a non-empty default value if not set. + // This indicates that if the user provides the empty value in a request, + // a non-empty value will be returned. The user will not be aware of what + // non-empty value to expect. + NON_EMPTY_DEFAULT = 7; + + // Denotes that the field in a resource (a message annotated with + // google.api.resource) is used in the resource name to uniquely identify the + // resource. For AIP-compliant APIs, this should only be applied to the + // `name` field on the resource. + // + // This behavior should not be applied to references to other resources within + // the message. + // + // The identifier field of resources often have different field behavior + // depending on the request it is embedded in (e.g. for Create methods name + // is optional and unused, while for Update methods it is required). Instead + // of method-specific annotations, only `IDENTIFIER` is required. + IDENTIFIER = 8; +} diff --git a/seed/java-sdk/grpc/.mock/proto/google/api/http.proto b/seed/java-sdk/grpc/.mock/proto/google/api/http.proto new file mode 100644 index 00000000000..31d867a27d5 --- /dev/null +++ b/seed/java-sdk/grpc/.mock/proto/google/api/http.proto @@ -0,0 +1,379 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "HttpProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Defines the HTTP configuration for an API service. It contains a list of +// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method +// to one or more HTTP REST API methods. +message Http { + // A list of HTTP configuration rules that apply to individual API methods. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated HttpRule rules = 1; + + // When set to true, URL path parameters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // + // The default behavior is to not decode RFC 6570 reserved characters in multi + // segment matches. + bool fully_decode_reserved_expansion = 2; +} + +// # gRPC Transcoding +// +// gRPC Transcoding is a feature for mapping between a gRPC method and one or +// more HTTP REST endpoints. It allows developers to build a single API service +// that supports both gRPC APIs and REST APIs. Many systems, including [Google +// APIs](https://github.com/googleapis/googleapis), +// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC +// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), +// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature +// and use it for large scale production services. +// +// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies +// how different portions of the gRPC request message are mapped to the URL +// path, URL query parameters, and HTTP request body. It also controls how the +// gRPC response message is mapped to the HTTP response body. `HttpRule` is +// typically specified as an `google.api.http` annotation on the gRPC method. +// +// Each mapping specifies a URL path template and an HTTP method. The path +// template may refer to one or more fields in the gRPC request message, as long +// as each field is a non-repeated field with a primitive (non-message) type. +// The path template controls how fields of the request message are mapped to +// the URL path. +// +// Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/{name=messages/*}" +// }; +// } +// } +// message GetMessageRequest { +// string name = 1; // Mapped to URL path. +// } +// message Message { +// string text = 1; // The resource content. +// } +// +// This enables an HTTP REST to gRPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` +// +// Any fields in the request message which are not bound by the path template +// automatically become HTTP query parameters if there is no HTTP request body. +// For example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get:"/v1/messages/{message_id}" +// }; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // Mapped to URL path. +// int64 revision = 2; // Mapped to URL query parameter `revision`. +// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. +// } +// +// This enables a HTTP JSON to RPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | +// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: +// "foo"))` +// +// Note that fields which are mapped to URL query parameters must have a +// primitive type or a repeated primitive type or a non-repeated message type. +// In the case of a repeated type, the parameter can be repeated in the URL +// as `...?param=A¶m=B`. In the case of a message type, each field of the +// message is mapped to a separate parameter, such as +// `...?foo.a=A&foo.b=B&foo.c=C`. +// +// For HTTP methods that allow a request body, the `body` field +// specifies the mapping. Consider a REST update method on the +// message resource collection: +// +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } +// +// The following HTTP JSON to RPC mapping is enabled, where the +// representation of the JSON in the request body is determined by +// protos JSON encoding: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" message { text: "Hi!" })` +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. This enables the following alternative definition of +// the update method: +// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } +// +// +// The following HTTP JSON to RPC mapping is enabled: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" text: "Hi!")` +// +// Note that when using `*` in the body mapping, it is not possible to +// have HTTP parameters, as all fields not bound by the path end in +// the body. This makes this option more rarely used in practice when +// defining REST APIs. The common usage of `*` is in custom methods +// which don't use the URL at all for transferring data. +// +// It is possible to define multiple HTTP methods for one RPC by using +// the `additional_bindings` option. Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } +// +// This enables the following two alternative HTTP JSON to RPC mappings: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` +// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: +// "123456")` +// +// ## Rules for HTTP mapping +// +// 1. Leaf request fields (recursive expansion nested messages in the request +// message) are classified into three categories: +// - Fields referred by the path template. They are passed via the URL path. +// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They +// are passed via the HTTP +// request body. +// - All other fields are passed via the URL query parameters, and the +// parameter name is the field path in the request message. A repeated +// field can be represented as multiple query parameters under the same +// name. +// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL +// query parameter, all fields +// are passed via URL path and HTTP request body. +// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP +// request body, all +// fields are passed via URL path and URL query parameters. +// +// ### Path template syntax +// +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +// +// The syntax `*` matches a single URL path segment. The syntax `**` matches +// zero or more URL path segments, which must be the last part of the URL path +// except the `Verb`. +// +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable +// matches a single path segment, its template may be omitted, e.g. `{var}` +// is equivalent to `{var=*}`. +// +// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` +// contains any reserved character, such characters should be percent-encoded +// before the matching. +// +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path on the client +// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The +// server side does the reverse decoding. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{var}`. +// +// If a variable contains multiple path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path on the +// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. +// The server side does the reverse decoding, except "%2F" and "%2f" are left +// unchanged. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{+var}`. +// +// ## Using gRPC API Service Configuration +// +// gRPC API Service Configuration (service config) is a configuration language +// for configuring a gRPC service to become a user-facing product. The +// service config is simply the YAML representation of the `google.api.Service` +// proto message. +// +// As an alternative to annotating your proto file, you can configure gRPC +// transcoding in your service config YAML files. You do this by specifying a +// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same +// effect as the proto annotation. This can be particularly useful if you +// have a proto that is reused in multiple services. Note that any transcoding +// specified in the service config will override any matching transcoding +// configuration in the proto. +// +// Example: +// +// http: +// rules: +// # Selects a gRPC method and applies HttpRule to it. +// - selector: example.v1.Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} +// +// ## Special notes +// +// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the +// proto to JSON conversion must follow the [proto3 +// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). +// +// While the single segment variable follows the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String +// Expansion, the multi segment variable **does not** follow RFC 6570 Section +// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding +// for multi segment variables. +// +// The path variables **must not** refer to any repeated or mapped field, +// because client libraries are not capable of handling such variable expansion. +// +// The path variables **must not** capture the leading "/" character. The reason +// is that the most common use case "{var}" does not capture the leading "/" +// character. For consistency, all path variables must share the same behavior. +// +// Repeated message fields must not be mapped to URL query parameters, because +// no client library can support such complicated mapping. +// +// If an API needs to use a JSON array for request or response body, it can map +// the request or response body to a repeated field. However, some gRPC +// Transcoding implementations may not support this feature. +message HttpRule { + // Selects a method to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax + // details. + string selector = 1; + + // Determines the URL pattern is matched by this rules. This pattern can be + // used with any of the {get|put|post|delete|patch} methods. A custom method + // can be defined using the 'custom' field. + oneof pattern { + // Maps to HTTP GET. Used for listing and getting information about + // resources. + string get = 2; + + // Maps to HTTP PUT. Used for replacing a resource. + string put = 3; + + // Maps to HTTP POST. Used for creating a resource or performing an action. + string post = 4; + + // Maps to HTTP DELETE. Used for deleting a resource. + string delete = 5; + + // Maps to HTTP PATCH. Used for updating a resource. + string patch = 6; + + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. + CustomHttpPattern custom = 8; + } + + // The name of the request field whose value is mapped to the HTTP request + // body, or `*` for mapping all request fields not captured by the path + // pattern to the HTTP body, or omitted for not having any HTTP request body. + // + // NOTE: the referred field must be present at the top-level of the request + // message type. + string body = 7; + + // Optional. The name of the response field whose value is mapped to the HTTP + // response body. When omitted, the entire response message will be used + // as the HTTP response body. + // + // NOTE: The referred field must be present at the top-level of the response + // message type. + string response_body = 12; + + // Additional HTTP bindings for the selector. Nested bindings must + // not contain an `additional_bindings` field themselves (that is, + // the nesting may only be one level deep). + repeated HttpRule additional_bindings = 11; +} + +// A custom pattern is used for defining custom HTTP verb. +message CustomHttpPattern { + // The name of this custom HTTP verb. + string kind = 1; + + // The path matched by this custom verb. + string path = 2; +} diff --git a/seed/java-sdk/grpc/.mock/proto/user/v1/user.proto b/seed/java-sdk/grpc/.mock/proto/user/v1/user.proto new file mode 100644 index 00000000000..adab9ceefb9 --- /dev/null +++ b/seed/java-sdk/grpc/.mock/proto/user/v1/user.proto @@ -0,0 +1,55 @@ +syntax = "proto3"; + +package user.v1; + +import "google/api/annotations.proto"; +import "google/api/field_behavior.proto"; +import "google/protobuf/struct.proto"; + +option csharp_namespace = "User.V1"; + +message User { + string username = 1 [ + (google.api.field_behavior) = REQUIRED + ]; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateUserRequest { + string username = 1 [ + (google.api.field_behavior) = REQUIRED + ]; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateUserResponse { + User user = 1; +} + +message GetUserRequest { + string username = 1; + string email = 2; + uint32 age = 3; + float weight = 4; +} + +service UserService { + rpc CreateUser(CreateUserRequest) returns (CreateUserResponse) { + option (google.api.http) = { + post: "/users" + body: "*" + }; + } + + rpc GetUser(GetUserRequest) returns (User) { + option (google.api.http) = { + get: "/users" + }; + } +} diff --git a/seed/java-sdk/grpc/build.gradle b/seed/java-sdk/grpc/build.gradle new file mode 100644 index 00000000000..96e2d7faff5 --- /dev/null +++ b/seed/java-sdk/grpc/build.gradle @@ -0,0 +1,70 @@ +plugins { + id 'java-library' + id 'maven-publish' + id 'com.diffplug.spotless' version '6.11.0' +} + +repositories { + mavenCentral() + maven { + url 'https://s01.oss.sonatype.org/content/repositories/releases/' + } +} + +dependencies { + api 'com.squareup.okhttp3:okhttp:4.12.0' + api 'com.fasterxml.jackson.core:jackson-databind:2.13.0' + api 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.12.3' + api 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.12.3' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' + testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.8.2' +} + + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +spotless { + java { + palantirJavaFormat() + } +} + +java { + withSourcesJar() + withJavadocJar() +} + +test { + useJUnitPlatform() + testLogging { + showStandardStreams = true + } +} +publishing { + publications { + maven(MavenPublication) { + groupId = 'com.fern' + artifactId = 'grpc' + version = '0.0.1' + from components.java + pom { + scm { + connection = 'scm:git:git://github.com/grpc/fern.git' + developerConnection = 'scm:git:git://github.com/grpc/fern.git' + url = 'https://github.com/grpc/fern' + } + } + } + } + repositories { + maven { + url "$System.env.MAVEN_PUBLISH_REGISTRY_URL" + credentials { + username "$System.env.MAVEN_USERNAME" + password "$System.env.MAVEN_PASSWORD" + } + } + } +} + diff --git a/seed/java-sdk/grpc/sample-app/build.gradle b/seed/java-sdk/grpc/sample-app/build.gradle new file mode 100644 index 00000000000..4ee8f227b7a --- /dev/null +++ b/seed/java-sdk/grpc/sample-app/build.gradle @@ -0,0 +1,19 @@ +plugins { + id 'java-library' +} + +repositories { + mavenCentral() + maven { + url 'https://s01.oss.sonatype.org/content/repositories/releases/' + } +} + +dependencies { + implementation rootProject +} + + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + diff --git a/seed/java-sdk/grpc/sample-app/src/main/java/sample/App.java b/seed/java-sdk/grpc/sample-app/src/main/java/sample/App.java new file mode 100644 index 00000000000..1211d90cc01 --- /dev/null +++ b/seed/java-sdk/grpc/sample-app/src/main/java/sample/App.java @@ -0,0 +1,13 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package sample; + +import java.lang.String; + +public final class App { + public static void main(String[] args) { + // import com.seed.api.SeedApiClient + } +} diff --git a/seed/java-sdk/grpc/settings.gradle b/seed/java-sdk/grpc/settings.gradle new file mode 100644 index 00000000000..aed36fec10b --- /dev/null +++ b/seed/java-sdk/grpc/settings.gradle @@ -0,0 +1 @@ +include 'sample-app' \ No newline at end of file diff --git a/seed/java-sdk/grpc/snippet-templates.json b/seed/java-sdk/grpc/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/java-sdk/grpc/snippet.json b/seed/java-sdk/grpc/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/java-sdk/grpc/src/main/java/com/seed/api/SeedApiClient.java b/seed/java-sdk/grpc/src/main/java/com/seed/api/SeedApiClient.java new file mode 100644 index 00000000000..bdd13c80319 --- /dev/null +++ b/seed/java-sdk/grpc/src/main/java/com/seed/api/SeedApiClient.java @@ -0,0 +1,28 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +import com.seed.api.core.ClientOptions; +import com.seed.api.core.Suppliers; +import com.seed.api.resources.user.UserClient; +import java.util.function.Supplier; + +public class SeedApiClient { + protected final ClientOptions clientOptions; + + protected final Supplier userClient; + + public SeedApiClient(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + this.userClient = Suppliers.memoize(() -> new UserClient(clientOptions)); + } + + public UserClient user() { + return this.userClient.get(); + } + + public static SeedApiClientBuilder builder() { + return new SeedApiClientBuilder(); + } +} diff --git a/seed/java-sdk/grpc/src/main/java/com/seed/api/SeedApiClientBuilder.java b/seed/java-sdk/grpc/src/main/java/com/seed/api/SeedApiClientBuilder.java new file mode 100644 index 00000000000..993a5726b6e --- /dev/null +++ b/seed/java-sdk/grpc/src/main/java/com/seed/api/SeedApiClientBuilder.java @@ -0,0 +1,23 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +import com.seed.api.core.ClientOptions; +import com.seed.api.core.Environment; + +public final class SeedApiClientBuilder { + private ClientOptions.Builder clientOptionsBuilder = ClientOptions.builder(); + + private Environment environment; + + public SeedApiClientBuilder url(String url) { + this.environment = Environment.custom(url); + return this; + } + + public SeedApiClient build() { + clientOptionsBuilder.environment(this.environment); + return new SeedApiClient(clientOptionsBuilder.build()); + } +} diff --git a/seed/java-sdk/grpc/src/main/java/com/seed/api/core/ClientOptions.java b/seed/java-sdk/grpc/src/main/java/com/seed/api/core/ClientOptions.java new file mode 100644 index 00000000000..4968c2526f8 --- /dev/null +++ b/seed/java-sdk/grpc/src/main/java/com/seed/api/core/ClientOptions.java @@ -0,0 +1,103 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; +import okhttp3.OkHttpClient; + +public final class ClientOptions { + private final Environment environment; + + private final Map headers; + + private final Map> headerSuppliers; + + private final OkHttpClient httpClient; + + private ClientOptions( + Environment environment, + Map headers, + Map> headerSuppliers, + OkHttpClient httpClient) { + this.environment = environment; + this.headers = new HashMap<>(); + this.headers.putAll(headers); + this.headers.putAll(new HashMap() { + { + put("X-Fern-Language", "JAVA"); + } + }); + this.headerSuppliers = headerSuppliers; + this.httpClient = httpClient; + } + + public Environment environment() { + return this.environment; + } + + public Map headers(RequestOptions requestOptions) { + Map values = new HashMap<>(this.headers); + headerSuppliers.forEach((key, supplier) -> { + values.put(key, supplier.get()); + }); + if (requestOptions != null) { + values.putAll(requestOptions.getHeaders()); + } + return values; + } + + public OkHttpClient httpClient() { + return this.httpClient; + } + + public OkHttpClient httpClientWithTimeout(RequestOptions requestOptions) { + if (requestOptions == null) { + return this.httpClient; + } + return this.httpClient + .newBuilder() + .callTimeout(requestOptions.getTimeout().get(), requestOptions.getTimeoutTimeUnit()) + .connectTimeout(0, TimeUnit.SECONDS) + .writeTimeout(0, TimeUnit.SECONDS) + .readTimeout(0, TimeUnit.SECONDS) + .build(); + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private Environment environment; + + private final Map headers = new HashMap<>(); + + private final Map> headerSuppliers = new HashMap<>(); + + public Builder environment(Environment environment) { + this.environment = environment; + return this; + } + + public Builder addHeader(String key, String value) { + this.headers.put(key, value); + return this; + } + + public Builder addHeader(String key, Supplier value) { + this.headerSuppliers.put(key, value); + return this; + } + + public ClientOptions build() { + OkHttpClient okhttpClient = new OkHttpClient.Builder() + .addInterceptor(new RetryInterceptor(3)) + .build(); + return new ClientOptions(environment, headers, headerSuppliers, okhttpClient); + } + } +} diff --git a/seed/java-sdk/grpc/src/main/java/com/seed/api/core/DateTimeDeserializer.java b/seed/java-sdk/grpc/src/main/java/com/seed/api/core/DateTimeDeserializer.java new file mode 100644 index 00000000000..eac7d50c71a --- /dev/null +++ b/seed/java-sdk/grpc/src/main/java/com/seed/api/core/DateTimeDeserializer.java @@ -0,0 +1,55 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.module.SimpleModule; +import java.io.IOException; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalQueries; + +/** + * Custom deserializer that handles converting ISO8601 dates into {@link OffsetDateTime} objects. + */ +class DateTimeDeserializer extends JsonDeserializer { + private static final SimpleModule MODULE; + + static { + MODULE = new SimpleModule().addDeserializer(OffsetDateTime.class, new DateTimeDeserializer()); + } + + /** + * Gets a module wrapping this deserializer as an adapter for the Jackson ObjectMapper. + * + * @return A {@link SimpleModule} to be plugged onto Jackson ObjectMapper. + */ + public static SimpleModule getModule() { + return MODULE; + } + + @Override + public OffsetDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException { + JsonToken token = parser.currentToken(); + if (token == JsonToken.VALUE_NUMBER_INT) { + return OffsetDateTime.ofInstant(Instant.ofEpochSecond(parser.getValueAsLong()), ZoneOffset.UTC); + } else { + TemporalAccessor temporal = DateTimeFormatter.ISO_DATE_TIME.parseBest( + parser.getValueAsString(), OffsetDateTime::from, LocalDateTime::from); + + if (temporal.query(TemporalQueries.offset()) == null) { + return LocalDateTime.from(temporal).atOffset(ZoneOffset.UTC); + } else { + return OffsetDateTime.from(temporal); + } + } + } +} diff --git a/seed/java-sdk/grpc/src/main/java/com/seed/api/core/Environment.java b/seed/java-sdk/grpc/src/main/java/com/seed/api/core/Environment.java new file mode 100644 index 00000000000..8a286722bb2 --- /dev/null +++ b/seed/java-sdk/grpc/src/main/java/com/seed/api/core/Environment.java @@ -0,0 +1,20 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +public final class Environment { + private final String url; + + private Environment(String url) { + this.url = url; + } + + public String getUrl() { + return this.url; + } + + public static Environment custom(String url) { + return new Environment(url); + } +} diff --git a/seed/java-sdk/grpc/src/main/java/com/seed/api/core/MediaTypes.java b/seed/java-sdk/grpc/src/main/java/com/seed/api/core/MediaTypes.java new file mode 100644 index 00000000000..4a8d1cf301d --- /dev/null +++ b/seed/java-sdk/grpc/src/main/java/com/seed/api/core/MediaTypes.java @@ -0,0 +1,13 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import okhttp3.MediaType; + +public final class MediaTypes { + + public static final MediaType APPLICATION_JSON = MediaType.parse("application/json"); + + private MediaTypes() {} +} diff --git a/seed/java-sdk/grpc/src/main/java/com/seed/api/core/ObjectMappers.java b/seed/java-sdk/grpc/src/main/java/com/seed/api/core/ObjectMappers.java new file mode 100644 index 00000000000..0b16d472dca --- /dev/null +++ b/seed/java-sdk/grpc/src/main/java/com/seed/api/core/ObjectMappers.java @@ -0,0 +1,36 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import java.io.IOException; + +public final class ObjectMappers { + public static final ObjectMapper JSON_MAPPER = JsonMapper.builder() + .addModule(new Jdk8Module()) + .addModule(new JavaTimeModule()) + .addModule(DateTimeDeserializer.getModule()) + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .build(); + + private ObjectMappers() {} + + public static String stringify(Object o) { + try { + return JSON_MAPPER + .setSerializationInclusion(JsonInclude.Include.ALWAYS) + .writerWithDefaultPrettyPrinter() + .writeValueAsString(o); + } catch (IOException e) { + return o.getClass().getName() + "@" + Integer.toHexString(o.hashCode()); + } + } +} diff --git a/seed/java-sdk/grpc/src/main/java/com/seed/api/core/RequestOptions.java b/seed/java-sdk/grpc/src/main/java/com/seed/api/core/RequestOptions.java new file mode 100644 index 00000000000..e74606352e4 --- /dev/null +++ b/seed/java-sdk/grpc/src/main/java/com/seed/api/core/RequestOptions.java @@ -0,0 +1,58 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +public final class RequestOptions { + private final Optional timeout; + + private final TimeUnit timeoutTimeUnit; + + private RequestOptions(Optional timeout, TimeUnit timeoutTimeUnit) { + this.timeout = timeout; + this.timeoutTimeUnit = timeoutTimeUnit; + } + + public Optional getTimeout() { + return timeout; + } + + public TimeUnit getTimeoutTimeUnit() { + return timeoutTimeUnit; + } + + public Map getHeaders() { + Map headers = new HashMap<>(); + return headers; + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private Optional timeout = Optional.empty(); + + private TimeUnit timeoutTimeUnit = TimeUnit.SECONDS; + + public Builder timeout(Integer timeout) { + this.timeout = Optional.of(timeout); + return this; + } + + public Builder timeout(Integer timeout, TimeUnit timeoutTimeUnit) { + this.timeout = Optional.of(timeout); + this.timeoutTimeUnit = timeoutTimeUnit; + return this; + } + + public RequestOptions build() { + return new RequestOptions(timeout, timeoutTimeUnit); + } + } +} diff --git a/seed/java-sdk/grpc/src/main/java/com/seed/api/core/ResponseBodyInputStream.java b/seed/java-sdk/grpc/src/main/java/com/seed/api/core/ResponseBodyInputStream.java new file mode 100644 index 00000000000..db05d538255 --- /dev/null +++ b/seed/java-sdk/grpc/src/main/java/com/seed/api/core/ResponseBodyInputStream.java @@ -0,0 +1,45 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.io.FilterInputStream; +import java.io.IOException; +import okhttp3.Response; + +/** + * A custom InputStream that wraps the InputStream from the OkHttp Response and ensures that the + * OkHttp Response object is properly closed when the stream is closed. + * + * This class extends FilterInputStream and takes an OkHttp Response object as a parameter. + * It retrieves the InputStream from the Response and overrides the close method to close + * both the InputStream and the Response object, ensuring proper resource management and preventing + * premature closure of the underlying HTTP connection. + */ +public class ResponseBodyInputStream extends FilterInputStream { + private final Response response; + + /** + * Constructs a ResponseBodyInputStream that wraps the InputStream from the given OkHttp + * Response object. + * + * @param response the OkHttp Response object from which the InputStream is retrieved + * @throws IOException if an I/O error occurs while retrieving the InputStream + */ + public ResponseBodyInputStream(Response response) throws IOException { + super(response.body().byteStream()); + this.response = response; + } + + /** + * Closes the InputStream and the associated OkHttp Response object. This ensures that the + * underlying HTTP connection is properly closed after the stream is no longer needed. + * + * @throws IOException if an I/O error occurs + */ + @Override + public void close() throws IOException { + super.close(); + response.close(); // Ensure the response is closed when the stream is closed + } +} diff --git a/seed/java-sdk/grpc/src/main/java/com/seed/api/core/ResponseBodyReader.java b/seed/java-sdk/grpc/src/main/java/com/seed/api/core/ResponseBodyReader.java new file mode 100644 index 00000000000..97fcf7a0efb --- /dev/null +++ b/seed/java-sdk/grpc/src/main/java/com/seed/api/core/ResponseBodyReader.java @@ -0,0 +1,44 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.io.FilterReader; +import java.io.IOException; +import okhttp3.Response; + +/** + * A custom Reader that wraps the Reader from the OkHttp Response and ensures that the + * OkHttp Response object is properly closed when the reader is closed. + * + * This class extends FilterReader and takes an OkHttp Response object as a parameter. + * It retrieves the Reader from the Response and overrides the close method to close + * both the Reader and the Response object, ensuring proper resource management and preventing + * premature closure of the underlying HTTP connection. + */ +public class ResponseBodyReader extends FilterReader { + private final Response response; + + /** + * Constructs a ResponseBodyReader that wraps the Reader from the given OkHttp Response object. + * + * @param response the OkHttp Response object from which the Reader is retrieved + * @throws IOException if an I/O error occurs while retrieving the Reader + */ + public ResponseBodyReader(Response response) throws IOException { + super(response.body().charStream()); + this.response = response; + } + + /** + * Closes the Reader and the associated OkHttp Response object. This ensures that the + * underlying HTTP connection is properly closed after the reader is no longer needed. + * + * @throws IOException if an I/O error occurs + */ + @Override + public void close() throws IOException { + super.close(); + response.close(); // Ensure the response is closed when the reader is closed + } +} diff --git a/seed/java-sdk/grpc/src/main/java/com/seed/api/core/RetryInterceptor.java b/seed/java-sdk/grpc/src/main/java/com/seed/api/core/RetryInterceptor.java new file mode 100644 index 00000000000..e60c078b7dc --- /dev/null +++ b/seed/java-sdk/grpc/src/main/java/com/seed/api/core/RetryInterceptor.java @@ -0,0 +1,78 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.io.IOException; +import java.time.Duration; +import java.util.Optional; +import java.util.Random; +import okhttp3.Interceptor; +import okhttp3.Response; + +public class RetryInterceptor implements Interceptor { + + private static final Duration ONE_SECOND = Duration.ofSeconds(1); + private final ExponentialBackoff backoff; + private final Random random = new Random(); + + public RetryInterceptor(int maxRetries) { + this.backoff = new ExponentialBackoff(maxRetries); + } + + @Override + public Response intercept(Chain chain) throws IOException { + Response response = chain.proceed(chain.request()); + + if (shouldRetry(response.code())) { + return retryChain(response, chain); + } + + return response; + } + + private Response retryChain(Response response, Chain chain) throws IOException { + Optional nextBackoff = this.backoff.nextBackoff(); + while (nextBackoff.isPresent()) { + try { + Thread.sleep(nextBackoff.get().toMillis()); + } catch (InterruptedException e) { + throw new IOException("Interrupted while trying request", e); + } + response.close(); + response = chain.proceed(chain.request()); + if (shouldRetry(response.code())) { + nextBackoff = this.backoff.nextBackoff(); + } else { + return response; + } + } + + return response; + } + + private static boolean shouldRetry(int statusCode) { + return statusCode == 408 || statusCode == 409 || statusCode == 429 || statusCode >= 500; + } + + private final class ExponentialBackoff { + + private final int maxNumRetries; + + private int retryNumber = 0; + + ExponentialBackoff(int maxNumRetries) { + this.maxNumRetries = maxNumRetries; + } + + public Optional nextBackoff() { + retryNumber += 1; + if (retryNumber > maxNumRetries) { + return Optional.empty(); + } + + int upperBound = (int) Math.pow(2, retryNumber); + return Optional.of(ONE_SECOND.multipliedBy(random.nextInt(upperBound))); + } + } +} diff --git a/seed/java-sdk/grpc/src/main/java/com/seed/api/core/SeedApiApiException.java b/seed/java-sdk/grpc/src/main/java/com/seed/api/core/SeedApiApiException.java new file mode 100644 index 00000000000..338c0b876e5 --- /dev/null +++ b/seed/java-sdk/grpc/src/main/java/com/seed/api/core/SeedApiApiException.java @@ -0,0 +1,45 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +/** + * This exception type will be thrown for any non-2XX API responses. + */ +public class SeedApiApiException extends SeedApiException { + /** + * The error code of the response that triggered the exception. + */ + private final int statusCode; + + /** + * The body of the response that triggered the exception. + */ + private final Object body; + + public SeedApiApiException(String message, int statusCode, Object body) { + super(message); + this.statusCode = statusCode; + this.body = body; + } + + /** + * @return the statusCode + */ + public int statusCode() { + return this.statusCode; + } + + /** + * @return the body + */ + public Object body() { + return this.body; + } + + @java.lang.Override + public String toString() { + return "SeedApiApiException{" + "message: " + getMessage() + ", statusCode: " + statusCode + ", body: " + body + + "}"; + } +} diff --git a/seed/java-sdk/grpc/src/main/java/com/seed/api/core/SeedApiException.java b/seed/java-sdk/grpc/src/main/java/com/seed/api/core/SeedApiException.java new file mode 100644 index 00000000000..cf823606667 --- /dev/null +++ b/seed/java-sdk/grpc/src/main/java/com/seed/api/core/SeedApiException.java @@ -0,0 +1,17 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +/** + * This class serves as the base exception for all errors in the SDK. + */ +public class SeedApiException extends RuntimeException { + public SeedApiException(String message) { + super(message); + } + + public SeedApiException(String message, Exception e) { + super(message, e); + } +} diff --git a/seed/java-sdk/grpc/src/main/java/com/seed/api/core/Stream.java b/seed/java-sdk/grpc/src/main/java/com/seed/api/core/Stream.java new file mode 100644 index 00000000000..96bf492958e --- /dev/null +++ b/seed/java-sdk/grpc/src/main/java/com/seed/api/core/Stream.java @@ -0,0 +1,97 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.io.Reader; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Scanner; + +/** + * The {@code Stream} class implmenets {@link Iterable} to provide a simple mechanism for reading and parsing + * objects of a given type from data streamed via a {@link Reader} using a specified delimiter. + *

+ * {@code Stream} assumes that data is being pushed to the provided {@link Reader} asynchronously and utilizes a + * {@code Scanner} to block during iteration if the next object is not available. + * + * @param The type of objects in the stream. + */ +public final class Stream implements Iterable { + /** + * The {@link Class} of the objects in the stream. + */ + private final Class valueType; + /** + * The {@link Scanner} used for reading from the input stream and blocking when neede during iteration. + */ + private final Scanner scanner; + + /** + * Constructs a new {@code Stream} with the specified value type, reader, and delimiter. + * + * @param valueType The class of the objects in the stream. + * @param reader The reader that provides the streamed data. + * @param delimiter The delimiter used to separate elements in the stream. + */ + public Stream(Class valueType, Reader reader, String delimiter) { + this.scanner = new Scanner(reader).useDelimiter(delimiter); + this.valueType = valueType; + } + + /** + * Returns an iterator over the elements in this stream that blocks during iteration when the next object is + * not yet available. + * + * @return An iterator that can be used to traverse the elements in the stream. + */ + @Override + public Iterator iterator() { + return new Iterator() { + /** + * Returns {@code true} if there are more elements in the stream. + *

+ * Will block and wait for input if the stream has not ended and the next object is not yet available. + * + * @return {@code true} if there are more elements, {@code false} otherwise. + */ + @Override + public boolean hasNext() { + return scanner.hasNext(); + } + + /** + * Returns the next element in the stream. + *

+ * Will block and wait for input if the stream has not ended and the next object is not yet available. + * + * @return The next element in the stream. + * @throws NoSuchElementException If there are no more elements in the stream. + */ + @Override + public T next() { + if (!scanner.hasNext()) { + throw new NoSuchElementException(); + } else { + try { + T parsedResponse = ObjectMappers.JSON_MAPPER.readValue( + scanner.next().trim(), valueType); + return parsedResponse; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + + /** + * Removing elements from {@code Stream} is not supported. + * + * @throws UnsupportedOperationException Always, as removal is not supported. + */ + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } +} diff --git a/seed/java-sdk/grpc/src/main/java/com/seed/api/core/Suppliers.java b/seed/java-sdk/grpc/src/main/java/com/seed/api/core/Suppliers.java new file mode 100644 index 00000000000..a3c24e96857 --- /dev/null +++ b/seed/java-sdk/grpc/src/main/java/com/seed/api/core/Suppliers.java @@ -0,0 +1,23 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.core; + +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; + +public final class Suppliers { + private Suppliers() {} + + public static Supplier memoize(Supplier delegate) { + AtomicReference value = new AtomicReference<>(); + return () -> { + T val = value.get(); + if (val == null) { + val = value.updateAndGet(cur -> cur == null ? Objects.requireNonNull(delegate.get()) : cur); + } + return val; + }; + } +} diff --git a/seed/java-sdk/grpc/src/main/java/com/seed/api/resources/user/UserClient.java b/seed/java-sdk/grpc/src/main/java/com/seed/api/resources/user/UserClient.java new file mode 100644 index 00000000000..d446abf9d94 --- /dev/null +++ b/seed/java-sdk/grpc/src/main/java/com/seed/api/resources/user/UserClient.java @@ -0,0 +1,119 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.resources.user; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.seed.api.core.ClientOptions; +import com.seed.api.core.MediaTypes; +import com.seed.api.core.ObjectMappers; +import com.seed.api.core.RequestOptions; +import com.seed.api.core.SeedApiApiException; +import com.seed.api.core.SeedApiException; +import com.seed.api.resources.user.requests.CreateUserRequest; +import com.seed.api.resources.user.requests.GetUserRequest; +import com.seed.api.resources.user.types.CreateUserResponse; +import com.seed.api.resources.user.types.User; +import java.io.IOException; +import okhttp3.Headers; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import okhttp3.ResponseBody; + +public class UserClient { + protected final ClientOptions clientOptions; + + public UserClient(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + } + + public CreateUserResponse createUser(CreateUserRequest request) { + return createUser(request, null); + } + + public CreateUserResponse createUser(CreateUserRequest request, RequestOptions requestOptions) { + HttpUrl httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("users") + .build(); + RequestBody body; + try { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); + } catch (JsonProcessingException e) { + throw new SeedApiException("Failed to serialize request", e); + } + Request okhttpRequest = new Request.Builder() + .url(httpUrl) + .method("POST", body) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Content-Type", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + try (Response response = client.newCall(okhttpRequest).execute()) { + ResponseBody responseBody = response.body(); + if (response.isSuccessful()) { + return ObjectMappers.JSON_MAPPER.readValue(responseBody.string(), CreateUserResponse.class); + } + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + throw new SeedApiApiException( + "Error with status code " + response.code(), + response.code(), + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class)); + } catch (IOException e) { + throw new SeedApiException("Network error executing HTTP request", e); + } + } + + public User getUser() { + return getUser(GetUserRequest.builder().build()); + } + + public User getUser(GetUserRequest request) { + return getUser(request, null); + } + + public User getUser(GetUserRequest request, RequestOptions requestOptions) { + HttpUrl.Builder httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("users"); + if (request.getUsername().isPresent()) { + httpUrl.addQueryParameter("username", request.getUsername().get()); + } + if (request.getAge().isPresent()) { + httpUrl.addQueryParameter("age", request.getAge().get().toString()); + } + if (request.getWeight().isPresent()) { + httpUrl.addQueryParameter("weight", request.getWeight().get().toString()); + } + Request.Builder _requestBuilder = new Request.Builder() + .url(httpUrl.build()) + .method("GET", null) + .headers(Headers.of(clientOptions.headers(requestOptions))) + .addHeader("Content-Type", "application/json"); + Request okhttpRequest = _requestBuilder.build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + try (Response response = client.newCall(okhttpRequest).execute()) { + ResponseBody responseBody = response.body(); + if (response.isSuccessful()) { + return ObjectMappers.JSON_MAPPER.readValue(responseBody.string(), User.class); + } + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + throw new SeedApiApiException( + "Error with status code " + response.code(), + response.code(), + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class)); + } catch (IOException e) { + throw new SeedApiException("Network error executing HTTP request", e); + } + } +} diff --git a/seed/java-sdk/grpc/src/main/java/com/seed/api/resources/user/requests/CreateUserRequest.java b/seed/java-sdk/grpc/src/main/java/com/seed/api/resources/user/requests/CreateUserRequest.java new file mode 100644 index 00000000000..2f1bde9d01e --- /dev/null +++ b/seed/java-sdk/grpc/src/main/java/com/seed/api/resources/user/requests/CreateUserRequest.java @@ -0,0 +1,195 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.resources.user.requests; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = CreateUserRequest.Builder.class) +public final class CreateUserRequest { + private final String username; + + private final Optional email; + + private final Optional age; + + private final Optional weight; + + private final Map additionalProperties; + + private CreateUserRequest( + String username, + Optional email, + Optional age, + Optional weight, + Map additionalProperties) { + this.username = username; + this.email = email; + this.age = age; + this.weight = weight; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("username") + public String getUsername() { + return username; + } + + @JsonProperty("email") + public Optional getEmail() { + return email; + } + + @JsonProperty("age") + public Optional getAge() { + return age; + } + + @JsonProperty("weight") + public Optional getWeight() { + return weight; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof CreateUserRequest && equalTo((CreateUserRequest) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(CreateUserRequest other) { + return username.equals(other.username) + && email.equals(other.email) + && age.equals(other.age) + && weight.equals(other.weight); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.username, this.email, this.age, this.weight); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static UsernameStage builder() { + return new Builder(); + } + + public interface UsernameStage { + _FinalStage username(String username); + + Builder from(CreateUserRequest other); + } + + public interface _FinalStage { + CreateUserRequest build(); + + _FinalStage email(Optional email); + + _FinalStage email(String email); + + _FinalStage age(Optional age); + + _FinalStage age(Integer age); + + _FinalStage weight(Optional weight); + + _FinalStage weight(Double weight); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements UsernameStage, _FinalStage { + private String username; + + private Optional weight = Optional.empty(); + + private Optional age = Optional.empty(); + + private Optional email = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(CreateUserRequest other) { + username(other.getUsername()); + email(other.getEmail()); + age(other.getAge()); + weight(other.getWeight()); + return this; + } + + @java.lang.Override + @JsonSetter("username") + public _FinalStage username(String username) { + this.username = username; + return this; + } + + @java.lang.Override + public _FinalStage weight(Double weight) { + this.weight = Optional.ofNullable(weight); + return this; + } + + @java.lang.Override + @JsonSetter(value = "weight", nulls = Nulls.SKIP) + public _FinalStage weight(Optional weight) { + this.weight = weight; + return this; + } + + @java.lang.Override + public _FinalStage age(Integer age) { + this.age = Optional.ofNullable(age); + return this; + } + + @java.lang.Override + @JsonSetter(value = "age", nulls = Nulls.SKIP) + public _FinalStage age(Optional age) { + this.age = age; + return this; + } + + @java.lang.Override + public _FinalStage email(String email) { + this.email = Optional.ofNullable(email); + return this; + } + + @java.lang.Override + @JsonSetter(value = "email", nulls = Nulls.SKIP) + public _FinalStage email(Optional email) { + this.email = email; + return this; + } + + @java.lang.Override + public CreateUserRequest build() { + return new CreateUserRequest(username, email, age, weight, additionalProperties); + } + } +} diff --git a/seed/java-sdk/grpc/src/main/java/com/seed/api/resources/user/requests/GetUserRequest.java b/seed/java-sdk/grpc/src/main/java/com/seed/api/resources/user/requests/GetUserRequest.java new file mode 100644 index 00000000000..51697ab0be0 --- /dev/null +++ b/seed/java-sdk/grpc/src/main/java/com/seed/api/resources/user/requests/GetUserRequest.java @@ -0,0 +1,143 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.resources.user.requests; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = GetUserRequest.Builder.class) +public final class GetUserRequest { + private final Optional username; + + private final Optional age; + + private final Optional weight; + + private final Map additionalProperties; + + private GetUserRequest( + Optional username, + Optional age, + Optional weight, + Map additionalProperties) { + this.username = username; + this.age = age; + this.weight = weight; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("username") + public Optional getUsername() { + return username; + } + + @JsonProperty("age") + public Optional getAge() { + return age; + } + + @JsonProperty("weight") + public Optional getWeight() { + return weight; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof GetUserRequest && equalTo((GetUserRequest) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(GetUserRequest other) { + return username.equals(other.username) && age.equals(other.age) && weight.equals(other.weight); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.username, this.age, this.weight); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder { + private Optional username = Optional.empty(); + + private Optional age = Optional.empty(); + + private Optional weight = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(GetUserRequest other) { + username(other.getUsername()); + age(other.getAge()); + weight(other.getWeight()); + return this; + } + + @JsonSetter(value = "username", nulls = Nulls.SKIP) + public Builder username(Optional username) { + this.username = username; + return this; + } + + public Builder username(String username) { + this.username = Optional.ofNullable(username); + return this; + } + + @JsonSetter(value = "age", nulls = Nulls.SKIP) + public Builder age(Optional age) { + this.age = age; + return this; + } + + public Builder age(Integer age) { + this.age = Optional.ofNullable(age); + return this; + } + + @JsonSetter(value = "weight", nulls = Nulls.SKIP) + public Builder weight(Optional weight) { + this.weight = weight; + return this; + } + + public Builder weight(Double weight) { + this.weight = Optional.ofNullable(weight); + return this; + } + + public GetUserRequest build() { + return new GetUserRequest(username, age, weight, additionalProperties); + } + } +} diff --git a/seed/java-sdk/grpc/src/main/java/com/seed/api/resources/user/types/CreateUserResponse.java b/seed/java-sdk/grpc/src/main/java/com/seed/api/resources/user/types/CreateUserResponse.java new file mode 100644 index 00000000000..6c016e88c91 --- /dev/null +++ b/seed/java-sdk/grpc/src/main/java/com/seed/api/resources/user/types/CreateUserResponse.java @@ -0,0 +1,101 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.resources.user.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = CreateUserResponse.Builder.class) +public final class CreateUserResponse { + private final User user; + + private final Map additionalProperties; + + private CreateUserResponse(User user, Map additionalProperties) { + this.user = user; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("user") + public User getUser() { + return user; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof CreateUserResponse && equalTo((CreateUserResponse) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(CreateUserResponse other) { + return user.equals(other.user); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.user); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static UserStage builder() { + return new Builder(); + } + + public interface UserStage { + _FinalStage user(User user); + + Builder from(CreateUserResponse other); + } + + public interface _FinalStage { + CreateUserResponse build(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements UserStage, _FinalStage { + private User user; + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(CreateUserResponse other) { + user(other.getUser()); + return this; + } + + @java.lang.Override + @JsonSetter("user") + public _FinalStage user(User user) { + this.user = user; + return this; + } + + @java.lang.Override + public CreateUserResponse build() { + return new CreateUserResponse(user, additionalProperties); + } + } +} diff --git a/seed/java-sdk/grpc/src/main/java/com/seed/api/resources/user/types/MetadataValue.java b/seed/java-sdk/grpc/src/main/java/com/seed/api/resources/user/types/MetadataValue.java new file mode 100644 index 00000000000..d326ace1a3c --- /dev/null +++ b/seed/java-sdk/grpc/src/main/java/com/seed/api/resources/user/types/MetadataValue.java @@ -0,0 +1,118 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.resources.user.types; + +import com.fasterxml.jackson.annotation.JsonValue; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.seed.api.core.ObjectMappers; +import java.io.IOException; +import java.util.List; +import java.util.Objects; + +@JsonDeserialize(using = MetadataValue.Deserializer.class) +public final class MetadataValue { + private final Object value; + + private final int type; + + private MetadataValue(Object value, int type) { + this.value = value; + this.type = type; + } + + @JsonValue + public Object get() { + return this.value; + } + + public T visit(Visitor visitor) { + if (this.type == 0) { + return visitor.visit((double) this.value); + } else if (this.type == 1) { + return visitor.visit((String) this.value); + } else if (this.type == 2) { + return visitor.visit((boolean) this.value); + } else if (this.type == 3) { + return visitor.visit((List) this.value); + } + throw new IllegalStateException("Failed to visit value. This should never happen."); + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof MetadataValue && equalTo((MetadataValue) other); + } + + private boolean equalTo(MetadataValue other) { + return value.equals(other.value); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.value); + } + + @java.lang.Override + public String toString() { + return this.value.toString(); + } + + public static MetadataValue of(double value) { + return new MetadataValue(value, 0); + } + + public static MetadataValue of(String value) { + return new MetadataValue(value, 1); + } + + public static MetadataValue of(boolean value) { + return new MetadataValue(value, 2); + } + + public static MetadataValue of(List value) { + return new MetadataValue(value, 3); + } + + public interface Visitor { + T visit(double value); + + T visit(String value); + + T visit(boolean value); + + T visit(List value); + } + + static final class Deserializer extends StdDeserializer { + Deserializer() { + super(MetadataValue.class); + } + + @java.lang.Override + public MetadataValue deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + Object value = p.readValueAs(Object.class); + if (value instanceof Double) { + return of((Double) value); + } + try { + return of(ObjectMappers.JSON_MAPPER.convertValue(value, String.class)); + } catch (IllegalArgumentException e) { + } + if (value instanceof Boolean) { + return of((Boolean) value); + } + try { + return of(ObjectMappers.JSON_MAPPER.convertValue(value, new TypeReference>() {})); + } catch (IllegalArgumentException e) { + } + throw new JsonParseException(p, "Failed to deserialize"); + } + } +} diff --git a/seed/java-sdk/grpc/src/main/java/com/seed/api/resources/user/types/User.java b/seed/java-sdk/grpc/src/main/java/com/seed/api/resources/user/types/User.java new file mode 100644 index 00000000000..c8bce8388b9 --- /dev/null +++ b/seed/java-sdk/grpc/src/main/java/com/seed/api/resources/user/types/User.java @@ -0,0 +1,249 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api.resources.user.types; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.seed.api.core.ObjectMappers; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = User.Builder.class) +public final class User { + private final String id; + + private final String username; + + private final Optional email; + + private final Optional age; + + private final Optional weight; + + private final Optional>> metadata; + + private final Map additionalProperties; + + private User( + String id, + String username, + Optional email, + Optional age, + Optional weight, + Optional>> metadata, + Map additionalProperties) { + this.id = id; + this.username = username; + this.email = email; + this.age = age; + this.weight = weight; + this.metadata = metadata; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("id") + public String getId() { + return id; + } + + @JsonProperty("username") + public String getUsername() { + return username; + } + + @JsonProperty("email") + public Optional getEmail() { + return email; + } + + @JsonProperty("age") + public Optional getAge() { + return age; + } + + @JsonProperty("weight") + public Optional getWeight() { + return weight; + } + + @JsonProperty("metadata") + public Optional>> getMetadata() { + return metadata; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof User && equalTo((User) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(User other) { + return id.equals(other.id) + && username.equals(other.username) + && email.equals(other.email) + && age.equals(other.age) + && weight.equals(other.weight) + && metadata.equals(other.metadata); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.id, this.username, this.email, this.age, this.weight, this.metadata); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static IdStage builder() { + return new Builder(); + } + + public interface IdStage { + UsernameStage id(String id); + + Builder from(User other); + } + + public interface UsernameStage { + _FinalStage username(String username); + } + + public interface _FinalStage { + User build(); + + _FinalStage email(Optional email); + + _FinalStage email(String email); + + _FinalStage age(Optional age); + + _FinalStage age(Integer age); + + _FinalStage weight(Optional weight); + + _FinalStage weight(Double weight); + + _FinalStage metadata(Optional>> metadata); + + _FinalStage metadata(Map> metadata); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder implements IdStage, UsernameStage, _FinalStage { + private String id; + + private String username; + + private Optional>> metadata = Optional.empty(); + + private Optional weight = Optional.empty(); + + private Optional age = Optional.empty(); + + private Optional email = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + @java.lang.Override + public Builder from(User other) { + id(other.getId()); + username(other.getUsername()); + email(other.getEmail()); + age(other.getAge()); + weight(other.getWeight()); + metadata(other.getMetadata()); + return this; + } + + @java.lang.Override + @JsonSetter("id") + public UsernameStage id(String id) { + this.id = id; + return this; + } + + @java.lang.Override + @JsonSetter("username") + public _FinalStage username(String username) { + this.username = username; + return this; + } + + @java.lang.Override + public _FinalStage metadata(Map> metadata) { + this.metadata = Optional.ofNullable(metadata); + return this; + } + + @java.lang.Override + @JsonSetter(value = "metadata", nulls = Nulls.SKIP) + public _FinalStage metadata(Optional>> metadata) { + this.metadata = metadata; + return this; + } + + @java.lang.Override + public _FinalStage weight(Double weight) { + this.weight = Optional.ofNullable(weight); + return this; + } + + @java.lang.Override + @JsonSetter(value = "weight", nulls = Nulls.SKIP) + public _FinalStage weight(Optional weight) { + this.weight = weight; + return this; + } + + @java.lang.Override + public _FinalStage age(Integer age) { + this.age = Optional.ofNullable(age); + return this; + } + + @java.lang.Override + @JsonSetter(value = "age", nulls = Nulls.SKIP) + public _FinalStage age(Optional age) { + this.age = age; + return this; + } + + @java.lang.Override + public _FinalStage email(String email) { + this.email = Optional.ofNullable(email); + return this; + } + + @java.lang.Override + @JsonSetter(value = "email", nulls = Nulls.SKIP) + public _FinalStage email(Optional email) { + this.email = email; + return this; + } + + @java.lang.Override + public User build() { + return new User(id, username, email, age, weight, metadata, additionalProperties); + } + } +} diff --git a/seed/java-sdk/grpc/src/test/java/com/seed/api/TestClient.java b/seed/java-sdk/grpc/src/test/java/com/seed/api/TestClient.java new file mode 100644 index 00000000000..1686cfd803c --- /dev/null +++ b/seed/java-sdk/grpc/src/test/java/com/seed/api/TestClient.java @@ -0,0 +1,11 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.seed.api; + +public final class TestClient { + public void test() { + // Add tests here and mark this file in .fernignore + assert true; + } +} diff --git a/seed/java-spring/grpc-proto/.mock/fern.config.json b/seed/java-spring/grpc-proto/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/java-spring/grpc-proto/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/java-spring/grpc-proto/.mock/generators.yml b/seed/java-spring/grpc-proto/.mock/generators.yml new file mode 100644 index 00000000000..902c4f50168 --- /dev/null +++ b/seed/java-spring/grpc-proto/.mock/generators.yml @@ -0,0 +1,5 @@ +api: + - proto: + root: proto + target: proto/user/v1/user.proto + local-generation: true \ No newline at end of file diff --git a/seed/java-spring/grpc-proto/.mock/proto/google/api/annotations.proto b/seed/java-spring/grpc-proto/.mock/proto/google/api/annotations.proto new file mode 100644 index 00000000000..8ff42098404 --- /dev/null +++ b/seed/java-spring/grpc-proto/.mock/proto/google/api/annotations.proto @@ -0,0 +1,31 @@ +// Copyright 2015 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/api/http.proto"; +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "AnnotationsProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // See `HttpRule`. + HttpRule http = 72295728; +} \ No newline at end of file diff --git a/seed/java-spring/grpc-proto/.mock/proto/google/api/field_behavior.proto b/seed/java-spring/grpc-proto/.mock/proto/google/api/field_behavior.proto new file mode 100644 index 00000000000..128799c558d --- /dev/null +++ b/seed/java-spring/grpc-proto/.mock/proto/google/api/field_behavior.proto @@ -0,0 +1,104 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "FieldBehaviorProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.FieldOptions { + // A designation of a specific field behavior (required, output only, etc.) + // in protobuf messages. + // + // Examples: + // + // string name = 1 [(google.api.field_behavior) = REQUIRED]; + // State state = 1 [(google.api.field_behavior) = OUTPUT_ONLY]; + // google.protobuf.Duration ttl = 1 + // [(google.api.field_behavior) = INPUT_ONLY]; + // google.protobuf.Timestamp expire_time = 1 + // [(google.api.field_behavior) = OUTPUT_ONLY, + // (google.api.field_behavior) = IMMUTABLE]; + repeated google.api.FieldBehavior field_behavior = 1052; +} + +// An indicator of the behavior of a given field (for example, that a field +// is required in requests, or given as output but ignored as input). +// This **does not** change the behavior in protocol buffers itself; it only +// denotes the behavior and may affect how API tooling handles the field. +// +// Note: This enum **may** receive new values in the future. +enum FieldBehavior { + // Conventional default for enums. Do not use this. + FIELD_BEHAVIOR_UNSPECIFIED = 0; + + // Specifically denotes a field as optional. + // While all fields in protocol buffers are optional, this may be specified + // for emphasis if appropriate. + OPTIONAL = 1; + + // Denotes a field as required. + // This indicates that the field **must** be provided as part of the request, + // and failure to do so will cause an error (usually `INVALID_ARGUMENT`). + REQUIRED = 2; + + // Denotes a field as output only. + // This indicates that the field is provided in responses, but including the + // field in a request does nothing (the server *must* ignore it and + // *must not* throw an error as a result of the field's presence). + OUTPUT_ONLY = 3; + + // Denotes a field as input only. + // This indicates that the field is provided in requests, and the + // corresponding field is not included in output. + INPUT_ONLY = 4; + + // Denotes a field as immutable. + // This indicates that the field may be set once in a request to create a + // resource, but may not be changed thereafter. + IMMUTABLE = 5; + + // Denotes that a (repeated) field is an unordered list. + // This indicates that the service may provide the elements of the list + // in any arbitrary order, rather than the order the user originally + // provided. Additionally, the list's order may or may not be stable. + UNORDERED_LIST = 6; + + // Denotes that this field returns a non-empty default value if not set. + // This indicates that if the user provides the empty value in a request, + // a non-empty value will be returned. The user will not be aware of what + // non-empty value to expect. + NON_EMPTY_DEFAULT = 7; + + // Denotes that the field in a resource (a message annotated with + // google.api.resource) is used in the resource name to uniquely identify the + // resource. For AIP-compliant APIs, this should only be applied to the + // `name` field on the resource. + // + // This behavior should not be applied to references to other resources within + // the message. + // + // The identifier field of resources often have different field behavior + // depending on the request it is embedded in (e.g. for Create methods name + // is optional and unused, while for Update methods it is required). Instead + // of method-specific annotations, only `IDENTIFIER` is required. + IDENTIFIER = 8; +} \ No newline at end of file diff --git a/seed/java-spring/grpc-proto/.mock/proto/google/api/http.proto b/seed/java-spring/grpc-proto/.mock/proto/google/api/http.proto new file mode 100644 index 00000000000..c8392381eb9 --- /dev/null +++ b/seed/java-spring/grpc-proto/.mock/proto/google/api/http.proto @@ -0,0 +1,379 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "HttpProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Defines the HTTP configuration for an API service. It contains a list of +// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method +// to one or more HTTP REST API methods. +message Http { + // A list of HTTP configuration rules that apply to individual API methods. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated HttpRule rules = 1; + + // When set to true, URL path parameters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // + // The default behavior is to not decode RFC 6570 reserved characters in multi + // segment matches. + bool fully_decode_reserved_expansion = 2; +} + +// # gRPC Transcoding +// +// gRPC Transcoding is a feature for mapping between a gRPC method and one or +// more HTTP REST endpoints. It allows developers to build a single API service +// that supports both gRPC APIs and REST APIs. Many systems, including [Google +// APIs](https://github.com/googleapis/googleapis), +// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC +// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), +// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature +// and use it for large scale production services. +// +// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies +// how different portions of the gRPC request message are mapped to the URL +// path, URL query parameters, and HTTP request body. It also controls how the +// gRPC response message is mapped to the HTTP response body. `HttpRule` is +// typically specified as an `google.api.http` annotation on the gRPC method. +// +// Each mapping specifies a URL path template and an HTTP method. The path +// template may refer to one or more fields in the gRPC request message, as long +// as each field is a non-repeated field with a primitive (non-message) type. +// The path template controls how fields of the request message are mapped to +// the URL path. +// +// Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/{name=messages/*}" +// }; +// } +// } +// message GetMessageRequest { +// string name = 1; // Mapped to URL path. +// } +// message Message { +// string text = 1; // The resource content. +// } +// +// This enables an HTTP REST to gRPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` +// +// Any fields in the request message which are not bound by the path template +// automatically become HTTP query parameters if there is no HTTP request body. +// For example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get:"/v1/messages/{message_id}" +// }; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // Mapped to URL path. +// int64 revision = 2; // Mapped to URL query parameter `revision`. +// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. +// } +// +// This enables a HTTP JSON to RPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | +// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: +// "foo"))` +// +// Note that fields which are mapped to URL query parameters must have a +// primitive type or a repeated primitive type or a non-repeated message type. +// In the case of a repeated type, the parameter can be repeated in the URL +// as `...?param=A¶m=B`. In the case of a message type, each field of the +// message is mapped to a separate parameter, such as +// `...?foo.a=A&foo.b=B&foo.c=C`. +// +// For HTTP methods that allow a request body, the `body` field +// specifies the mapping. Consider a REST update method on the +// message resource collection: +// +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } +// +// The following HTTP JSON to RPC mapping is enabled, where the +// representation of the JSON in the request body is determined by +// protos JSON encoding: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" message { text: "Hi!" })` +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. This enables the following alternative definition of +// the update method: +// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } +// +// +// The following HTTP JSON to RPC mapping is enabled: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" text: "Hi!")` +// +// Note that when using `*` in the body mapping, it is not possible to +// have HTTP parameters, as all fields not bound by the path end in +// the body. This makes this option more rarely used in practice when +// defining REST APIs. The common usage of `*` is in custom methods +// which don't use the URL at all for transferring data. +// +// It is possible to define multiple HTTP methods for one RPC by using +// the `additional_bindings` option. Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } +// +// This enables the following two alternative HTTP JSON to RPC mappings: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` +// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: +// "123456")` +// +// ## Rules for HTTP mapping +// +// 1. Leaf request fields (recursive expansion nested messages in the request +// message) are classified into three categories: +// - Fields referred by the path template. They are passed via the URL path. +// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They +// are passed via the HTTP +// request body. +// - All other fields are passed via the URL query parameters, and the +// parameter name is the field path in the request message. A repeated +// field can be represented as multiple query parameters under the same +// name. +// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL +// query parameter, all fields +// are passed via URL path and HTTP request body. +// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP +// request body, all +// fields are passed via URL path and URL query parameters. +// +// ### Path template syntax +// +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +// +// The syntax `*` matches a single URL path segment. The syntax `**` matches +// zero or more URL path segments, which must be the last part of the URL path +// except the `Verb`. +// +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable +// matches a single path segment, its template may be omitted, e.g. `{var}` +// is equivalent to `{var=*}`. +// +// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` +// contains any reserved character, such characters should be percent-encoded +// before the matching. +// +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path on the client +// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The +// server side does the reverse decoding. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{var}`. +// +// If a variable contains multiple path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path on the +// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. +// The server side does the reverse decoding, except "%2F" and "%2f" are left +// unchanged. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{+var}`. +// +// ## Using gRPC API Service Configuration +// +// gRPC API Service Configuration (service config) is a configuration language +// for configuring a gRPC service to become a user-facing product. The +// service config is simply the YAML representation of the `google.api.Service` +// proto message. +// +// As an alternative to annotating your proto file, you can configure gRPC +// transcoding in your service config YAML files. You do this by specifying a +// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same +// effect as the proto annotation. This can be particularly useful if you +// have a proto that is reused in multiple services. Note that any transcoding +// specified in the service config will override any matching transcoding +// configuration in the proto. +// +// Example: +// +// http: +// rules: +// # Selects a gRPC method and applies HttpRule to it. +// - selector: example.v1.Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} +// +// ## Special notes +// +// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the +// proto to JSON conversion must follow the [proto3 +// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). +// +// While the single segment variable follows the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String +// Expansion, the multi segment variable **does not** follow RFC 6570 Section +// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding +// for multi segment variables. +// +// The path variables **must not** refer to any repeated or mapped field, +// because client libraries are not capable of handling such variable expansion. +// +// The path variables **must not** capture the leading "/" character. The reason +// is that the most common use case "{var}" does not capture the leading "/" +// character. For consistency, all path variables must share the same behavior. +// +// Repeated message fields must not be mapped to URL query parameters, because +// no client library can support such complicated mapping. +// +// If an API needs to use a JSON array for request or response body, it can map +// the request or response body to a repeated field. However, some gRPC +// Transcoding implementations may not support this feature. +message HttpRule { + // Selects a method to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax + // details. + string selector = 1; + + // Determines the URL pattern is matched by this rules. This pattern can be + // used with any of the {get|put|post|delete|patch} methods. A custom method + // can be defined using the 'custom' field. + oneof pattern { + // Maps to HTTP GET. Used for listing and getting information about + // resources. + string get = 2; + + // Maps to HTTP PUT. Used for replacing a resource. + string put = 3; + + // Maps to HTTP POST. Used for creating a resource or performing an action. + string post = 4; + + // Maps to HTTP DELETE. Used for deleting a resource. + string delete = 5; + + // Maps to HTTP PATCH. Used for updating a resource. + string patch = 6; + + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. + CustomHttpPattern custom = 8; + } + + // The name of the request field whose value is mapped to the HTTP request + // body, or `*` for mapping all request fields not captured by the path + // pattern to the HTTP body, or omitted for not having any HTTP request body. + // + // NOTE: the referred field must be present at the top-level of the request + // message type. + string body = 7; + + // Optional. The name of the response field whose value is mapped to the HTTP + // response body. When omitted, the entire response message will be used + // as the HTTP response body. + // + // NOTE: The referred field must be present at the top-level of the response + // message type. + string response_body = 12; + + // Additional HTTP bindings for the selector. Nested bindings must + // not contain an `additional_bindings` field themselves (that is, + // the nesting may only be one level deep). + repeated HttpRule additional_bindings = 11; +} + +// A custom pattern is used for defining custom HTTP verb. +message CustomHttpPattern { + // The name of this custom HTTP verb. + string kind = 1; + + // The path matched by this custom verb. + string path = 2; +} \ No newline at end of file diff --git a/seed/java-spring/grpc-proto/.mock/proto/user/v1/user.proto b/seed/java-spring/grpc-proto/.mock/proto/user/v1/user.proto new file mode 100644 index 00000000000..a3ad0195bd4 --- /dev/null +++ b/seed/java-spring/grpc-proto/.mock/proto/user/v1/user.proto @@ -0,0 +1,39 @@ +syntax = "proto3"; + +package user.v1; + +import "google/api/annotations.proto"; +import "google/api/field_behavior.proto"; +import "google/protobuf/struct.proto"; + +option go_package = "user/v1"; +option csharp_namespace = "User.V1"; + +message UserModel { + string username = 1; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateRequest { + string username = 1; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateResponse { + UserModel user = 1; +} + +service User { + rpc Create(CreateRequest) returns (CreateResponse) { + option (google.api.http) = { + post: "/users" + body: "*" + }; + } +} \ No newline at end of file diff --git a/seed/java-spring/grpc-proto/core/APIException.java b/seed/java-spring/grpc-proto/core/APIException.java new file mode 100644 index 00000000000..27289cf9b2e --- /dev/null +++ b/seed/java-spring/grpc-proto/core/APIException.java @@ -0,0 +1,10 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package core; + +import java.lang.Exception; + +public class APIException extends Exception { +} diff --git a/seed/java-spring/grpc-proto/core/DateTimeDeserializer.java b/seed/java-spring/grpc-proto/core/DateTimeDeserializer.java new file mode 100644 index 00000000000..3d3174aec00 --- /dev/null +++ b/seed/java-spring/grpc-proto/core/DateTimeDeserializer.java @@ -0,0 +1,56 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package core; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.module.SimpleModule; +import java.io.IOException; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalQueries; + +/** + * Custom deserializer that handles converting ISO8601 dates into {@link OffsetDateTime} objects. + */ +class DateTimeDeserializer extends JsonDeserializer { + private static final SimpleModule MODULE; + + static { + MODULE = new SimpleModule().addDeserializer(OffsetDateTime.class, new DateTimeDeserializer()); + } + + /** + * Gets a module wrapping this deserializer as an adapter for the Jackson ObjectMapper. + * + * @return A {@link SimpleModule} to be plugged onto Jackson ObjectMapper. + */ + public static SimpleModule getModule() { + return MODULE; + } + + @Override + public OffsetDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException { + JsonToken token = parser.currentToken(); + if (token == JsonToken.VALUE_NUMBER_INT) { + return OffsetDateTime.ofInstant(Instant.ofEpochSecond(parser.getValueAsLong()), ZoneOffset.UTC); + } else { + TemporalAccessor temporal = DateTimeFormatter.ISO_DATE_TIME.parseBest( + parser.getValueAsString(), OffsetDateTime::from, LocalDateTime::from); + + if (temporal.query(TemporalQueries.offset()) == null) { + return LocalDateTime.from(temporal).atOffset(ZoneOffset.UTC); + } else { + return OffsetDateTime.from(temporal); + } + } + } +} \ No newline at end of file diff --git a/seed/java-spring/grpc-proto/core/ObjectMappers.java b/seed/java-spring/grpc-proto/core/ObjectMappers.java new file mode 100644 index 00000000000..e02822614a8 --- /dev/null +++ b/seed/java-spring/grpc-proto/core/ObjectMappers.java @@ -0,0 +1,41 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package core; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import java.io.IOException; +import java.lang.Integer; +import java.lang.Object; +import java.lang.String; + +public final class ObjectMappers { + public static final ObjectMapper JSON_MAPPER = JsonMapper.builder() + .addModule(new Jdk8Module()) + .addModule(new JavaTimeModule()) + .addModule(DateTimeDeserializer.getModule()) + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .build(); + + private ObjectMappers() { + } + + public static String stringify(Object o) { + try { + return JSON_MAPPER.setSerializationInclusion(JsonInclude.Include.ALWAYS) + .writerWithDefaultPrettyPrinter() + .writeValueAsString(o); + } + catch (IOException e) { + return o.getClass().getName() + "@" + Integer.toHexString(o.hashCode()); + } + } + } diff --git a/seed/java-spring/grpc-proto/resources/user/UserService.java b/seed/java-spring/grpc-proto/resources/user/UserService.java new file mode 100644 index 00000000000..89a365af49f --- /dev/null +++ b/seed/java-spring/grpc-proto/resources/user/UserService.java @@ -0,0 +1,23 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package resources.user; + +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import resources.user.requests.CreateRequest; +import types.CreateResponse; + +@RequestMapping( + path = "/" +) +public interface UserService { + @PostMapping( + value = "/users", + produces = "application/json", + consumes = "application/json" + ) + CreateResponse create(@RequestBody CreateRequest body); +} diff --git a/seed/java-spring/grpc-proto/resources/user/requests/CreateRequest.java b/seed/java-spring/grpc-proto/resources/user/requests/CreateRequest.java new file mode 100644 index 00000000000..5b797fc09d6 --- /dev/null +++ b/seed/java-spring/grpc-proto/resources/user/requests/CreateRequest.java @@ -0,0 +1,195 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package resources.user.requests; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import core.ObjectMappers; +import java.lang.Double; +import java.lang.Integer; +import java.lang.Object; +import java.lang.String; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize( + builder = CreateRequest.Builder.class +) +public final class CreateRequest { + private final Optional username; + + private final Optional email; + + private final Optional age; + + private final Optional weight; + + private final Optional> metadata; + + private CreateRequest(Optional username, Optional email, Optional age, + Optional weight, Optional> metadata) { + this.username = username; + this.email = email; + this.age = age; + this.weight = weight; + this.metadata = metadata; + } + + @JsonProperty("username") + public Optional getUsername() { + return username; + } + + @JsonProperty("email") + public Optional getEmail() { + return email; + } + + @JsonProperty("age") + public Optional getAge() { + return age; + } + + @JsonProperty("weight") + public Optional getWeight() { + return weight; + } + + @JsonProperty("metadata") + public Optional> getMetadata() { + return metadata; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof CreateRequest && equalTo((CreateRequest) other); + } + + private boolean equalTo(CreateRequest other) { + return username.equals(other.username) && email.equals(other.email) && age.equals(other.age) && weight.equals(other.weight) && metadata.equals(other.metadata); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.username, this.email, this.age, this.weight, this.metadata); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties( + ignoreUnknown = true + ) + public static final class Builder { + private Optional username = Optional.empty(); + + private Optional email = Optional.empty(); + + private Optional age = Optional.empty(); + + private Optional weight = Optional.empty(); + + private Optional> metadata = Optional.empty(); + + private Builder() { + } + + public Builder from(CreateRequest other) { + username(other.getUsername()); + email(other.getEmail()); + age(other.getAge()); + weight(other.getWeight()); + metadata(other.getMetadata()); + return this; + } + + @JsonSetter( + value = "username", + nulls = Nulls.SKIP + ) + public Builder username(Optional username) { + this.username = username; + return this; + } + + public Builder username(String username) { + this.username = Optional.ofNullable(username); + return this; + } + + @JsonSetter( + value = "email", + nulls = Nulls.SKIP + ) + public Builder email(Optional email) { + this.email = email; + return this; + } + + public Builder email(String email) { + this.email = Optional.ofNullable(email); + return this; + } + + @JsonSetter( + value = "age", + nulls = Nulls.SKIP + ) + public Builder age(Optional age) { + this.age = age; + return this; + } + + public Builder age(Integer age) { + this.age = Optional.ofNullable(age); + return this; + } + + @JsonSetter( + value = "weight", + nulls = Nulls.SKIP + ) + public Builder weight(Optional weight) { + this.weight = weight; + return this; + } + + public Builder weight(Double weight) { + this.weight = Optional.ofNullable(weight); + return this; + } + + @JsonSetter( + value = "metadata", + nulls = Nulls.SKIP + ) + public Builder metadata(Optional> metadata) { + this.metadata = metadata; + return this; + } + + public Builder metadata(Map metadata) { + this.metadata = Optional.ofNullable(metadata); + return this; + } + + public CreateRequest build() { + return new CreateRequest(username, email, age, weight, metadata); + } + } +} diff --git a/seed/java-spring/grpc-proto/snippet-templates.json b/seed/java-spring/grpc-proto/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/java-spring/grpc-proto/snippet.json b/seed/java-spring/grpc-proto/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/java-spring/grpc-proto/types/CreateResponse.java b/seed/java-spring/grpc-proto/types/CreateResponse.java new file mode 100644 index 00000000000..d3296fddebc --- /dev/null +++ b/seed/java-spring/grpc-proto/types/CreateResponse.java @@ -0,0 +1,91 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package types; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import core.ObjectMappers; +import java.lang.Object; +import java.lang.String; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize( + builder = CreateResponse.Builder.class +) +public final class CreateResponse { + private final Optional user; + + private CreateResponse(Optional user) { + this.user = user; + } + + @JsonProperty("user") + public Optional getUser() { + return user; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof CreateResponse && equalTo((CreateResponse) other); + } + + private boolean equalTo(CreateResponse other) { + return user.equals(other.user); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.user); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties( + ignoreUnknown = true + ) + public static final class Builder { + private Optional user = Optional.empty(); + + private Builder() { + } + + public Builder from(CreateResponse other) { + user(other.getUser()); + return this; + } + + @JsonSetter( + value = "user", + nulls = Nulls.SKIP + ) + public Builder user(Optional user) { + this.user = user; + return this; + } + + public Builder user(UserModel user) { + this.user = Optional.ofNullable(user); + return this; + } + + public CreateResponse build() { + return new CreateResponse(user); + } + } +} diff --git a/seed/java-spring/grpc-proto/types/UserModel.java b/seed/java-spring/grpc-proto/types/UserModel.java new file mode 100644 index 00000000000..9524d41ecfa --- /dev/null +++ b/seed/java-spring/grpc-proto/types/UserModel.java @@ -0,0 +1,195 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package types; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import core.ObjectMappers; +import java.lang.Double; +import java.lang.Integer; +import java.lang.Object; +import java.lang.String; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize( + builder = UserModel.Builder.class +) +public final class UserModel { + private final Optional username; + + private final Optional email; + + private final Optional age; + + private final Optional weight; + + private final Optional> metadata; + + private UserModel(Optional username, Optional email, Optional age, + Optional weight, Optional> metadata) { + this.username = username; + this.email = email; + this.age = age; + this.weight = weight; + this.metadata = metadata; + } + + @JsonProperty("username") + public Optional getUsername() { + return username; + } + + @JsonProperty("email") + public Optional getEmail() { + return email; + } + + @JsonProperty("age") + public Optional getAge() { + return age; + } + + @JsonProperty("weight") + public Optional getWeight() { + return weight; + } + + @JsonProperty("metadata") + public Optional> getMetadata() { + return metadata; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof UserModel && equalTo((UserModel) other); + } + + private boolean equalTo(UserModel other) { + return username.equals(other.username) && email.equals(other.email) && age.equals(other.age) && weight.equals(other.weight) && metadata.equals(other.metadata); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.username, this.email, this.age, this.weight, this.metadata); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static Builder builder() { + return new Builder(); + } + + @JsonIgnoreProperties( + ignoreUnknown = true + ) + public static final class Builder { + private Optional username = Optional.empty(); + + private Optional email = Optional.empty(); + + private Optional age = Optional.empty(); + + private Optional weight = Optional.empty(); + + private Optional> metadata = Optional.empty(); + + private Builder() { + } + + public Builder from(UserModel other) { + username(other.getUsername()); + email(other.getEmail()); + age(other.getAge()); + weight(other.getWeight()); + metadata(other.getMetadata()); + return this; + } + + @JsonSetter( + value = "username", + nulls = Nulls.SKIP + ) + public Builder username(Optional username) { + this.username = username; + return this; + } + + public Builder username(String username) { + this.username = Optional.ofNullable(username); + return this; + } + + @JsonSetter( + value = "email", + nulls = Nulls.SKIP + ) + public Builder email(Optional email) { + this.email = email; + return this; + } + + public Builder email(String email) { + this.email = Optional.ofNullable(email); + return this; + } + + @JsonSetter( + value = "age", + nulls = Nulls.SKIP + ) + public Builder age(Optional age) { + this.age = age; + return this; + } + + public Builder age(Integer age) { + this.age = Optional.ofNullable(age); + return this; + } + + @JsonSetter( + value = "weight", + nulls = Nulls.SKIP + ) + public Builder weight(Optional weight) { + this.weight = weight; + return this; + } + + public Builder weight(Double weight) { + this.weight = Optional.ofNullable(weight); + return this; + } + + @JsonSetter( + value = "metadata", + nulls = Nulls.SKIP + ) + public Builder metadata(Optional> metadata) { + this.metadata = metadata; + return this; + } + + public Builder metadata(Map metadata) { + this.metadata = Optional.ofNullable(metadata); + return this; + } + + public UserModel build() { + return new UserModel(username, email, age, weight, metadata); + } + } +} diff --git a/seed/java-spring/grpc/.mock/definition/api.yml b/seed/java-spring/grpc/.mock/definition/api.yml new file mode 100644 index 00000000000..15a9f823724 --- /dev/null +++ b/seed/java-spring/grpc/.mock/definition/api.yml @@ -0,0 +1,4 @@ +name: api + +error-discrimination: + strategy: status-code \ No newline at end of file diff --git a/seed/java-spring/grpc/.mock/definition/user.yml b/seed/java-spring/grpc/.mock/definition/user.yml new file mode 100644 index 00000000000..80223300afc --- /dev/null +++ b/seed/java-spring/grpc/.mock/definition/user.yml @@ -0,0 +1,61 @@ +types: + Metadata: + type: map> + encoding: + proto: + type: google.protobuf.Struct + + MetadataValue: + discriminated: false + union: + - double + - string + - boolean + - list + encoding: + proto: + type: google.protobuf.Value + + User: + properties: + id: string + username: string + email: optional + age: optional + weight: optional + metadata: optional + + CreateUserResponse: + properties: + user: User + +service: + auth: false + base-path: / + transport: + grpc: + service-name: UserService + endpoints: + createUser: + method: POST + path: /users + request: + name: CreateUserRequest + body: + properties: + username: string + email: optional + age: optional + weight: optional + response: CreateUserResponse + + getUser: + method: GET + path: /users + request: + name: GetUserRequest + query-parameters: + username: optional + age: optional + weight: optional + response: User diff --git a/seed/java-spring/grpc/.mock/fern.config.json b/seed/java-spring/grpc/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/java-spring/grpc/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/java-spring/grpc/.mock/generators.yml b/seed/java-spring/grpc/.mock/generators.yml new file mode 100644 index 00000000000..f62dfba5843 --- /dev/null +++ b/seed/java-spring/grpc/.mock/generators.yml @@ -0,0 +1,4 @@ +api: + - proto: + root: proto + target: proto/user/v1/user.proto \ No newline at end of file diff --git a/seed/java-spring/grpc/.mock/proto/google/api/annotations.proto b/seed/java-spring/grpc/.mock/proto/google/api/annotations.proto new file mode 100644 index 00000000000..efdab3db6ca --- /dev/null +++ b/seed/java-spring/grpc/.mock/proto/google/api/annotations.proto @@ -0,0 +1,31 @@ +// Copyright 2015 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/api/http.proto"; +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "AnnotationsProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // See `HttpRule`. + HttpRule http = 72295728; +} diff --git a/seed/java-spring/grpc/.mock/proto/google/api/field_behavior.proto b/seed/java-spring/grpc/.mock/proto/google/api/field_behavior.proto new file mode 100644 index 00000000000..344cb0b1fc2 --- /dev/null +++ b/seed/java-spring/grpc/.mock/proto/google/api/field_behavior.proto @@ -0,0 +1,104 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "FieldBehaviorProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.FieldOptions { + // A designation of a specific field behavior (required, output only, etc.) + // in protobuf messages. + // + // Examples: + // + // string name = 1 [(google.api.field_behavior) = REQUIRED]; + // State state = 1 [(google.api.field_behavior) = OUTPUT_ONLY]; + // google.protobuf.Duration ttl = 1 + // [(google.api.field_behavior) = INPUT_ONLY]; + // google.protobuf.Timestamp expire_time = 1 + // [(google.api.field_behavior) = OUTPUT_ONLY, + // (google.api.field_behavior) = IMMUTABLE]; + repeated google.api.FieldBehavior field_behavior = 1052; +} + +// An indicator of the behavior of a given field (for example, that a field +// is required in requests, or given as output but ignored as input). +// This **does not** change the behavior in protocol buffers itself; it only +// denotes the behavior and may affect how API tooling handles the field. +// +// Note: This enum **may** receive new values in the future. +enum FieldBehavior { + // Conventional default for enums. Do not use this. + FIELD_BEHAVIOR_UNSPECIFIED = 0; + + // Specifically denotes a field as optional. + // While all fields in protocol buffers are optional, this may be specified + // for emphasis if appropriate. + OPTIONAL = 1; + + // Denotes a field as required. + // This indicates that the field **must** be provided as part of the request, + // and failure to do so will cause an error (usually `INVALID_ARGUMENT`). + REQUIRED = 2; + + // Denotes a field as output only. + // This indicates that the field is provided in responses, but including the + // field in a request does nothing (the server *must* ignore it and + // *must not* throw an error as a result of the field's presence). + OUTPUT_ONLY = 3; + + // Denotes a field as input only. + // This indicates that the field is provided in requests, and the + // corresponding field is not included in output. + INPUT_ONLY = 4; + + // Denotes a field as immutable. + // This indicates that the field may be set once in a request to create a + // resource, but may not be changed thereafter. + IMMUTABLE = 5; + + // Denotes that a (repeated) field is an unordered list. + // This indicates that the service may provide the elements of the list + // in any arbitrary order, rather than the order the user originally + // provided. Additionally, the list's order may or may not be stable. + UNORDERED_LIST = 6; + + // Denotes that this field returns a non-empty default value if not set. + // This indicates that if the user provides the empty value in a request, + // a non-empty value will be returned. The user will not be aware of what + // non-empty value to expect. + NON_EMPTY_DEFAULT = 7; + + // Denotes that the field in a resource (a message annotated with + // google.api.resource) is used in the resource name to uniquely identify the + // resource. For AIP-compliant APIs, this should only be applied to the + // `name` field on the resource. + // + // This behavior should not be applied to references to other resources within + // the message. + // + // The identifier field of resources often have different field behavior + // depending on the request it is embedded in (e.g. for Create methods name + // is optional and unused, while for Update methods it is required). Instead + // of method-specific annotations, only `IDENTIFIER` is required. + IDENTIFIER = 8; +} diff --git a/seed/java-spring/grpc/.mock/proto/google/api/http.proto b/seed/java-spring/grpc/.mock/proto/google/api/http.proto new file mode 100644 index 00000000000..31d867a27d5 --- /dev/null +++ b/seed/java-spring/grpc/.mock/proto/google/api/http.proto @@ -0,0 +1,379 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "HttpProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Defines the HTTP configuration for an API service. It contains a list of +// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method +// to one or more HTTP REST API methods. +message Http { + // A list of HTTP configuration rules that apply to individual API methods. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated HttpRule rules = 1; + + // When set to true, URL path parameters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // + // The default behavior is to not decode RFC 6570 reserved characters in multi + // segment matches. + bool fully_decode_reserved_expansion = 2; +} + +// # gRPC Transcoding +// +// gRPC Transcoding is a feature for mapping between a gRPC method and one or +// more HTTP REST endpoints. It allows developers to build a single API service +// that supports both gRPC APIs and REST APIs. Many systems, including [Google +// APIs](https://github.com/googleapis/googleapis), +// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC +// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), +// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature +// and use it for large scale production services. +// +// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies +// how different portions of the gRPC request message are mapped to the URL +// path, URL query parameters, and HTTP request body. It also controls how the +// gRPC response message is mapped to the HTTP response body. `HttpRule` is +// typically specified as an `google.api.http` annotation on the gRPC method. +// +// Each mapping specifies a URL path template and an HTTP method. The path +// template may refer to one or more fields in the gRPC request message, as long +// as each field is a non-repeated field with a primitive (non-message) type. +// The path template controls how fields of the request message are mapped to +// the URL path. +// +// Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/{name=messages/*}" +// }; +// } +// } +// message GetMessageRequest { +// string name = 1; // Mapped to URL path. +// } +// message Message { +// string text = 1; // The resource content. +// } +// +// This enables an HTTP REST to gRPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` +// +// Any fields in the request message which are not bound by the path template +// automatically become HTTP query parameters if there is no HTTP request body. +// For example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get:"/v1/messages/{message_id}" +// }; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // Mapped to URL path. +// int64 revision = 2; // Mapped to URL query parameter `revision`. +// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. +// } +// +// This enables a HTTP JSON to RPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | +// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: +// "foo"))` +// +// Note that fields which are mapped to URL query parameters must have a +// primitive type or a repeated primitive type or a non-repeated message type. +// In the case of a repeated type, the parameter can be repeated in the URL +// as `...?param=A¶m=B`. In the case of a message type, each field of the +// message is mapped to a separate parameter, such as +// `...?foo.a=A&foo.b=B&foo.c=C`. +// +// For HTTP methods that allow a request body, the `body` field +// specifies the mapping. Consider a REST update method on the +// message resource collection: +// +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } +// +// The following HTTP JSON to RPC mapping is enabled, where the +// representation of the JSON in the request body is determined by +// protos JSON encoding: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" message { text: "Hi!" })` +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. This enables the following alternative definition of +// the update method: +// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } +// +// +// The following HTTP JSON to RPC mapping is enabled: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" text: "Hi!")` +// +// Note that when using `*` in the body mapping, it is not possible to +// have HTTP parameters, as all fields not bound by the path end in +// the body. This makes this option more rarely used in practice when +// defining REST APIs. The common usage of `*` is in custom methods +// which don't use the URL at all for transferring data. +// +// It is possible to define multiple HTTP methods for one RPC by using +// the `additional_bindings` option. Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } +// +// This enables the following two alternative HTTP JSON to RPC mappings: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` +// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: +// "123456")` +// +// ## Rules for HTTP mapping +// +// 1. Leaf request fields (recursive expansion nested messages in the request +// message) are classified into three categories: +// - Fields referred by the path template. They are passed via the URL path. +// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They +// are passed via the HTTP +// request body. +// - All other fields are passed via the URL query parameters, and the +// parameter name is the field path in the request message. A repeated +// field can be represented as multiple query parameters under the same +// name. +// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL +// query parameter, all fields +// are passed via URL path and HTTP request body. +// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP +// request body, all +// fields are passed via URL path and URL query parameters. +// +// ### Path template syntax +// +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +// +// The syntax `*` matches a single URL path segment. The syntax `**` matches +// zero or more URL path segments, which must be the last part of the URL path +// except the `Verb`. +// +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable +// matches a single path segment, its template may be omitted, e.g. `{var}` +// is equivalent to `{var=*}`. +// +// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` +// contains any reserved character, such characters should be percent-encoded +// before the matching. +// +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path on the client +// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The +// server side does the reverse decoding. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{var}`. +// +// If a variable contains multiple path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path on the +// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. +// The server side does the reverse decoding, except "%2F" and "%2f" are left +// unchanged. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{+var}`. +// +// ## Using gRPC API Service Configuration +// +// gRPC API Service Configuration (service config) is a configuration language +// for configuring a gRPC service to become a user-facing product. The +// service config is simply the YAML representation of the `google.api.Service` +// proto message. +// +// As an alternative to annotating your proto file, you can configure gRPC +// transcoding in your service config YAML files. You do this by specifying a +// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same +// effect as the proto annotation. This can be particularly useful if you +// have a proto that is reused in multiple services. Note that any transcoding +// specified in the service config will override any matching transcoding +// configuration in the proto. +// +// Example: +// +// http: +// rules: +// # Selects a gRPC method and applies HttpRule to it. +// - selector: example.v1.Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} +// +// ## Special notes +// +// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the +// proto to JSON conversion must follow the [proto3 +// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). +// +// While the single segment variable follows the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String +// Expansion, the multi segment variable **does not** follow RFC 6570 Section +// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding +// for multi segment variables. +// +// The path variables **must not** refer to any repeated or mapped field, +// because client libraries are not capable of handling such variable expansion. +// +// The path variables **must not** capture the leading "/" character. The reason +// is that the most common use case "{var}" does not capture the leading "/" +// character. For consistency, all path variables must share the same behavior. +// +// Repeated message fields must not be mapped to URL query parameters, because +// no client library can support such complicated mapping. +// +// If an API needs to use a JSON array for request or response body, it can map +// the request or response body to a repeated field. However, some gRPC +// Transcoding implementations may not support this feature. +message HttpRule { + // Selects a method to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax + // details. + string selector = 1; + + // Determines the URL pattern is matched by this rules. This pattern can be + // used with any of the {get|put|post|delete|patch} methods. A custom method + // can be defined using the 'custom' field. + oneof pattern { + // Maps to HTTP GET. Used for listing and getting information about + // resources. + string get = 2; + + // Maps to HTTP PUT. Used for replacing a resource. + string put = 3; + + // Maps to HTTP POST. Used for creating a resource or performing an action. + string post = 4; + + // Maps to HTTP DELETE. Used for deleting a resource. + string delete = 5; + + // Maps to HTTP PATCH. Used for updating a resource. + string patch = 6; + + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. + CustomHttpPattern custom = 8; + } + + // The name of the request field whose value is mapped to the HTTP request + // body, or `*` for mapping all request fields not captured by the path + // pattern to the HTTP body, or omitted for not having any HTTP request body. + // + // NOTE: the referred field must be present at the top-level of the request + // message type. + string body = 7; + + // Optional. The name of the response field whose value is mapped to the HTTP + // response body. When omitted, the entire response message will be used + // as the HTTP response body. + // + // NOTE: The referred field must be present at the top-level of the response + // message type. + string response_body = 12; + + // Additional HTTP bindings for the selector. Nested bindings must + // not contain an `additional_bindings` field themselves (that is, + // the nesting may only be one level deep). + repeated HttpRule additional_bindings = 11; +} + +// A custom pattern is used for defining custom HTTP verb. +message CustomHttpPattern { + // The name of this custom HTTP verb. + string kind = 1; + + // The path matched by this custom verb. + string path = 2; +} diff --git a/seed/java-spring/grpc/.mock/proto/user/v1/user.proto b/seed/java-spring/grpc/.mock/proto/user/v1/user.proto new file mode 100644 index 00000000000..adab9ceefb9 --- /dev/null +++ b/seed/java-spring/grpc/.mock/proto/user/v1/user.proto @@ -0,0 +1,55 @@ +syntax = "proto3"; + +package user.v1; + +import "google/api/annotations.proto"; +import "google/api/field_behavior.proto"; +import "google/protobuf/struct.proto"; + +option csharp_namespace = "User.V1"; + +message User { + string username = 1 [ + (google.api.field_behavior) = REQUIRED + ]; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateUserRequest { + string username = 1 [ + (google.api.field_behavior) = REQUIRED + ]; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateUserResponse { + User user = 1; +} + +message GetUserRequest { + string username = 1; + string email = 2; + uint32 age = 3; + float weight = 4; +} + +service UserService { + rpc CreateUser(CreateUserRequest) returns (CreateUserResponse) { + option (google.api.http) = { + post: "/users" + body: "*" + }; + } + + rpc GetUser(GetUserRequest) returns (User) { + option (google.api.http) = { + get: "/users" + }; + } +} diff --git a/seed/java-spring/grpc/core/APIException.java b/seed/java-spring/grpc/core/APIException.java new file mode 100644 index 00000000000..27289cf9b2e --- /dev/null +++ b/seed/java-spring/grpc/core/APIException.java @@ -0,0 +1,10 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package core; + +import java.lang.Exception; + +public class APIException extends Exception { +} diff --git a/seed/java-spring/grpc/core/DateTimeDeserializer.java b/seed/java-spring/grpc/core/DateTimeDeserializer.java new file mode 100644 index 00000000000..3d3174aec00 --- /dev/null +++ b/seed/java-spring/grpc/core/DateTimeDeserializer.java @@ -0,0 +1,56 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package core; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.module.SimpleModule; +import java.io.IOException; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalQueries; + +/** + * Custom deserializer that handles converting ISO8601 dates into {@link OffsetDateTime} objects. + */ +class DateTimeDeserializer extends JsonDeserializer { + private static final SimpleModule MODULE; + + static { + MODULE = new SimpleModule().addDeserializer(OffsetDateTime.class, new DateTimeDeserializer()); + } + + /** + * Gets a module wrapping this deserializer as an adapter for the Jackson ObjectMapper. + * + * @return A {@link SimpleModule} to be plugged onto Jackson ObjectMapper. + */ + public static SimpleModule getModule() { + return MODULE; + } + + @Override + public OffsetDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException { + JsonToken token = parser.currentToken(); + if (token == JsonToken.VALUE_NUMBER_INT) { + return OffsetDateTime.ofInstant(Instant.ofEpochSecond(parser.getValueAsLong()), ZoneOffset.UTC); + } else { + TemporalAccessor temporal = DateTimeFormatter.ISO_DATE_TIME.parseBest( + parser.getValueAsString(), OffsetDateTime::from, LocalDateTime::from); + + if (temporal.query(TemporalQueries.offset()) == null) { + return LocalDateTime.from(temporal).atOffset(ZoneOffset.UTC); + } else { + return OffsetDateTime.from(temporal); + } + } + } +} \ No newline at end of file diff --git a/seed/java-spring/grpc/core/ObjectMappers.java b/seed/java-spring/grpc/core/ObjectMappers.java new file mode 100644 index 00000000000..e02822614a8 --- /dev/null +++ b/seed/java-spring/grpc/core/ObjectMappers.java @@ -0,0 +1,41 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package core; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import java.io.IOException; +import java.lang.Integer; +import java.lang.Object; +import java.lang.String; + +public final class ObjectMappers { + public static final ObjectMapper JSON_MAPPER = JsonMapper.builder() + .addModule(new Jdk8Module()) + .addModule(new JavaTimeModule()) + .addModule(DateTimeDeserializer.getModule()) + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .build(); + + private ObjectMappers() { + } + + public static String stringify(Object o) { + try { + return JSON_MAPPER.setSerializationInclusion(JsonInclude.Include.ALWAYS) + .writerWithDefaultPrettyPrinter() + .writeValueAsString(o); + } + catch (IOException e) { + return o.getClass().getName() + "@" + Integer.toHexString(o.hashCode()); + } + } + } diff --git a/seed/java-spring/grpc/resources/user/UserService.java b/seed/java-spring/grpc/resources/user/UserService.java new file mode 100644 index 00000000000..3b66e5459b0 --- /dev/null +++ b/seed/java-spring/grpc/resources/user/UserService.java @@ -0,0 +1,37 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package resources.user; + +import java.lang.Double; +import java.lang.Integer; +import java.lang.String; +import java.util.Optional; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import resources.user.requests.CreateUserRequest; +import resources.user.types.CreateUserResponse; +import resources.user.types.User; + +@RequestMapping( + path = "/" +) +public interface UserService { + @PostMapping( + value = "/users", + produces = "application/json", + consumes = "application/json" + ) + CreateUserResponse createUser(@RequestBody CreateUserRequest body); + + @GetMapping( + value = "/users", + produces = "application/json" + ) + User getUser(@RequestParam("username") Optional username, + @RequestParam("age") Optional age, @RequestParam("weight") Optional weight); +} diff --git a/seed/java-spring/grpc/resources/user/requests/CreateUserRequest.java b/seed/java-spring/grpc/resources/user/requests/CreateUserRequest.java new file mode 100644 index 00000000000..617f91685cc --- /dev/null +++ b/seed/java-spring/grpc/resources/user/requests/CreateUserRequest.java @@ -0,0 +1,192 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package resources.user.requests; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import core.ObjectMappers; +import java.lang.Double; +import java.lang.Integer; +import java.lang.Object; +import java.lang.String; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize( + builder = CreateUserRequest.Builder.class +) +public final class CreateUserRequest { + private final String username; + + private final Optional email; + + private final Optional age; + + private final Optional weight; + + private CreateUserRequest(String username, Optional email, Optional age, + Optional weight) { + this.username = username; + this.email = email; + this.age = age; + this.weight = weight; + } + + @JsonProperty("username") + public String getUsername() { + return username; + } + + @JsonProperty("email") + public Optional getEmail() { + return email; + } + + @JsonProperty("age") + public Optional getAge() { + return age; + } + + @JsonProperty("weight") + public Optional getWeight() { + return weight; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof CreateUserRequest && equalTo((CreateUserRequest) other); + } + + private boolean equalTo(CreateUserRequest other) { + return username.equals(other.username) && email.equals(other.email) && age.equals(other.age) && weight.equals(other.weight); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.username, this.email, this.age, this.weight); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static UsernameStage builder() { + return new Builder(); + } + + public interface UsernameStage { + _FinalStage username(String username); + + Builder from(CreateUserRequest other); + } + + public interface _FinalStage { + CreateUserRequest build(); + + _FinalStage email(Optional email); + + _FinalStage email(String email); + + _FinalStage age(Optional age); + + _FinalStage age(Integer age); + + _FinalStage weight(Optional weight); + + _FinalStage weight(Double weight); + } + + @JsonIgnoreProperties( + ignoreUnknown = true + ) + public static final class Builder implements UsernameStage, _FinalStage { + private String username; + + private Optional weight = Optional.empty(); + + private Optional age = Optional.empty(); + + private Optional email = Optional.empty(); + + private Builder() { + } + + @java.lang.Override + public Builder from(CreateUserRequest other) { + username(other.getUsername()); + email(other.getEmail()); + age(other.getAge()); + weight(other.getWeight()); + return this; + } + + @java.lang.Override + @JsonSetter("username") + public _FinalStage username(String username) { + this.username = username; + return this; + } + + @java.lang.Override + public _FinalStage weight(Double weight) { + this.weight = Optional.ofNullable(weight); + return this; + } + + @java.lang.Override + @JsonSetter( + value = "weight", + nulls = Nulls.SKIP + ) + public _FinalStage weight(Optional weight) { + this.weight = weight; + return this; + } + + @java.lang.Override + public _FinalStage age(Integer age) { + this.age = Optional.ofNullable(age); + return this; + } + + @java.lang.Override + @JsonSetter( + value = "age", + nulls = Nulls.SKIP + ) + public _FinalStage age(Optional age) { + this.age = age; + return this; + } + + @java.lang.Override + public _FinalStage email(String email) { + this.email = Optional.ofNullable(email); + return this; + } + + @java.lang.Override + @JsonSetter( + value = "email", + nulls = Nulls.SKIP + ) + public _FinalStage email(Optional email) { + this.email = email; + return this; + } + + @java.lang.Override + public CreateUserRequest build() { + return new CreateUserRequest(username, email, age, weight); + } + } +} diff --git a/seed/java-spring/grpc/resources/user/types/CreateUserResponse.java b/seed/java-spring/grpc/resources/user/types/CreateUserResponse.java new file mode 100644 index 00000000000..7b7bcd7271f --- /dev/null +++ b/seed/java-spring/grpc/resources/user/types/CreateUserResponse.java @@ -0,0 +1,94 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package resources.user.types; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import core.ObjectMappers; +import java.lang.Object; +import java.lang.String; +import java.util.Objects; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize( + builder = CreateUserResponse.Builder.class +) +public final class CreateUserResponse { + private final User user; + + private CreateUserResponse(User user) { + this.user = user; + } + + @JsonProperty("user") + public User getUser() { + return user; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof CreateUserResponse && equalTo((CreateUserResponse) other); + } + + private boolean equalTo(CreateUserResponse other) { + return user.equals(other.user); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.user); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static UserStage builder() { + return new Builder(); + } + + public interface UserStage { + _FinalStage user(User user); + + Builder from(CreateUserResponse other); + } + + public interface _FinalStage { + CreateUserResponse build(); + } + + @JsonIgnoreProperties( + ignoreUnknown = true + ) + public static final class Builder implements UserStage, _FinalStage { + private User user; + + private Builder() { + } + + @java.lang.Override + public Builder from(CreateUserResponse other) { + user(other.getUser()); + return this; + } + + @java.lang.Override + @JsonSetter("user") + public _FinalStage user(User user) { + this.user = user; + return this; + } + + @java.lang.Override + public CreateUserResponse build() { + return new CreateUserResponse(user); + } + } +} diff --git a/seed/java-spring/grpc/resources/user/types/Metadata.java b/seed/java-spring/grpc/resources/user/types/Metadata.java new file mode 100644 index 00000000000..6e9135e9951 --- /dev/null +++ b/seed/java-spring/grpc/resources/user/types/Metadata.java @@ -0,0 +1,47 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package resources.user.types; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import java.lang.Object; +import java.lang.String; +import java.util.Map; +import java.util.Optional; + +public final class Metadata { + private final Map> value; + + private Metadata(Map> value) { + this.value = value; + } + + @JsonValue + public Map> get() { + return this.value; + } + + @java.lang.Override + public boolean equals(Object other) { + return this == other || (other instanceof Metadata && this.value.equals(((Metadata) other).value)); + } + + @java.lang.Override + public int hashCode() { + return value.hashCode(); + } + + @java.lang.Override + public String toString() { + return value.toString(); + } + + @JsonCreator( + mode = JsonCreator.Mode.DELEGATING + ) + public static Metadata of(Map> value) { + return new Metadata(value); + } +} diff --git a/seed/java-spring/grpc/resources/user/types/MetadataValue.java b/seed/java-spring/grpc/resources/user/types/MetadataValue.java new file mode 100644 index 00000000000..0f2a075ea28 --- /dev/null +++ b/seed/java-spring/grpc/resources/user/types/MetadataValue.java @@ -0,0 +1,127 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package resources.user.types; + +import com.fasterxml.jackson.annotation.JsonValue; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import core.ObjectMappers; +import java.io.IOException; +import java.lang.Boolean; +import java.lang.Double; +import java.lang.IllegalArgumentException; +import java.lang.IllegalStateException; +import java.lang.Object; +import java.lang.String; +import java.util.List; +import java.util.Objects; + +@JsonDeserialize( + using = MetadataValue.Deserializer.class +) +public final class MetadataValue { + private final Object value; + + private final int type; + + private MetadataValue(Object value, int type) { + this.value = value; + this.type = type; + } + + @JsonValue + public Object get() { + return this.value; + } + + public T visit(Visitor visitor) { + if(this.type == 0) { + return visitor.visit((double) this.value); + } else if(this.type == 1) { + return visitor.visit((String) this.value); + } else if(this.type == 2) { + return visitor.visit((boolean) this.value); + } else if(this.type == 3) { + return visitor.visit((List) this.value); + } + throw new IllegalStateException("Failed to visit value. This should never happen."); + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof MetadataValue && equalTo((MetadataValue) other); + } + + private boolean equalTo(MetadataValue other) { + return value.equals(other.value); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.value); + } + + @java.lang.Override + public String toString() { + return this.value.toString(); + } + + public static MetadataValue of(double value) { + return new MetadataValue(value, 0); + } + + public static MetadataValue of(String value) { + return new MetadataValue(value, 1); + } + + public static MetadataValue of(boolean value) { + return new MetadataValue(value, 2); + } + + public static MetadataValue of(List value) { + return new MetadataValue(value, 3); + } + + public interface Visitor { + T visit(double value); + + T visit(String value); + + T visit(boolean value); + + T visit(List value); + } + + static final class Deserializer extends StdDeserializer { + Deserializer() { + super(MetadataValue.class); + } + + @java.lang.Override + public MetadataValue deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + Object value = p.readValueAs(Object.class); + if (value instanceof Double) { + return of((Double) value); + } + try { + return of(ObjectMappers.JSON_MAPPER.convertValue(value, String.class)); + } catch(IllegalArgumentException e) { + } + if (value instanceof Boolean) { + return of((Boolean) value); + } + try { + return of(ObjectMappers.JSON_MAPPER.convertValue(value, new TypeReference>() {})); + } catch(IllegalArgumentException e) { + } + throw new JsonParseException(p, "Failed to deserialize"); + } + } +} diff --git a/seed/java-spring/grpc/resources/user/types/User.java b/seed/java-spring/grpc/resources/user/types/User.java new file mode 100644 index 00000000000..bd10a13eea0 --- /dev/null +++ b/seed/java-spring/grpc/resources/user/types/User.java @@ -0,0 +1,245 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +package resources.user.types; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import core.ObjectMappers; +import java.lang.Double; +import java.lang.Integer; +import java.lang.Object; +import java.lang.String; +import java.util.Objects; +import java.util.Optional; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize( + builder = User.Builder.class +) +public final class User { + private final String id; + + private final String username; + + private final Optional email; + + private final Optional age; + + private final Optional weight; + + private final Optional metadata; + + private User(String id, String username, Optional email, Optional age, + Optional weight, Optional metadata) { + this.id = id; + this.username = username; + this.email = email; + this.age = age; + this.weight = weight; + this.metadata = metadata; + } + + @JsonProperty("id") + public String getId() { + return id; + } + + @JsonProperty("username") + public String getUsername() { + return username; + } + + @JsonProperty("email") + public Optional getEmail() { + return email; + } + + @JsonProperty("age") + public Optional getAge() { + return age; + } + + @JsonProperty("weight") + public Optional getWeight() { + return weight; + } + + @JsonProperty("metadata") + public Optional getMetadata() { + return metadata; + } + + @java.lang.Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof User && equalTo((User) other); + } + + private boolean equalTo(User other) { + return id.equals(other.id) && username.equals(other.username) && email.equals(other.email) && age.equals(other.age) && weight.equals(other.weight) && metadata.equals(other.metadata); + } + + @java.lang.Override + public int hashCode() { + return Objects.hash(this.id, this.username, this.email, this.age, this.weight, this.metadata); + } + + @java.lang.Override + public String toString() { + return ObjectMappers.stringify(this); + } + + public static IdStage builder() { + return new Builder(); + } + + public interface IdStage { + UsernameStage id(String id); + + Builder from(User other); + } + + public interface UsernameStage { + _FinalStage username(String username); + } + + public interface _FinalStage { + User build(); + + _FinalStage email(Optional email); + + _FinalStage email(String email); + + _FinalStage age(Optional age); + + _FinalStage age(Integer age); + + _FinalStage weight(Optional weight); + + _FinalStage weight(Double weight); + + _FinalStage metadata(Optional metadata); + + _FinalStage metadata(Metadata metadata); + } + + @JsonIgnoreProperties( + ignoreUnknown = true + ) + public static final class Builder implements IdStage, UsernameStage, _FinalStage { + private String id; + + private String username; + + private Optional metadata = Optional.empty(); + + private Optional weight = Optional.empty(); + + private Optional age = Optional.empty(); + + private Optional email = Optional.empty(); + + private Builder() { + } + + @java.lang.Override + public Builder from(User other) { + id(other.getId()); + username(other.getUsername()); + email(other.getEmail()); + age(other.getAge()); + weight(other.getWeight()); + metadata(other.getMetadata()); + return this; + } + + @java.lang.Override + @JsonSetter("id") + public UsernameStage id(String id) { + this.id = id; + return this; + } + + @java.lang.Override + @JsonSetter("username") + public _FinalStage username(String username) { + this.username = username; + return this; + } + + @java.lang.Override + public _FinalStage metadata(Metadata metadata) { + this.metadata = Optional.ofNullable(metadata); + return this; + } + + @java.lang.Override + @JsonSetter( + value = "metadata", + nulls = Nulls.SKIP + ) + public _FinalStage metadata(Optional metadata) { + this.metadata = metadata; + return this; + } + + @java.lang.Override + public _FinalStage weight(Double weight) { + this.weight = Optional.ofNullable(weight); + return this; + } + + @java.lang.Override + @JsonSetter( + value = "weight", + nulls = Nulls.SKIP + ) + public _FinalStage weight(Optional weight) { + this.weight = weight; + return this; + } + + @java.lang.Override + public _FinalStage age(Integer age) { + this.age = Optional.ofNullable(age); + return this; + } + + @java.lang.Override + @JsonSetter( + value = "age", + nulls = Nulls.SKIP + ) + public _FinalStage age(Optional age) { + this.age = age; + return this; + } + + @java.lang.Override + public _FinalStage email(String email) { + this.email = Optional.ofNullable(email); + return this; + } + + @java.lang.Override + @JsonSetter( + value = "email", + nulls = Nulls.SKIP + ) + public _FinalStage email(Optional email) { + this.email = email; + return this; + } + + @java.lang.Override + public User build() { + return new User(id, username, email, age, weight, metadata); + } + } +} diff --git a/seed/java-spring/grpc/snippet-templates.json b/seed/java-spring/grpc/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/java-spring/grpc/snippet.json b/seed/java-spring/grpc/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/openapi/grpc-proto/.mock/fern.config.json b/seed/openapi/grpc-proto/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/openapi/grpc-proto/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/openapi/grpc-proto/.mock/generators.yml b/seed/openapi/grpc-proto/.mock/generators.yml new file mode 100644 index 00000000000..902c4f50168 --- /dev/null +++ b/seed/openapi/grpc-proto/.mock/generators.yml @@ -0,0 +1,5 @@ +api: + - proto: + root: proto + target: proto/user/v1/user.proto + local-generation: true \ No newline at end of file diff --git a/seed/openapi/grpc-proto/.mock/proto/google/api/annotations.proto b/seed/openapi/grpc-proto/.mock/proto/google/api/annotations.proto new file mode 100644 index 00000000000..8ff42098404 --- /dev/null +++ b/seed/openapi/grpc-proto/.mock/proto/google/api/annotations.proto @@ -0,0 +1,31 @@ +// Copyright 2015 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/api/http.proto"; +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "AnnotationsProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // See `HttpRule`. + HttpRule http = 72295728; +} \ No newline at end of file diff --git a/seed/openapi/grpc-proto/.mock/proto/google/api/field_behavior.proto b/seed/openapi/grpc-proto/.mock/proto/google/api/field_behavior.proto new file mode 100644 index 00000000000..128799c558d --- /dev/null +++ b/seed/openapi/grpc-proto/.mock/proto/google/api/field_behavior.proto @@ -0,0 +1,104 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "FieldBehaviorProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.FieldOptions { + // A designation of a specific field behavior (required, output only, etc.) + // in protobuf messages. + // + // Examples: + // + // string name = 1 [(google.api.field_behavior) = REQUIRED]; + // State state = 1 [(google.api.field_behavior) = OUTPUT_ONLY]; + // google.protobuf.Duration ttl = 1 + // [(google.api.field_behavior) = INPUT_ONLY]; + // google.protobuf.Timestamp expire_time = 1 + // [(google.api.field_behavior) = OUTPUT_ONLY, + // (google.api.field_behavior) = IMMUTABLE]; + repeated google.api.FieldBehavior field_behavior = 1052; +} + +// An indicator of the behavior of a given field (for example, that a field +// is required in requests, or given as output but ignored as input). +// This **does not** change the behavior in protocol buffers itself; it only +// denotes the behavior and may affect how API tooling handles the field. +// +// Note: This enum **may** receive new values in the future. +enum FieldBehavior { + // Conventional default for enums. Do not use this. + FIELD_BEHAVIOR_UNSPECIFIED = 0; + + // Specifically denotes a field as optional. + // While all fields in protocol buffers are optional, this may be specified + // for emphasis if appropriate. + OPTIONAL = 1; + + // Denotes a field as required. + // This indicates that the field **must** be provided as part of the request, + // and failure to do so will cause an error (usually `INVALID_ARGUMENT`). + REQUIRED = 2; + + // Denotes a field as output only. + // This indicates that the field is provided in responses, but including the + // field in a request does nothing (the server *must* ignore it and + // *must not* throw an error as a result of the field's presence). + OUTPUT_ONLY = 3; + + // Denotes a field as input only. + // This indicates that the field is provided in requests, and the + // corresponding field is not included in output. + INPUT_ONLY = 4; + + // Denotes a field as immutable. + // This indicates that the field may be set once in a request to create a + // resource, but may not be changed thereafter. + IMMUTABLE = 5; + + // Denotes that a (repeated) field is an unordered list. + // This indicates that the service may provide the elements of the list + // in any arbitrary order, rather than the order the user originally + // provided. Additionally, the list's order may or may not be stable. + UNORDERED_LIST = 6; + + // Denotes that this field returns a non-empty default value if not set. + // This indicates that if the user provides the empty value in a request, + // a non-empty value will be returned. The user will not be aware of what + // non-empty value to expect. + NON_EMPTY_DEFAULT = 7; + + // Denotes that the field in a resource (a message annotated with + // google.api.resource) is used in the resource name to uniquely identify the + // resource. For AIP-compliant APIs, this should only be applied to the + // `name` field on the resource. + // + // This behavior should not be applied to references to other resources within + // the message. + // + // The identifier field of resources often have different field behavior + // depending on the request it is embedded in (e.g. for Create methods name + // is optional and unused, while for Update methods it is required). Instead + // of method-specific annotations, only `IDENTIFIER` is required. + IDENTIFIER = 8; +} \ No newline at end of file diff --git a/seed/openapi/grpc-proto/.mock/proto/google/api/http.proto b/seed/openapi/grpc-proto/.mock/proto/google/api/http.proto new file mode 100644 index 00000000000..c8392381eb9 --- /dev/null +++ b/seed/openapi/grpc-proto/.mock/proto/google/api/http.proto @@ -0,0 +1,379 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "HttpProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Defines the HTTP configuration for an API service. It contains a list of +// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method +// to one or more HTTP REST API methods. +message Http { + // A list of HTTP configuration rules that apply to individual API methods. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated HttpRule rules = 1; + + // When set to true, URL path parameters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // + // The default behavior is to not decode RFC 6570 reserved characters in multi + // segment matches. + bool fully_decode_reserved_expansion = 2; +} + +// # gRPC Transcoding +// +// gRPC Transcoding is a feature for mapping between a gRPC method and one or +// more HTTP REST endpoints. It allows developers to build a single API service +// that supports both gRPC APIs and REST APIs. Many systems, including [Google +// APIs](https://github.com/googleapis/googleapis), +// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC +// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), +// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature +// and use it for large scale production services. +// +// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies +// how different portions of the gRPC request message are mapped to the URL +// path, URL query parameters, and HTTP request body. It also controls how the +// gRPC response message is mapped to the HTTP response body. `HttpRule` is +// typically specified as an `google.api.http` annotation on the gRPC method. +// +// Each mapping specifies a URL path template and an HTTP method. The path +// template may refer to one or more fields in the gRPC request message, as long +// as each field is a non-repeated field with a primitive (non-message) type. +// The path template controls how fields of the request message are mapped to +// the URL path. +// +// Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/{name=messages/*}" +// }; +// } +// } +// message GetMessageRequest { +// string name = 1; // Mapped to URL path. +// } +// message Message { +// string text = 1; // The resource content. +// } +// +// This enables an HTTP REST to gRPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` +// +// Any fields in the request message which are not bound by the path template +// automatically become HTTP query parameters if there is no HTTP request body. +// For example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get:"/v1/messages/{message_id}" +// }; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // Mapped to URL path. +// int64 revision = 2; // Mapped to URL query parameter `revision`. +// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. +// } +// +// This enables a HTTP JSON to RPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | +// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: +// "foo"))` +// +// Note that fields which are mapped to URL query parameters must have a +// primitive type or a repeated primitive type or a non-repeated message type. +// In the case of a repeated type, the parameter can be repeated in the URL +// as `...?param=A¶m=B`. In the case of a message type, each field of the +// message is mapped to a separate parameter, such as +// `...?foo.a=A&foo.b=B&foo.c=C`. +// +// For HTTP methods that allow a request body, the `body` field +// specifies the mapping. Consider a REST update method on the +// message resource collection: +// +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } +// +// The following HTTP JSON to RPC mapping is enabled, where the +// representation of the JSON in the request body is determined by +// protos JSON encoding: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" message { text: "Hi!" })` +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. This enables the following alternative definition of +// the update method: +// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } +// +// +// The following HTTP JSON to RPC mapping is enabled: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" text: "Hi!")` +// +// Note that when using `*` in the body mapping, it is not possible to +// have HTTP parameters, as all fields not bound by the path end in +// the body. This makes this option more rarely used in practice when +// defining REST APIs. The common usage of `*` is in custom methods +// which don't use the URL at all for transferring data. +// +// It is possible to define multiple HTTP methods for one RPC by using +// the `additional_bindings` option. Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } +// +// This enables the following two alternative HTTP JSON to RPC mappings: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` +// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: +// "123456")` +// +// ## Rules for HTTP mapping +// +// 1. Leaf request fields (recursive expansion nested messages in the request +// message) are classified into three categories: +// - Fields referred by the path template. They are passed via the URL path. +// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They +// are passed via the HTTP +// request body. +// - All other fields are passed via the URL query parameters, and the +// parameter name is the field path in the request message. A repeated +// field can be represented as multiple query parameters under the same +// name. +// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL +// query parameter, all fields +// are passed via URL path and HTTP request body. +// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP +// request body, all +// fields are passed via URL path and URL query parameters. +// +// ### Path template syntax +// +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +// +// The syntax `*` matches a single URL path segment. The syntax `**` matches +// zero or more URL path segments, which must be the last part of the URL path +// except the `Verb`. +// +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable +// matches a single path segment, its template may be omitted, e.g. `{var}` +// is equivalent to `{var=*}`. +// +// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` +// contains any reserved character, such characters should be percent-encoded +// before the matching. +// +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path on the client +// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The +// server side does the reverse decoding. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{var}`. +// +// If a variable contains multiple path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path on the +// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. +// The server side does the reverse decoding, except "%2F" and "%2f" are left +// unchanged. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{+var}`. +// +// ## Using gRPC API Service Configuration +// +// gRPC API Service Configuration (service config) is a configuration language +// for configuring a gRPC service to become a user-facing product. The +// service config is simply the YAML representation of the `google.api.Service` +// proto message. +// +// As an alternative to annotating your proto file, you can configure gRPC +// transcoding in your service config YAML files. You do this by specifying a +// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same +// effect as the proto annotation. This can be particularly useful if you +// have a proto that is reused in multiple services. Note that any transcoding +// specified in the service config will override any matching transcoding +// configuration in the proto. +// +// Example: +// +// http: +// rules: +// # Selects a gRPC method and applies HttpRule to it. +// - selector: example.v1.Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} +// +// ## Special notes +// +// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the +// proto to JSON conversion must follow the [proto3 +// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). +// +// While the single segment variable follows the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String +// Expansion, the multi segment variable **does not** follow RFC 6570 Section +// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding +// for multi segment variables. +// +// The path variables **must not** refer to any repeated or mapped field, +// because client libraries are not capable of handling such variable expansion. +// +// The path variables **must not** capture the leading "/" character. The reason +// is that the most common use case "{var}" does not capture the leading "/" +// character. For consistency, all path variables must share the same behavior. +// +// Repeated message fields must not be mapped to URL query parameters, because +// no client library can support such complicated mapping. +// +// If an API needs to use a JSON array for request or response body, it can map +// the request or response body to a repeated field. However, some gRPC +// Transcoding implementations may not support this feature. +message HttpRule { + // Selects a method to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax + // details. + string selector = 1; + + // Determines the URL pattern is matched by this rules. This pattern can be + // used with any of the {get|put|post|delete|patch} methods. A custom method + // can be defined using the 'custom' field. + oneof pattern { + // Maps to HTTP GET. Used for listing and getting information about + // resources. + string get = 2; + + // Maps to HTTP PUT. Used for replacing a resource. + string put = 3; + + // Maps to HTTP POST. Used for creating a resource or performing an action. + string post = 4; + + // Maps to HTTP DELETE. Used for deleting a resource. + string delete = 5; + + // Maps to HTTP PATCH. Used for updating a resource. + string patch = 6; + + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. + CustomHttpPattern custom = 8; + } + + // The name of the request field whose value is mapped to the HTTP request + // body, or `*` for mapping all request fields not captured by the path + // pattern to the HTTP body, or omitted for not having any HTTP request body. + // + // NOTE: the referred field must be present at the top-level of the request + // message type. + string body = 7; + + // Optional. The name of the response field whose value is mapped to the HTTP + // response body. When omitted, the entire response message will be used + // as the HTTP response body. + // + // NOTE: The referred field must be present at the top-level of the response + // message type. + string response_body = 12; + + // Additional HTTP bindings for the selector. Nested bindings must + // not contain an `additional_bindings` field themselves (that is, + // the nesting may only be one level deep). + repeated HttpRule additional_bindings = 11; +} + +// A custom pattern is used for defining custom HTTP verb. +message CustomHttpPattern { + // The name of this custom HTTP verb. + string kind = 1; + + // The path matched by this custom verb. + string path = 2; +} \ No newline at end of file diff --git a/seed/openapi/grpc-proto/.mock/proto/user/v1/user.proto b/seed/openapi/grpc-proto/.mock/proto/user/v1/user.proto new file mode 100644 index 00000000000..a3ad0195bd4 --- /dev/null +++ b/seed/openapi/grpc-proto/.mock/proto/user/v1/user.proto @@ -0,0 +1,39 @@ +syntax = "proto3"; + +package user.v1; + +import "google/api/annotations.proto"; +import "google/api/field_behavior.proto"; +import "google/protobuf/struct.proto"; + +option go_package = "user/v1"; +option csharp_namespace = "User.V1"; + +message UserModel { + string username = 1; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateRequest { + string username = 1; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateResponse { + UserModel user = 1; +} + +service User { + rpc Create(CreateRequest) returns (CreateResponse) { + option (google.api.http) = { + post: "/users" + body: "*" + }; + } +} \ No newline at end of file diff --git a/seed/openapi/grpc-proto/openapi.yml b/seed/openapi/grpc-proto/openapi.yml new file mode 100644 index 00000000000..ae54a8a787c --- /dev/null +++ b/seed/openapi/grpc-proto/openapi.yml @@ -0,0 +1,86 @@ +openapi: 3.0.1 +info: + title: '""' + version: '' +paths: + /users: + post: + operationId: user_create + tags: + - User + parameters: [] + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/CreateResponse' + examples: + Example1: + value: + user: + username: username + email: email + age: 1 + weight: 1.1 + metadata: + key: value + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + username: + type: string + nullable: true + email: + type: string + nullable: true + age: + type: integer + nullable: true + weight: + type: number + format: double + nullable: true + metadata: + type: object + additionalProperties: true + nullable: true + examples: + Example1: + value: {} +components: + schemas: + CreateResponse: + title: CreateResponse + type: object + properties: + user: + $ref: '#/components/schemas/UserModel' + nullable: true + UserModel: + title: UserModel + type: object + properties: + username: + type: string + nullable: true + email: + type: string + nullable: true + age: + type: integer + nullable: true + weight: + type: number + format: double + nullable: true + metadata: + type: object + additionalProperties: true + nullable: true + securitySchemes: {} diff --git a/seed/openapi/grpc-proto/snippet-templates.json b/seed/openapi/grpc-proto/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/openapi/grpc-proto/snippet.json b/seed/openapi/grpc-proto/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/openapi/grpc/.mock/definition/api.yml b/seed/openapi/grpc/.mock/definition/api.yml new file mode 100644 index 00000000000..15a9f823724 --- /dev/null +++ b/seed/openapi/grpc/.mock/definition/api.yml @@ -0,0 +1,4 @@ +name: api + +error-discrimination: + strategy: status-code \ No newline at end of file diff --git a/seed/openapi/grpc/.mock/definition/user.yml b/seed/openapi/grpc/.mock/definition/user.yml new file mode 100644 index 00000000000..80223300afc --- /dev/null +++ b/seed/openapi/grpc/.mock/definition/user.yml @@ -0,0 +1,61 @@ +types: + Metadata: + type: map> + encoding: + proto: + type: google.protobuf.Struct + + MetadataValue: + discriminated: false + union: + - double + - string + - boolean + - list + encoding: + proto: + type: google.protobuf.Value + + User: + properties: + id: string + username: string + email: optional + age: optional + weight: optional + metadata: optional + + CreateUserResponse: + properties: + user: User + +service: + auth: false + base-path: / + transport: + grpc: + service-name: UserService + endpoints: + createUser: + method: POST + path: /users + request: + name: CreateUserRequest + body: + properties: + username: string + email: optional + age: optional + weight: optional + response: CreateUserResponse + + getUser: + method: GET + path: /users + request: + name: GetUserRequest + query-parameters: + username: optional + age: optional + weight: optional + response: User diff --git a/seed/openapi/grpc/.mock/fern.config.json b/seed/openapi/grpc/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/openapi/grpc/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/openapi/grpc/.mock/generators.yml b/seed/openapi/grpc/.mock/generators.yml new file mode 100644 index 00000000000..f62dfba5843 --- /dev/null +++ b/seed/openapi/grpc/.mock/generators.yml @@ -0,0 +1,4 @@ +api: + - proto: + root: proto + target: proto/user/v1/user.proto \ No newline at end of file diff --git a/seed/openapi/grpc/.mock/proto/google/api/annotations.proto b/seed/openapi/grpc/.mock/proto/google/api/annotations.proto new file mode 100644 index 00000000000..efdab3db6ca --- /dev/null +++ b/seed/openapi/grpc/.mock/proto/google/api/annotations.proto @@ -0,0 +1,31 @@ +// Copyright 2015 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/api/http.proto"; +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "AnnotationsProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // See `HttpRule`. + HttpRule http = 72295728; +} diff --git a/seed/openapi/grpc/.mock/proto/google/api/field_behavior.proto b/seed/openapi/grpc/.mock/proto/google/api/field_behavior.proto new file mode 100644 index 00000000000..344cb0b1fc2 --- /dev/null +++ b/seed/openapi/grpc/.mock/proto/google/api/field_behavior.proto @@ -0,0 +1,104 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "FieldBehaviorProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.FieldOptions { + // A designation of a specific field behavior (required, output only, etc.) + // in protobuf messages. + // + // Examples: + // + // string name = 1 [(google.api.field_behavior) = REQUIRED]; + // State state = 1 [(google.api.field_behavior) = OUTPUT_ONLY]; + // google.protobuf.Duration ttl = 1 + // [(google.api.field_behavior) = INPUT_ONLY]; + // google.protobuf.Timestamp expire_time = 1 + // [(google.api.field_behavior) = OUTPUT_ONLY, + // (google.api.field_behavior) = IMMUTABLE]; + repeated google.api.FieldBehavior field_behavior = 1052; +} + +// An indicator of the behavior of a given field (for example, that a field +// is required in requests, or given as output but ignored as input). +// This **does not** change the behavior in protocol buffers itself; it only +// denotes the behavior and may affect how API tooling handles the field. +// +// Note: This enum **may** receive new values in the future. +enum FieldBehavior { + // Conventional default for enums. Do not use this. + FIELD_BEHAVIOR_UNSPECIFIED = 0; + + // Specifically denotes a field as optional. + // While all fields in protocol buffers are optional, this may be specified + // for emphasis if appropriate. + OPTIONAL = 1; + + // Denotes a field as required. + // This indicates that the field **must** be provided as part of the request, + // and failure to do so will cause an error (usually `INVALID_ARGUMENT`). + REQUIRED = 2; + + // Denotes a field as output only. + // This indicates that the field is provided in responses, but including the + // field in a request does nothing (the server *must* ignore it and + // *must not* throw an error as a result of the field's presence). + OUTPUT_ONLY = 3; + + // Denotes a field as input only. + // This indicates that the field is provided in requests, and the + // corresponding field is not included in output. + INPUT_ONLY = 4; + + // Denotes a field as immutable. + // This indicates that the field may be set once in a request to create a + // resource, but may not be changed thereafter. + IMMUTABLE = 5; + + // Denotes that a (repeated) field is an unordered list. + // This indicates that the service may provide the elements of the list + // in any arbitrary order, rather than the order the user originally + // provided. Additionally, the list's order may or may not be stable. + UNORDERED_LIST = 6; + + // Denotes that this field returns a non-empty default value if not set. + // This indicates that if the user provides the empty value in a request, + // a non-empty value will be returned. The user will not be aware of what + // non-empty value to expect. + NON_EMPTY_DEFAULT = 7; + + // Denotes that the field in a resource (a message annotated with + // google.api.resource) is used in the resource name to uniquely identify the + // resource. For AIP-compliant APIs, this should only be applied to the + // `name` field on the resource. + // + // This behavior should not be applied to references to other resources within + // the message. + // + // The identifier field of resources often have different field behavior + // depending on the request it is embedded in (e.g. for Create methods name + // is optional and unused, while for Update methods it is required). Instead + // of method-specific annotations, only `IDENTIFIER` is required. + IDENTIFIER = 8; +} diff --git a/seed/openapi/grpc/.mock/proto/google/api/http.proto b/seed/openapi/grpc/.mock/proto/google/api/http.proto new file mode 100644 index 00000000000..31d867a27d5 --- /dev/null +++ b/seed/openapi/grpc/.mock/proto/google/api/http.proto @@ -0,0 +1,379 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "HttpProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Defines the HTTP configuration for an API service. It contains a list of +// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method +// to one or more HTTP REST API methods. +message Http { + // A list of HTTP configuration rules that apply to individual API methods. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated HttpRule rules = 1; + + // When set to true, URL path parameters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // + // The default behavior is to not decode RFC 6570 reserved characters in multi + // segment matches. + bool fully_decode_reserved_expansion = 2; +} + +// # gRPC Transcoding +// +// gRPC Transcoding is a feature for mapping between a gRPC method and one or +// more HTTP REST endpoints. It allows developers to build a single API service +// that supports both gRPC APIs and REST APIs. Many systems, including [Google +// APIs](https://github.com/googleapis/googleapis), +// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC +// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), +// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature +// and use it for large scale production services. +// +// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies +// how different portions of the gRPC request message are mapped to the URL +// path, URL query parameters, and HTTP request body. It also controls how the +// gRPC response message is mapped to the HTTP response body. `HttpRule` is +// typically specified as an `google.api.http` annotation on the gRPC method. +// +// Each mapping specifies a URL path template and an HTTP method. The path +// template may refer to one or more fields in the gRPC request message, as long +// as each field is a non-repeated field with a primitive (non-message) type. +// The path template controls how fields of the request message are mapped to +// the URL path. +// +// Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/{name=messages/*}" +// }; +// } +// } +// message GetMessageRequest { +// string name = 1; // Mapped to URL path. +// } +// message Message { +// string text = 1; // The resource content. +// } +// +// This enables an HTTP REST to gRPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` +// +// Any fields in the request message which are not bound by the path template +// automatically become HTTP query parameters if there is no HTTP request body. +// For example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get:"/v1/messages/{message_id}" +// }; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // Mapped to URL path. +// int64 revision = 2; // Mapped to URL query parameter `revision`. +// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. +// } +// +// This enables a HTTP JSON to RPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | +// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: +// "foo"))` +// +// Note that fields which are mapped to URL query parameters must have a +// primitive type or a repeated primitive type or a non-repeated message type. +// In the case of a repeated type, the parameter can be repeated in the URL +// as `...?param=A¶m=B`. In the case of a message type, each field of the +// message is mapped to a separate parameter, such as +// `...?foo.a=A&foo.b=B&foo.c=C`. +// +// For HTTP methods that allow a request body, the `body` field +// specifies the mapping. Consider a REST update method on the +// message resource collection: +// +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } +// +// The following HTTP JSON to RPC mapping is enabled, where the +// representation of the JSON in the request body is determined by +// protos JSON encoding: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" message { text: "Hi!" })` +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. This enables the following alternative definition of +// the update method: +// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } +// +// +// The following HTTP JSON to RPC mapping is enabled: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" text: "Hi!")` +// +// Note that when using `*` in the body mapping, it is not possible to +// have HTTP parameters, as all fields not bound by the path end in +// the body. This makes this option more rarely used in practice when +// defining REST APIs. The common usage of `*` is in custom methods +// which don't use the URL at all for transferring data. +// +// It is possible to define multiple HTTP methods for one RPC by using +// the `additional_bindings` option. Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } +// +// This enables the following two alternative HTTP JSON to RPC mappings: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` +// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: +// "123456")` +// +// ## Rules for HTTP mapping +// +// 1. Leaf request fields (recursive expansion nested messages in the request +// message) are classified into three categories: +// - Fields referred by the path template. They are passed via the URL path. +// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They +// are passed via the HTTP +// request body. +// - All other fields are passed via the URL query parameters, and the +// parameter name is the field path in the request message. A repeated +// field can be represented as multiple query parameters under the same +// name. +// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL +// query parameter, all fields +// are passed via URL path and HTTP request body. +// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP +// request body, all +// fields are passed via URL path and URL query parameters. +// +// ### Path template syntax +// +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +// +// The syntax `*` matches a single URL path segment. The syntax `**` matches +// zero or more URL path segments, which must be the last part of the URL path +// except the `Verb`. +// +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable +// matches a single path segment, its template may be omitted, e.g. `{var}` +// is equivalent to `{var=*}`. +// +// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` +// contains any reserved character, such characters should be percent-encoded +// before the matching. +// +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path on the client +// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The +// server side does the reverse decoding. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{var}`. +// +// If a variable contains multiple path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path on the +// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. +// The server side does the reverse decoding, except "%2F" and "%2f" are left +// unchanged. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{+var}`. +// +// ## Using gRPC API Service Configuration +// +// gRPC API Service Configuration (service config) is a configuration language +// for configuring a gRPC service to become a user-facing product. The +// service config is simply the YAML representation of the `google.api.Service` +// proto message. +// +// As an alternative to annotating your proto file, you can configure gRPC +// transcoding in your service config YAML files. You do this by specifying a +// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same +// effect as the proto annotation. This can be particularly useful if you +// have a proto that is reused in multiple services. Note that any transcoding +// specified in the service config will override any matching transcoding +// configuration in the proto. +// +// Example: +// +// http: +// rules: +// # Selects a gRPC method and applies HttpRule to it. +// - selector: example.v1.Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} +// +// ## Special notes +// +// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the +// proto to JSON conversion must follow the [proto3 +// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). +// +// While the single segment variable follows the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String +// Expansion, the multi segment variable **does not** follow RFC 6570 Section +// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding +// for multi segment variables. +// +// The path variables **must not** refer to any repeated or mapped field, +// because client libraries are not capable of handling such variable expansion. +// +// The path variables **must not** capture the leading "/" character. The reason +// is that the most common use case "{var}" does not capture the leading "/" +// character. For consistency, all path variables must share the same behavior. +// +// Repeated message fields must not be mapped to URL query parameters, because +// no client library can support such complicated mapping. +// +// If an API needs to use a JSON array for request or response body, it can map +// the request or response body to a repeated field. However, some gRPC +// Transcoding implementations may not support this feature. +message HttpRule { + // Selects a method to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax + // details. + string selector = 1; + + // Determines the URL pattern is matched by this rules. This pattern can be + // used with any of the {get|put|post|delete|patch} methods. A custom method + // can be defined using the 'custom' field. + oneof pattern { + // Maps to HTTP GET. Used for listing and getting information about + // resources. + string get = 2; + + // Maps to HTTP PUT. Used for replacing a resource. + string put = 3; + + // Maps to HTTP POST. Used for creating a resource or performing an action. + string post = 4; + + // Maps to HTTP DELETE. Used for deleting a resource. + string delete = 5; + + // Maps to HTTP PATCH. Used for updating a resource. + string patch = 6; + + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. + CustomHttpPattern custom = 8; + } + + // The name of the request field whose value is mapped to the HTTP request + // body, or `*` for mapping all request fields not captured by the path + // pattern to the HTTP body, or omitted for not having any HTTP request body. + // + // NOTE: the referred field must be present at the top-level of the request + // message type. + string body = 7; + + // Optional. The name of the response field whose value is mapped to the HTTP + // response body. When omitted, the entire response message will be used + // as the HTTP response body. + // + // NOTE: The referred field must be present at the top-level of the response + // message type. + string response_body = 12; + + // Additional HTTP bindings for the selector. Nested bindings must + // not contain an `additional_bindings` field themselves (that is, + // the nesting may only be one level deep). + repeated HttpRule additional_bindings = 11; +} + +// A custom pattern is used for defining custom HTTP verb. +message CustomHttpPattern { + // The name of this custom HTTP verb. + string kind = 1; + + // The path matched by this custom verb. + string path = 2; +} diff --git a/seed/openapi/grpc/.mock/proto/user/v1/user.proto b/seed/openapi/grpc/.mock/proto/user/v1/user.proto new file mode 100644 index 00000000000..adab9ceefb9 --- /dev/null +++ b/seed/openapi/grpc/.mock/proto/user/v1/user.proto @@ -0,0 +1,55 @@ +syntax = "proto3"; + +package user.v1; + +import "google/api/annotations.proto"; +import "google/api/field_behavior.proto"; +import "google/protobuf/struct.proto"; + +option csharp_namespace = "User.V1"; + +message User { + string username = 1 [ + (google.api.field_behavior) = REQUIRED + ]; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateUserRequest { + string username = 1 [ + (google.api.field_behavior) = REQUIRED + ]; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateUserResponse { + User user = 1; +} + +message GetUserRequest { + string username = 1; + string email = 2; + uint32 age = 3; + float weight = 4; +} + +service UserService { + rpc CreateUser(CreateUserRequest) returns (CreateUserResponse) { + option (google.api.http) = { + post: "/users" + body: "*" + }; + } + + rpc GetUser(GetUserRequest) returns (User) { + option (google.api.http) = { + get: "/users" + }; + } +} diff --git a/seed/openapi/grpc/openapi.yml b/seed/openapi/grpc/openapi.yml new file mode 100644 index 00000000000..13d97dd40a6 --- /dev/null +++ b/seed/openapi/grpc/openapi.yml @@ -0,0 +1,121 @@ +openapi: 3.0.1 +info: + title: api + version: '' +paths: + /users: + post: + operationId: user_createUser + tags: + - User + parameters: [] + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/CreateUserResponse' + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + username: + type: string + email: + type: string + nullable: true + age: + type: integer + nullable: true + weight: + type: number + format: double + nullable: true + required: + - username + get: + operationId: user_getUser + tags: + - User + parameters: + - name: username + in: query + required: false + schema: + type: string + nullable: true + - name: age + in: query + required: false + schema: + type: integer + nullable: true + - name: weight + in: query + required: false + schema: + type: number + format: double + nullable: true + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/User' +components: + schemas: + Metadata: + title: Metadata + type: object + additionalProperties: + $ref: '#/components/schemas/MetadataValue' + nullable: true + MetadataValue: + title: MetadataValue + oneOf: + - type: number + format: double + - type: string + - type: boolean + - type: array + items: + $ref: '#/components/schemas/MetadataValue' + User: + title: User + type: object + properties: + id: + type: string + username: + type: string + email: + type: string + nullable: true + age: + type: integer + nullable: true + weight: + type: number + format: double + nullable: true + metadata: + $ref: '#/components/schemas/Metadata' + nullable: true + required: + - id + - username + CreateUserResponse: + title: CreateUserResponse + type: object + properties: + user: + $ref: '#/components/schemas/User' + required: + - user + securitySchemes: {} diff --git a/seed/openapi/grpc/snippet-templates.json b/seed/openapi/grpc/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/openapi/grpc/snippet.json b/seed/openapi/grpc/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/postman/grpc-proto/.mock/fern.config.json b/seed/postman/grpc-proto/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/postman/grpc-proto/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/postman/grpc-proto/.mock/generators.yml b/seed/postman/grpc-proto/.mock/generators.yml new file mode 100644 index 00000000000..902c4f50168 --- /dev/null +++ b/seed/postman/grpc-proto/.mock/generators.yml @@ -0,0 +1,5 @@ +api: + - proto: + root: proto + target: proto/user/v1/user.proto + local-generation: true \ No newline at end of file diff --git a/seed/postman/grpc-proto/.mock/proto/google/api/annotations.proto b/seed/postman/grpc-proto/.mock/proto/google/api/annotations.proto new file mode 100644 index 00000000000..8ff42098404 --- /dev/null +++ b/seed/postman/grpc-proto/.mock/proto/google/api/annotations.proto @@ -0,0 +1,31 @@ +// Copyright 2015 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/api/http.proto"; +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "AnnotationsProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // See `HttpRule`. + HttpRule http = 72295728; +} \ No newline at end of file diff --git a/seed/postman/grpc-proto/.mock/proto/google/api/field_behavior.proto b/seed/postman/grpc-proto/.mock/proto/google/api/field_behavior.proto new file mode 100644 index 00000000000..128799c558d --- /dev/null +++ b/seed/postman/grpc-proto/.mock/proto/google/api/field_behavior.proto @@ -0,0 +1,104 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "FieldBehaviorProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.FieldOptions { + // A designation of a specific field behavior (required, output only, etc.) + // in protobuf messages. + // + // Examples: + // + // string name = 1 [(google.api.field_behavior) = REQUIRED]; + // State state = 1 [(google.api.field_behavior) = OUTPUT_ONLY]; + // google.protobuf.Duration ttl = 1 + // [(google.api.field_behavior) = INPUT_ONLY]; + // google.protobuf.Timestamp expire_time = 1 + // [(google.api.field_behavior) = OUTPUT_ONLY, + // (google.api.field_behavior) = IMMUTABLE]; + repeated google.api.FieldBehavior field_behavior = 1052; +} + +// An indicator of the behavior of a given field (for example, that a field +// is required in requests, or given as output but ignored as input). +// This **does not** change the behavior in protocol buffers itself; it only +// denotes the behavior and may affect how API tooling handles the field. +// +// Note: This enum **may** receive new values in the future. +enum FieldBehavior { + // Conventional default for enums. Do not use this. + FIELD_BEHAVIOR_UNSPECIFIED = 0; + + // Specifically denotes a field as optional. + // While all fields in protocol buffers are optional, this may be specified + // for emphasis if appropriate. + OPTIONAL = 1; + + // Denotes a field as required. + // This indicates that the field **must** be provided as part of the request, + // and failure to do so will cause an error (usually `INVALID_ARGUMENT`). + REQUIRED = 2; + + // Denotes a field as output only. + // This indicates that the field is provided in responses, but including the + // field in a request does nothing (the server *must* ignore it and + // *must not* throw an error as a result of the field's presence). + OUTPUT_ONLY = 3; + + // Denotes a field as input only. + // This indicates that the field is provided in requests, and the + // corresponding field is not included in output. + INPUT_ONLY = 4; + + // Denotes a field as immutable. + // This indicates that the field may be set once in a request to create a + // resource, but may not be changed thereafter. + IMMUTABLE = 5; + + // Denotes that a (repeated) field is an unordered list. + // This indicates that the service may provide the elements of the list + // in any arbitrary order, rather than the order the user originally + // provided. Additionally, the list's order may or may not be stable. + UNORDERED_LIST = 6; + + // Denotes that this field returns a non-empty default value if not set. + // This indicates that if the user provides the empty value in a request, + // a non-empty value will be returned. The user will not be aware of what + // non-empty value to expect. + NON_EMPTY_DEFAULT = 7; + + // Denotes that the field in a resource (a message annotated with + // google.api.resource) is used in the resource name to uniquely identify the + // resource. For AIP-compliant APIs, this should only be applied to the + // `name` field on the resource. + // + // This behavior should not be applied to references to other resources within + // the message. + // + // The identifier field of resources often have different field behavior + // depending on the request it is embedded in (e.g. for Create methods name + // is optional and unused, while for Update methods it is required). Instead + // of method-specific annotations, only `IDENTIFIER` is required. + IDENTIFIER = 8; +} \ No newline at end of file diff --git a/seed/postman/grpc-proto/.mock/proto/google/api/http.proto b/seed/postman/grpc-proto/.mock/proto/google/api/http.proto new file mode 100644 index 00000000000..c8392381eb9 --- /dev/null +++ b/seed/postman/grpc-proto/.mock/proto/google/api/http.proto @@ -0,0 +1,379 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "HttpProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Defines the HTTP configuration for an API service. It contains a list of +// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method +// to one or more HTTP REST API methods. +message Http { + // A list of HTTP configuration rules that apply to individual API methods. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated HttpRule rules = 1; + + // When set to true, URL path parameters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // + // The default behavior is to not decode RFC 6570 reserved characters in multi + // segment matches. + bool fully_decode_reserved_expansion = 2; +} + +// # gRPC Transcoding +// +// gRPC Transcoding is a feature for mapping between a gRPC method and one or +// more HTTP REST endpoints. It allows developers to build a single API service +// that supports both gRPC APIs and REST APIs. Many systems, including [Google +// APIs](https://github.com/googleapis/googleapis), +// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC +// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), +// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature +// and use it for large scale production services. +// +// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies +// how different portions of the gRPC request message are mapped to the URL +// path, URL query parameters, and HTTP request body. It also controls how the +// gRPC response message is mapped to the HTTP response body. `HttpRule` is +// typically specified as an `google.api.http` annotation on the gRPC method. +// +// Each mapping specifies a URL path template and an HTTP method. The path +// template may refer to one or more fields in the gRPC request message, as long +// as each field is a non-repeated field with a primitive (non-message) type. +// The path template controls how fields of the request message are mapped to +// the URL path. +// +// Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/{name=messages/*}" +// }; +// } +// } +// message GetMessageRequest { +// string name = 1; // Mapped to URL path. +// } +// message Message { +// string text = 1; // The resource content. +// } +// +// This enables an HTTP REST to gRPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` +// +// Any fields in the request message which are not bound by the path template +// automatically become HTTP query parameters if there is no HTTP request body. +// For example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get:"/v1/messages/{message_id}" +// }; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // Mapped to URL path. +// int64 revision = 2; // Mapped to URL query parameter `revision`. +// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. +// } +// +// This enables a HTTP JSON to RPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | +// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: +// "foo"))` +// +// Note that fields which are mapped to URL query parameters must have a +// primitive type or a repeated primitive type or a non-repeated message type. +// In the case of a repeated type, the parameter can be repeated in the URL +// as `...?param=A¶m=B`. In the case of a message type, each field of the +// message is mapped to a separate parameter, such as +// `...?foo.a=A&foo.b=B&foo.c=C`. +// +// For HTTP methods that allow a request body, the `body` field +// specifies the mapping. Consider a REST update method on the +// message resource collection: +// +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } +// +// The following HTTP JSON to RPC mapping is enabled, where the +// representation of the JSON in the request body is determined by +// protos JSON encoding: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" message { text: "Hi!" })` +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. This enables the following alternative definition of +// the update method: +// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } +// +// +// The following HTTP JSON to RPC mapping is enabled: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" text: "Hi!")` +// +// Note that when using `*` in the body mapping, it is not possible to +// have HTTP parameters, as all fields not bound by the path end in +// the body. This makes this option more rarely used in practice when +// defining REST APIs. The common usage of `*` is in custom methods +// which don't use the URL at all for transferring data. +// +// It is possible to define multiple HTTP methods for one RPC by using +// the `additional_bindings` option. Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } +// +// This enables the following two alternative HTTP JSON to RPC mappings: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` +// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: +// "123456")` +// +// ## Rules for HTTP mapping +// +// 1. Leaf request fields (recursive expansion nested messages in the request +// message) are classified into three categories: +// - Fields referred by the path template. They are passed via the URL path. +// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They +// are passed via the HTTP +// request body. +// - All other fields are passed via the URL query parameters, and the +// parameter name is the field path in the request message. A repeated +// field can be represented as multiple query parameters under the same +// name. +// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL +// query parameter, all fields +// are passed via URL path and HTTP request body. +// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP +// request body, all +// fields are passed via URL path and URL query parameters. +// +// ### Path template syntax +// +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +// +// The syntax `*` matches a single URL path segment. The syntax `**` matches +// zero or more URL path segments, which must be the last part of the URL path +// except the `Verb`. +// +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable +// matches a single path segment, its template may be omitted, e.g. `{var}` +// is equivalent to `{var=*}`. +// +// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` +// contains any reserved character, such characters should be percent-encoded +// before the matching. +// +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path on the client +// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The +// server side does the reverse decoding. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{var}`. +// +// If a variable contains multiple path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path on the +// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. +// The server side does the reverse decoding, except "%2F" and "%2f" are left +// unchanged. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{+var}`. +// +// ## Using gRPC API Service Configuration +// +// gRPC API Service Configuration (service config) is a configuration language +// for configuring a gRPC service to become a user-facing product. The +// service config is simply the YAML representation of the `google.api.Service` +// proto message. +// +// As an alternative to annotating your proto file, you can configure gRPC +// transcoding in your service config YAML files. You do this by specifying a +// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same +// effect as the proto annotation. This can be particularly useful if you +// have a proto that is reused in multiple services. Note that any transcoding +// specified in the service config will override any matching transcoding +// configuration in the proto. +// +// Example: +// +// http: +// rules: +// # Selects a gRPC method and applies HttpRule to it. +// - selector: example.v1.Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} +// +// ## Special notes +// +// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the +// proto to JSON conversion must follow the [proto3 +// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). +// +// While the single segment variable follows the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String +// Expansion, the multi segment variable **does not** follow RFC 6570 Section +// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding +// for multi segment variables. +// +// The path variables **must not** refer to any repeated or mapped field, +// because client libraries are not capable of handling such variable expansion. +// +// The path variables **must not** capture the leading "/" character. The reason +// is that the most common use case "{var}" does not capture the leading "/" +// character. For consistency, all path variables must share the same behavior. +// +// Repeated message fields must not be mapped to URL query parameters, because +// no client library can support such complicated mapping. +// +// If an API needs to use a JSON array for request or response body, it can map +// the request or response body to a repeated field. However, some gRPC +// Transcoding implementations may not support this feature. +message HttpRule { + // Selects a method to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax + // details. + string selector = 1; + + // Determines the URL pattern is matched by this rules. This pattern can be + // used with any of the {get|put|post|delete|patch} methods. A custom method + // can be defined using the 'custom' field. + oneof pattern { + // Maps to HTTP GET. Used for listing and getting information about + // resources. + string get = 2; + + // Maps to HTTP PUT. Used for replacing a resource. + string put = 3; + + // Maps to HTTP POST. Used for creating a resource or performing an action. + string post = 4; + + // Maps to HTTP DELETE. Used for deleting a resource. + string delete = 5; + + // Maps to HTTP PATCH. Used for updating a resource. + string patch = 6; + + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. + CustomHttpPattern custom = 8; + } + + // The name of the request field whose value is mapped to the HTTP request + // body, or `*` for mapping all request fields not captured by the path + // pattern to the HTTP body, or omitted for not having any HTTP request body. + // + // NOTE: the referred field must be present at the top-level of the request + // message type. + string body = 7; + + // Optional. The name of the response field whose value is mapped to the HTTP + // response body. When omitted, the entire response message will be used + // as the HTTP response body. + // + // NOTE: The referred field must be present at the top-level of the response + // message type. + string response_body = 12; + + // Additional HTTP bindings for the selector. Nested bindings must + // not contain an `additional_bindings` field themselves (that is, + // the nesting may only be one level deep). + repeated HttpRule additional_bindings = 11; +} + +// A custom pattern is used for defining custom HTTP verb. +message CustomHttpPattern { + // The name of this custom HTTP verb. + string kind = 1; + + // The path matched by this custom verb. + string path = 2; +} \ No newline at end of file diff --git a/seed/postman/grpc-proto/.mock/proto/user/v1/user.proto b/seed/postman/grpc-proto/.mock/proto/user/v1/user.proto new file mode 100644 index 00000000000..a3ad0195bd4 --- /dev/null +++ b/seed/postman/grpc-proto/.mock/proto/user/v1/user.proto @@ -0,0 +1,39 @@ +syntax = "proto3"; + +package user.v1; + +import "google/api/annotations.proto"; +import "google/api/field_behavior.proto"; +import "google/protobuf/struct.proto"; + +option go_package = "user/v1"; +option csharp_namespace = "User.V1"; + +message UserModel { + string username = 1; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateRequest { + string username = 1; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateResponse { + UserModel user = 1; +} + +service User { + rpc Create(CreateRequest) returns (CreateResponse) { + option (google.api.http) = { + post: "/users" + body: "*" + }; + } +} \ No newline at end of file diff --git a/seed/postman/grpc-proto/collection.json b/seed/postman/grpc-proto/collection.json new file mode 100644 index 00000000000..5d5635b2fbd --- /dev/null +++ b/seed/postman/grpc-proto/collection.json @@ -0,0 +1,102 @@ +{ + "info": { + "name": "\"\"", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "description": null + }, + "variable": [ + { + "key": "baseUrl", + "value": "", + "type": "string" + } + ], + "auth": null, + "item": [ + { + "_type": "container", + "description": null, + "name": "User", + "item": [ + { + "_type": "endpoint", + "name": "Create", + "request": { + "description": null, + "url": { + "raw": "{{baseUrl}}/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users" + ], + "query": [], + "variable": [] + }, + "header": [ + { + "type": "text", + "key": "Content-Type", + "value": "application/json" + } + ], + "method": "POST", + "auth": null, + "body": { + "mode": "raw", + "raw": "{}", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "response": [ + { + "name": "Success", + "status": "OK", + "code": 200, + "originalRequest": { + "description": null, + "url": { + "raw": "{{baseUrl}}/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users" + ], + "query": [], + "variable": [] + }, + "header": [ + { + "type": "text", + "key": "Content-Type", + "value": "application/json" + } + ], + "method": "POST", + "auth": null, + "body": { + "mode": "raw", + "raw": "{}", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "description": "OK", + "body": "{\n \"user\": {\n \"username\": \"username\",\n \"email\": \"email\",\n \"age\": 1,\n \"weight\": 1.1,\n \"metadata\": {\n \"key\": \"value\"\n }\n }\n}", + "_postman_previewlanguage": "json" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/seed/postman/grpc-proto/snippet-templates.json b/seed/postman/grpc-proto/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/postman/grpc-proto/snippet.json b/seed/postman/grpc-proto/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/postman/grpc/.mock/definition/api.yml b/seed/postman/grpc/.mock/definition/api.yml new file mode 100644 index 00000000000..15a9f823724 --- /dev/null +++ b/seed/postman/grpc/.mock/definition/api.yml @@ -0,0 +1,4 @@ +name: api + +error-discrimination: + strategy: status-code \ No newline at end of file diff --git a/seed/postman/grpc/.mock/definition/user.yml b/seed/postman/grpc/.mock/definition/user.yml new file mode 100644 index 00000000000..80223300afc --- /dev/null +++ b/seed/postman/grpc/.mock/definition/user.yml @@ -0,0 +1,61 @@ +types: + Metadata: + type: map> + encoding: + proto: + type: google.protobuf.Struct + + MetadataValue: + discriminated: false + union: + - double + - string + - boolean + - list + encoding: + proto: + type: google.protobuf.Value + + User: + properties: + id: string + username: string + email: optional + age: optional + weight: optional + metadata: optional + + CreateUserResponse: + properties: + user: User + +service: + auth: false + base-path: / + transport: + grpc: + service-name: UserService + endpoints: + createUser: + method: POST + path: /users + request: + name: CreateUserRequest + body: + properties: + username: string + email: optional + age: optional + weight: optional + response: CreateUserResponse + + getUser: + method: GET + path: /users + request: + name: GetUserRequest + query-parameters: + username: optional + age: optional + weight: optional + response: User diff --git a/seed/postman/grpc/.mock/fern.config.json b/seed/postman/grpc/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/postman/grpc/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/postman/grpc/.mock/generators.yml b/seed/postman/grpc/.mock/generators.yml new file mode 100644 index 00000000000..f62dfba5843 --- /dev/null +++ b/seed/postman/grpc/.mock/generators.yml @@ -0,0 +1,4 @@ +api: + - proto: + root: proto + target: proto/user/v1/user.proto \ No newline at end of file diff --git a/seed/postman/grpc/.mock/proto/google/api/annotations.proto b/seed/postman/grpc/.mock/proto/google/api/annotations.proto new file mode 100644 index 00000000000..efdab3db6ca --- /dev/null +++ b/seed/postman/grpc/.mock/proto/google/api/annotations.proto @@ -0,0 +1,31 @@ +// Copyright 2015 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/api/http.proto"; +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "AnnotationsProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // See `HttpRule`. + HttpRule http = 72295728; +} diff --git a/seed/postman/grpc/.mock/proto/google/api/field_behavior.proto b/seed/postman/grpc/.mock/proto/google/api/field_behavior.proto new file mode 100644 index 00000000000..344cb0b1fc2 --- /dev/null +++ b/seed/postman/grpc/.mock/proto/google/api/field_behavior.proto @@ -0,0 +1,104 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "FieldBehaviorProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.FieldOptions { + // A designation of a specific field behavior (required, output only, etc.) + // in protobuf messages. + // + // Examples: + // + // string name = 1 [(google.api.field_behavior) = REQUIRED]; + // State state = 1 [(google.api.field_behavior) = OUTPUT_ONLY]; + // google.protobuf.Duration ttl = 1 + // [(google.api.field_behavior) = INPUT_ONLY]; + // google.protobuf.Timestamp expire_time = 1 + // [(google.api.field_behavior) = OUTPUT_ONLY, + // (google.api.field_behavior) = IMMUTABLE]; + repeated google.api.FieldBehavior field_behavior = 1052; +} + +// An indicator of the behavior of a given field (for example, that a field +// is required in requests, or given as output but ignored as input). +// This **does not** change the behavior in protocol buffers itself; it only +// denotes the behavior and may affect how API tooling handles the field. +// +// Note: This enum **may** receive new values in the future. +enum FieldBehavior { + // Conventional default for enums. Do not use this. + FIELD_BEHAVIOR_UNSPECIFIED = 0; + + // Specifically denotes a field as optional. + // While all fields in protocol buffers are optional, this may be specified + // for emphasis if appropriate. + OPTIONAL = 1; + + // Denotes a field as required. + // This indicates that the field **must** be provided as part of the request, + // and failure to do so will cause an error (usually `INVALID_ARGUMENT`). + REQUIRED = 2; + + // Denotes a field as output only. + // This indicates that the field is provided in responses, but including the + // field in a request does nothing (the server *must* ignore it and + // *must not* throw an error as a result of the field's presence). + OUTPUT_ONLY = 3; + + // Denotes a field as input only. + // This indicates that the field is provided in requests, and the + // corresponding field is not included in output. + INPUT_ONLY = 4; + + // Denotes a field as immutable. + // This indicates that the field may be set once in a request to create a + // resource, but may not be changed thereafter. + IMMUTABLE = 5; + + // Denotes that a (repeated) field is an unordered list. + // This indicates that the service may provide the elements of the list + // in any arbitrary order, rather than the order the user originally + // provided. Additionally, the list's order may or may not be stable. + UNORDERED_LIST = 6; + + // Denotes that this field returns a non-empty default value if not set. + // This indicates that if the user provides the empty value in a request, + // a non-empty value will be returned. The user will not be aware of what + // non-empty value to expect. + NON_EMPTY_DEFAULT = 7; + + // Denotes that the field in a resource (a message annotated with + // google.api.resource) is used in the resource name to uniquely identify the + // resource. For AIP-compliant APIs, this should only be applied to the + // `name` field on the resource. + // + // This behavior should not be applied to references to other resources within + // the message. + // + // The identifier field of resources often have different field behavior + // depending on the request it is embedded in (e.g. for Create methods name + // is optional and unused, while for Update methods it is required). Instead + // of method-specific annotations, only `IDENTIFIER` is required. + IDENTIFIER = 8; +} diff --git a/seed/postman/grpc/.mock/proto/google/api/http.proto b/seed/postman/grpc/.mock/proto/google/api/http.proto new file mode 100644 index 00000000000..31d867a27d5 --- /dev/null +++ b/seed/postman/grpc/.mock/proto/google/api/http.proto @@ -0,0 +1,379 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "HttpProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Defines the HTTP configuration for an API service. It contains a list of +// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method +// to one or more HTTP REST API methods. +message Http { + // A list of HTTP configuration rules that apply to individual API methods. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated HttpRule rules = 1; + + // When set to true, URL path parameters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // + // The default behavior is to not decode RFC 6570 reserved characters in multi + // segment matches. + bool fully_decode_reserved_expansion = 2; +} + +// # gRPC Transcoding +// +// gRPC Transcoding is a feature for mapping between a gRPC method and one or +// more HTTP REST endpoints. It allows developers to build a single API service +// that supports both gRPC APIs and REST APIs. Many systems, including [Google +// APIs](https://github.com/googleapis/googleapis), +// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC +// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), +// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature +// and use it for large scale production services. +// +// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies +// how different portions of the gRPC request message are mapped to the URL +// path, URL query parameters, and HTTP request body. It also controls how the +// gRPC response message is mapped to the HTTP response body. `HttpRule` is +// typically specified as an `google.api.http` annotation on the gRPC method. +// +// Each mapping specifies a URL path template and an HTTP method. The path +// template may refer to one or more fields in the gRPC request message, as long +// as each field is a non-repeated field with a primitive (non-message) type. +// The path template controls how fields of the request message are mapped to +// the URL path. +// +// Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/{name=messages/*}" +// }; +// } +// } +// message GetMessageRequest { +// string name = 1; // Mapped to URL path. +// } +// message Message { +// string text = 1; // The resource content. +// } +// +// This enables an HTTP REST to gRPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` +// +// Any fields in the request message which are not bound by the path template +// automatically become HTTP query parameters if there is no HTTP request body. +// For example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get:"/v1/messages/{message_id}" +// }; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // Mapped to URL path. +// int64 revision = 2; // Mapped to URL query parameter `revision`. +// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. +// } +// +// This enables a HTTP JSON to RPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | +// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: +// "foo"))` +// +// Note that fields which are mapped to URL query parameters must have a +// primitive type or a repeated primitive type or a non-repeated message type. +// In the case of a repeated type, the parameter can be repeated in the URL +// as `...?param=A¶m=B`. In the case of a message type, each field of the +// message is mapped to a separate parameter, such as +// `...?foo.a=A&foo.b=B&foo.c=C`. +// +// For HTTP methods that allow a request body, the `body` field +// specifies the mapping. Consider a REST update method on the +// message resource collection: +// +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } +// +// The following HTTP JSON to RPC mapping is enabled, where the +// representation of the JSON in the request body is determined by +// protos JSON encoding: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" message { text: "Hi!" })` +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. This enables the following alternative definition of +// the update method: +// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } +// +// +// The following HTTP JSON to RPC mapping is enabled: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" text: "Hi!")` +// +// Note that when using `*` in the body mapping, it is not possible to +// have HTTP parameters, as all fields not bound by the path end in +// the body. This makes this option more rarely used in practice when +// defining REST APIs. The common usage of `*` is in custom methods +// which don't use the URL at all for transferring data. +// +// It is possible to define multiple HTTP methods for one RPC by using +// the `additional_bindings` option. Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } +// +// This enables the following two alternative HTTP JSON to RPC mappings: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` +// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: +// "123456")` +// +// ## Rules for HTTP mapping +// +// 1. Leaf request fields (recursive expansion nested messages in the request +// message) are classified into three categories: +// - Fields referred by the path template. They are passed via the URL path. +// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They +// are passed via the HTTP +// request body. +// - All other fields are passed via the URL query parameters, and the +// parameter name is the field path in the request message. A repeated +// field can be represented as multiple query parameters under the same +// name. +// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL +// query parameter, all fields +// are passed via URL path and HTTP request body. +// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP +// request body, all +// fields are passed via URL path and URL query parameters. +// +// ### Path template syntax +// +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +// +// The syntax `*` matches a single URL path segment. The syntax `**` matches +// zero or more URL path segments, which must be the last part of the URL path +// except the `Verb`. +// +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable +// matches a single path segment, its template may be omitted, e.g. `{var}` +// is equivalent to `{var=*}`. +// +// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` +// contains any reserved character, such characters should be percent-encoded +// before the matching. +// +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path on the client +// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The +// server side does the reverse decoding. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{var}`. +// +// If a variable contains multiple path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path on the +// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. +// The server side does the reverse decoding, except "%2F" and "%2f" are left +// unchanged. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{+var}`. +// +// ## Using gRPC API Service Configuration +// +// gRPC API Service Configuration (service config) is a configuration language +// for configuring a gRPC service to become a user-facing product. The +// service config is simply the YAML representation of the `google.api.Service` +// proto message. +// +// As an alternative to annotating your proto file, you can configure gRPC +// transcoding in your service config YAML files. You do this by specifying a +// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same +// effect as the proto annotation. This can be particularly useful if you +// have a proto that is reused in multiple services. Note that any transcoding +// specified in the service config will override any matching transcoding +// configuration in the proto. +// +// Example: +// +// http: +// rules: +// # Selects a gRPC method and applies HttpRule to it. +// - selector: example.v1.Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} +// +// ## Special notes +// +// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the +// proto to JSON conversion must follow the [proto3 +// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). +// +// While the single segment variable follows the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String +// Expansion, the multi segment variable **does not** follow RFC 6570 Section +// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding +// for multi segment variables. +// +// The path variables **must not** refer to any repeated or mapped field, +// because client libraries are not capable of handling such variable expansion. +// +// The path variables **must not** capture the leading "/" character. The reason +// is that the most common use case "{var}" does not capture the leading "/" +// character. For consistency, all path variables must share the same behavior. +// +// Repeated message fields must not be mapped to URL query parameters, because +// no client library can support such complicated mapping. +// +// If an API needs to use a JSON array for request or response body, it can map +// the request or response body to a repeated field. However, some gRPC +// Transcoding implementations may not support this feature. +message HttpRule { + // Selects a method to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax + // details. + string selector = 1; + + // Determines the URL pattern is matched by this rules. This pattern can be + // used with any of the {get|put|post|delete|patch} methods. A custom method + // can be defined using the 'custom' field. + oneof pattern { + // Maps to HTTP GET. Used for listing and getting information about + // resources. + string get = 2; + + // Maps to HTTP PUT. Used for replacing a resource. + string put = 3; + + // Maps to HTTP POST. Used for creating a resource or performing an action. + string post = 4; + + // Maps to HTTP DELETE. Used for deleting a resource. + string delete = 5; + + // Maps to HTTP PATCH. Used for updating a resource. + string patch = 6; + + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. + CustomHttpPattern custom = 8; + } + + // The name of the request field whose value is mapped to the HTTP request + // body, or `*` for mapping all request fields not captured by the path + // pattern to the HTTP body, or omitted for not having any HTTP request body. + // + // NOTE: the referred field must be present at the top-level of the request + // message type. + string body = 7; + + // Optional. The name of the response field whose value is mapped to the HTTP + // response body. When omitted, the entire response message will be used + // as the HTTP response body. + // + // NOTE: The referred field must be present at the top-level of the response + // message type. + string response_body = 12; + + // Additional HTTP bindings for the selector. Nested bindings must + // not contain an `additional_bindings` field themselves (that is, + // the nesting may only be one level deep). + repeated HttpRule additional_bindings = 11; +} + +// A custom pattern is used for defining custom HTTP verb. +message CustomHttpPattern { + // The name of this custom HTTP verb. + string kind = 1; + + // The path matched by this custom verb. + string path = 2; +} diff --git a/seed/postman/grpc/.mock/proto/user/v1/user.proto b/seed/postman/grpc/.mock/proto/user/v1/user.proto new file mode 100644 index 00000000000..adab9ceefb9 --- /dev/null +++ b/seed/postman/grpc/.mock/proto/user/v1/user.proto @@ -0,0 +1,55 @@ +syntax = "proto3"; + +package user.v1; + +import "google/api/annotations.proto"; +import "google/api/field_behavior.proto"; +import "google/protobuf/struct.proto"; + +option csharp_namespace = "User.V1"; + +message User { + string username = 1 [ + (google.api.field_behavior) = REQUIRED + ]; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateUserRequest { + string username = 1 [ + (google.api.field_behavior) = REQUIRED + ]; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateUserResponse { + User user = 1; +} + +message GetUserRequest { + string username = 1; + string email = 2; + uint32 age = 3; + float weight = 4; +} + +service UserService { + rpc CreateUser(CreateUserRequest) returns (CreateUserResponse) { + option (google.api.http) = { + post: "/users" + body: "*" + }; + } + + rpc GetUser(GetUserRequest) returns (User) { + option (google.api.http) = { + get: "/users" + }; + } +} diff --git a/seed/postman/grpc/collection.json b/seed/postman/grpc/collection.json new file mode 100644 index 00000000000..cabd5e66c82 --- /dev/null +++ b/seed/postman/grpc/collection.json @@ -0,0 +1,94 @@ +{ + "info": { + "name": "Api", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "description": null + }, + "variable": [ + { + "key": "baseUrl", + "value": "", + "type": "string" + } + ], + "auth": null, + "item": [ + { + "_type": "container", + "description": null, + "name": "User", + "item": [ + { + "_type": "endpoint", + "name": "Create User", + "request": { + "description": null, + "url": { + "raw": "{{baseUrl}}/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users" + ], + "query": [], + "variable": [] + }, + "header": [], + "method": "POST", + "auth": null, + "body": { + "mode": "raw", + "raw": "{\n \"username\": \"example\",\n \"email\": \"example\",\n \"age\": 0,\n \"weight\": 0\n}", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "response": [] + }, + { + "_type": "endpoint", + "name": "Get User", + "request": { + "description": null, + "url": { + "raw": "{{baseUrl}}/users?username=&age=&weight=", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users" + ], + "query": [ + { + "key": "username", + "value": "", + "description": null + }, + { + "key": "age", + "value": "", + "description": null + }, + { + "key": "weight", + "value": "", + "description": null + } + ], + "variable": [] + }, + "header": [], + "method": "GET", + "auth": null, + "body": null + }, + "response": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/seed/postman/grpc/snippet-templates.json b/seed/postman/grpc/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/postman/grpc/snippet.json b/seed/postman/grpc/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/pydantic/grpc-proto/.github/workflows/ci.yml b/seed/pydantic/grpc-proto/.github/workflows/ci.yml new file mode 100644 index 00000000000..17b9d4fa0e8 --- /dev/null +++ b/seed/pydantic/grpc-proto/.github/workflows/ci.yml @@ -0,0 +1,61 @@ +name: ci + +on: [push] +jobs: + compile: + runs-on: ubuntu-20.04 + steps: + - name: Checkout repo + uses: actions/checkout@v3 + - name: Set up python + uses: actions/setup-python@v4 + with: + python-version: 3.8 + - name: Bootstrap poetry + run: | + curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 + - name: Install dependencies + run: poetry install + - name: Compile + run: poetry run mypy . + test: + runs-on: ubuntu-20.04 + steps: + - name: Checkout repo + uses: actions/checkout@v3 + - name: Set up python + uses: actions/setup-python@v4 + with: + python-version: 3.8 + - name: Bootstrap poetry + run: | + curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 + - name: Install dependencies + run: poetry install + + - name: Test + run: poetry run pytest ./tests/custom/ + + publish: + needs: [compile, test] + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + runs-on: ubuntu-20.04 + steps: + - name: Checkout repo + uses: actions/checkout@v3 + - name: Set up python + uses: actions/setup-python@v4 + with: + python-version: 3.8 + - name: Bootstrap poetry + run: | + curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 + - name: Install dependencies + run: poetry install + - name: Publish to pypi + run: | + poetry config repositories.remote + poetry --no-interaction -v publish --build --repository remote --username "$" --password "$" + env: + : ${{ secrets. }} + : ${{ secrets. }} diff --git a/seed/pydantic/grpc-proto/.gitignore b/seed/pydantic/grpc-proto/.gitignore new file mode 100644 index 00000000000..42cb863501e --- /dev/null +++ b/seed/pydantic/grpc-proto/.gitignore @@ -0,0 +1,4 @@ +dist/ +.mypy_cache/ +__pycache__/ +poetry.toml diff --git a/seed/pydantic/grpc-proto/.mock/fern.config.json b/seed/pydantic/grpc-proto/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/pydantic/grpc-proto/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/pydantic/grpc-proto/.mock/generators.yml b/seed/pydantic/grpc-proto/.mock/generators.yml new file mode 100644 index 00000000000..902c4f50168 --- /dev/null +++ b/seed/pydantic/grpc-proto/.mock/generators.yml @@ -0,0 +1,5 @@ +api: + - proto: + root: proto + target: proto/user/v1/user.proto + local-generation: true \ No newline at end of file diff --git a/seed/pydantic/grpc-proto/.mock/proto/google/api/annotations.proto b/seed/pydantic/grpc-proto/.mock/proto/google/api/annotations.proto new file mode 100644 index 00000000000..8ff42098404 --- /dev/null +++ b/seed/pydantic/grpc-proto/.mock/proto/google/api/annotations.proto @@ -0,0 +1,31 @@ +// Copyright 2015 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/api/http.proto"; +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "AnnotationsProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // See `HttpRule`. + HttpRule http = 72295728; +} \ No newline at end of file diff --git a/seed/pydantic/grpc-proto/.mock/proto/google/api/field_behavior.proto b/seed/pydantic/grpc-proto/.mock/proto/google/api/field_behavior.proto new file mode 100644 index 00000000000..128799c558d --- /dev/null +++ b/seed/pydantic/grpc-proto/.mock/proto/google/api/field_behavior.proto @@ -0,0 +1,104 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "FieldBehaviorProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.FieldOptions { + // A designation of a specific field behavior (required, output only, etc.) + // in protobuf messages. + // + // Examples: + // + // string name = 1 [(google.api.field_behavior) = REQUIRED]; + // State state = 1 [(google.api.field_behavior) = OUTPUT_ONLY]; + // google.protobuf.Duration ttl = 1 + // [(google.api.field_behavior) = INPUT_ONLY]; + // google.protobuf.Timestamp expire_time = 1 + // [(google.api.field_behavior) = OUTPUT_ONLY, + // (google.api.field_behavior) = IMMUTABLE]; + repeated google.api.FieldBehavior field_behavior = 1052; +} + +// An indicator of the behavior of a given field (for example, that a field +// is required in requests, or given as output but ignored as input). +// This **does not** change the behavior in protocol buffers itself; it only +// denotes the behavior and may affect how API tooling handles the field. +// +// Note: This enum **may** receive new values in the future. +enum FieldBehavior { + // Conventional default for enums. Do not use this. + FIELD_BEHAVIOR_UNSPECIFIED = 0; + + // Specifically denotes a field as optional. + // While all fields in protocol buffers are optional, this may be specified + // for emphasis if appropriate. + OPTIONAL = 1; + + // Denotes a field as required. + // This indicates that the field **must** be provided as part of the request, + // and failure to do so will cause an error (usually `INVALID_ARGUMENT`). + REQUIRED = 2; + + // Denotes a field as output only. + // This indicates that the field is provided in responses, but including the + // field in a request does nothing (the server *must* ignore it and + // *must not* throw an error as a result of the field's presence). + OUTPUT_ONLY = 3; + + // Denotes a field as input only. + // This indicates that the field is provided in requests, and the + // corresponding field is not included in output. + INPUT_ONLY = 4; + + // Denotes a field as immutable. + // This indicates that the field may be set once in a request to create a + // resource, but may not be changed thereafter. + IMMUTABLE = 5; + + // Denotes that a (repeated) field is an unordered list. + // This indicates that the service may provide the elements of the list + // in any arbitrary order, rather than the order the user originally + // provided. Additionally, the list's order may or may not be stable. + UNORDERED_LIST = 6; + + // Denotes that this field returns a non-empty default value if not set. + // This indicates that if the user provides the empty value in a request, + // a non-empty value will be returned. The user will not be aware of what + // non-empty value to expect. + NON_EMPTY_DEFAULT = 7; + + // Denotes that the field in a resource (a message annotated with + // google.api.resource) is used in the resource name to uniquely identify the + // resource. For AIP-compliant APIs, this should only be applied to the + // `name` field on the resource. + // + // This behavior should not be applied to references to other resources within + // the message. + // + // The identifier field of resources often have different field behavior + // depending on the request it is embedded in (e.g. for Create methods name + // is optional and unused, while for Update methods it is required). Instead + // of method-specific annotations, only `IDENTIFIER` is required. + IDENTIFIER = 8; +} \ No newline at end of file diff --git a/seed/pydantic/grpc-proto/.mock/proto/google/api/http.proto b/seed/pydantic/grpc-proto/.mock/proto/google/api/http.proto new file mode 100644 index 00000000000..c8392381eb9 --- /dev/null +++ b/seed/pydantic/grpc-proto/.mock/proto/google/api/http.proto @@ -0,0 +1,379 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "HttpProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Defines the HTTP configuration for an API service. It contains a list of +// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method +// to one or more HTTP REST API methods. +message Http { + // A list of HTTP configuration rules that apply to individual API methods. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated HttpRule rules = 1; + + // When set to true, URL path parameters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // + // The default behavior is to not decode RFC 6570 reserved characters in multi + // segment matches. + bool fully_decode_reserved_expansion = 2; +} + +// # gRPC Transcoding +// +// gRPC Transcoding is a feature for mapping between a gRPC method and one or +// more HTTP REST endpoints. It allows developers to build a single API service +// that supports both gRPC APIs and REST APIs. Many systems, including [Google +// APIs](https://github.com/googleapis/googleapis), +// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC +// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), +// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature +// and use it for large scale production services. +// +// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies +// how different portions of the gRPC request message are mapped to the URL +// path, URL query parameters, and HTTP request body. It also controls how the +// gRPC response message is mapped to the HTTP response body. `HttpRule` is +// typically specified as an `google.api.http` annotation on the gRPC method. +// +// Each mapping specifies a URL path template and an HTTP method. The path +// template may refer to one or more fields in the gRPC request message, as long +// as each field is a non-repeated field with a primitive (non-message) type. +// The path template controls how fields of the request message are mapped to +// the URL path. +// +// Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/{name=messages/*}" +// }; +// } +// } +// message GetMessageRequest { +// string name = 1; // Mapped to URL path. +// } +// message Message { +// string text = 1; // The resource content. +// } +// +// This enables an HTTP REST to gRPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` +// +// Any fields in the request message which are not bound by the path template +// automatically become HTTP query parameters if there is no HTTP request body. +// For example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get:"/v1/messages/{message_id}" +// }; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // Mapped to URL path. +// int64 revision = 2; // Mapped to URL query parameter `revision`. +// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. +// } +// +// This enables a HTTP JSON to RPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | +// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: +// "foo"))` +// +// Note that fields which are mapped to URL query parameters must have a +// primitive type or a repeated primitive type or a non-repeated message type. +// In the case of a repeated type, the parameter can be repeated in the URL +// as `...?param=A¶m=B`. In the case of a message type, each field of the +// message is mapped to a separate parameter, such as +// `...?foo.a=A&foo.b=B&foo.c=C`. +// +// For HTTP methods that allow a request body, the `body` field +// specifies the mapping. Consider a REST update method on the +// message resource collection: +// +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } +// +// The following HTTP JSON to RPC mapping is enabled, where the +// representation of the JSON in the request body is determined by +// protos JSON encoding: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" message { text: "Hi!" })` +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. This enables the following alternative definition of +// the update method: +// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } +// +// +// The following HTTP JSON to RPC mapping is enabled: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" text: "Hi!")` +// +// Note that when using `*` in the body mapping, it is not possible to +// have HTTP parameters, as all fields not bound by the path end in +// the body. This makes this option more rarely used in practice when +// defining REST APIs. The common usage of `*` is in custom methods +// which don't use the URL at all for transferring data. +// +// It is possible to define multiple HTTP methods for one RPC by using +// the `additional_bindings` option. Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } +// +// This enables the following two alternative HTTP JSON to RPC mappings: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` +// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: +// "123456")` +// +// ## Rules for HTTP mapping +// +// 1. Leaf request fields (recursive expansion nested messages in the request +// message) are classified into three categories: +// - Fields referred by the path template. They are passed via the URL path. +// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They +// are passed via the HTTP +// request body. +// - All other fields are passed via the URL query parameters, and the +// parameter name is the field path in the request message. A repeated +// field can be represented as multiple query parameters under the same +// name. +// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL +// query parameter, all fields +// are passed via URL path and HTTP request body. +// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP +// request body, all +// fields are passed via URL path and URL query parameters. +// +// ### Path template syntax +// +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +// +// The syntax `*` matches a single URL path segment. The syntax `**` matches +// zero or more URL path segments, which must be the last part of the URL path +// except the `Verb`. +// +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable +// matches a single path segment, its template may be omitted, e.g. `{var}` +// is equivalent to `{var=*}`. +// +// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` +// contains any reserved character, such characters should be percent-encoded +// before the matching. +// +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path on the client +// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The +// server side does the reverse decoding. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{var}`. +// +// If a variable contains multiple path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path on the +// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. +// The server side does the reverse decoding, except "%2F" and "%2f" are left +// unchanged. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{+var}`. +// +// ## Using gRPC API Service Configuration +// +// gRPC API Service Configuration (service config) is a configuration language +// for configuring a gRPC service to become a user-facing product. The +// service config is simply the YAML representation of the `google.api.Service` +// proto message. +// +// As an alternative to annotating your proto file, you can configure gRPC +// transcoding in your service config YAML files. You do this by specifying a +// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same +// effect as the proto annotation. This can be particularly useful if you +// have a proto that is reused in multiple services. Note that any transcoding +// specified in the service config will override any matching transcoding +// configuration in the proto. +// +// Example: +// +// http: +// rules: +// # Selects a gRPC method and applies HttpRule to it. +// - selector: example.v1.Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} +// +// ## Special notes +// +// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the +// proto to JSON conversion must follow the [proto3 +// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). +// +// While the single segment variable follows the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String +// Expansion, the multi segment variable **does not** follow RFC 6570 Section +// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding +// for multi segment variables. +// +// The path variables **must not** refer to any repeated or mapped field, +// because client libraries are not capable of handling such variable expansion. +// +// The path variables **must not** capture the leading "/" character. The reason +// is that the most common use case "{var}" does not capture the leading "/" +// character. For consistency, all path variables must share the same behavior. +// +// Repeated message fields must not be mapped to URL query parameters, because +// no client library can support such complicated mapping. +// +// If an API needs to use a JSON array for request or response body, it can map +// the request or response body to a repeated field. However, some gRPC +// Transcoding implementations may not support this feature. +message HttpRule { + // Selects a method to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax + // details. + string selector = 1; + + // Determines the URL pattern is matched by this rules. This pattern can be + // used with any of the {get|put|post|delete|patch} methods. A custom method + // can be defined using the 'custom' field. + oneof pattern { + // Maps to HTTP GET. Used for listing and getting information about + // resources. + string get = 2; + + // Maps to HTTP PUT. Used for replacing a resource. + string put = 3; + + // Maps to HTTP POST. Used for creating a resource or performing an action. + string post = 4; + + // Maps to HTTP DELETE. Used for deleting a resource. + string delete = 5; + + // Maps to HTTP PATCH. Used for updating a resource. + string patch = 6; + + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. + CustomHttpPattern custom = 8; + } + + // The name of the request field whose value is mapped to the HTTP request + // body, or `*` for mapping all request fields not captured by the path + // pattern to the HTTP body, or omitted for not having any HTTP request body. + // + // NOTE: the referred field must be present at the top-level of the request + // message type. + string body = 7; + + // Optional. The name of the response field whose value is mapped to the HTTP + // response body. When omitted, the entire response message will be used + // as the HTTP response body. + // + // NOTE: The referred field must be present at the top-level of the response + // message type. + string response_body = 12; + + // Additional HTTP bindings for the selector. Nested bindings must + // not contain an `additional_bindings` field themselves (that is, + // the nesting may only be one level deep). + repeated HttpRule additional_bindings = 11; +} + +// A custom pattern is used for defining custom HTTP verb. +message CustomHttpPattern { + // The name of this custom HTTP verb. + string kind = 1; + + // The path matched by this custom verb. + string path = 2; +} \ No newline at end of file diff --git a/seed/pydantic/grpc-proto/.mock/proto/user/v1/user.proto b/seed/pydantic/grpc-proto/.mock/proto/user/v1/user.proto new file mode 100644 index 00000000000..a3ad0195bd4 --- /dev/null +++ b/seed/pydantic/grpc-proto/.mock/proto/user/v1/user.proto @@ -0,0 +1,39 @@ +syntax = "proto3"; + +package user.v1; + +import "google/api/annotations.proto"; +import "google/api/field_behavior.proto"; +import "google/protobuf/struct.proto"; + +option go_package = "user/v1"; +option csharp_namespace = "User.V1"; + +message UserModel { + string username = 1; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateRequest { + string username = 1; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateResponse { + UserModel user = 1; +} + +service User { + rpc Create(CreateRequest) returns (CreateResponse) { + option (google.api.http) = { + post: "/users" + body: "*" + }; + } +} \ No newline at end of file diff --git a/seed/pydantic/grpc-proto/README.md b/seed/pydantic/grpc-proto/README.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/pydantic/grpc-proto/pyproject.toml b/seed/pydantic/grpc-proto/pyproject.toml new file mode 100644 index 00000000000..2aca44c8b37 --- /dev/null +++ b/seed/pydantic/grpc-proto/pyproject.toml @@ -0,0 +1,55 @@ +[tool.poetry] +name = "fern_grpc-proto" +version = "0.0.1" +description = "" +readme = "README.md" +authors = [] +keywords = [] + +classifiers = [ + "Intended Audience :: Developers", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Operating System :: OS Independent", + "Operating System :: POSIX", + "Operating System :: MacOS", + "Operating System :: POSIX :: Linux", + "Operating System :: Microsoft :: Windows", + "Topic :: Software Development :: Libraries :: Python Modules", + "Typing :: Typed" +] +packages = [ + { include = "seed/api", from = "src"} +] + +[project.urls] +Repository = 'https://github.com/grpc-proto/fern' + +[tool.poetry.dependencies] +python = "^3.8" +pydantic = ">= 1.9.2" +pydantic-core = "^2.18.2" + +[tool.poetry.dev-dependencies] +mypy = "1.0.1" +pytest = "^7.4.0" +pytest-asyncio = "^0.23.5" +python-dateutil = "^2.9.0" +types-python-dateutil = "^2.9.0.20240316" + +[tool.pytest.ini_options] +testpaths = [ "tests" ] +asyncio_mode = "auto" + +[tool.mypy] +plugins = ["pydantic.mypy"] + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/seed/pydantic/grpc-proto/snippet-templates.json b/seed/pydantic/grpc-proto/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/pydantic/grpc-proto/snippet.json b/seed/pydantic/grpc-proto/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/pydantic/grpc-proto/src/seed/api/__init__.py b/seed/pydantic/grpc-proto/src/seed/api/__init__.py new file mode 100644 index 00000000000..243a9961e67 --- /dev/null +++ b/seed/pydantic/grpc-proto/src/seed/api/__init__.py @@ -0,0 +1,6 @@ +# This file was auto-generated by Fern from our API Definition. + +from .create_response import CreateResponse +from .user_model import UserModel + +__all__ = ["CreateResponse", "UserModel"] diff --git a/seed/pydantic/grpc-proto/src/seed/api/core/__init__.py b/seed/pydantic/grpc-proto/src/seed/api/core/__init__.py new file mode 100644 index 00000000000..85460274fba --- /dev/null +++ b/seed/pydantic/grpc-proto/src/seed/api/core/__init__.py @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +from .datetime_utils import serialize_datetime +from .pydantic_utilities import ( + IS_PYDANTIC_V2, + UniversalBaseModel, + UniversalRootModel, + deep_union_pydantic_dicts, + parse_obj_as, + universal_field_validator, + universal_root_validator, + update_forward_refs, +) +from .serialization import FieldMetadata + +__all__ = [ + "FieldMetadata", + "IS_PYDANTIC_V2", + "UniversalBaseModel", + "UniversalRootModel", + "deep_union_pydantic_dicts", + "parse_obj_as", + "serialize_datetime", + "universal_field_validator", + "universal_root_validator", + "update_forward_refs", +] diff --git a/seed/pydantic/grpc-proto/src/seed/api/core/datetime_utils.py b/seed/pydantic/grpc-proto/src/seed/api/core/datetime_utils.py new file mode 100644 index 00000000000..7c9864a944c --- /dev/null +++ b/seed/pydantic/grpc-proto/src/seed/api/core/datetime_utils.py @@ -0,0 +1,28 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt + + +def serialize_datetime(v: dt.datetime) -> str: + """ + Serialize a datetime including timezone info. + + Uses the timezone info provided if present, otherwise uses the current runtime's timezone info. + + UTC datetimes end in "Z" while all other timezones are represented as offset from UTC, e.g. +05:00. + """ + + def _serialize_zoned_datetime(v: dt.datetime) -> str: + if v.tzinfo is not None and v.tzinfo.tzname(None) == dt.timezone.utc.tzname(None): + # UTC is a special case where we use "Z" at the end instead of "+00:00" + return v.isoformat().replace("+00:00", "Z") + else: + # Delegate to the typical +/- offset format + return v.isoformat() + + if v.tzinfo is not None: + return _serialize_zoned_datetime(v) + else: + local_tz = dt.datetime.now().astimezone().tzinfo + localized_dt = v.replace(tzinfo=local_tz) + return _serialize_zoned_datetime(localized_dt) diff --git a/seed/pydantic/grpc-proto/src/seed/api/core/pydantic_utilities.py b/seed/pydantic/grpc-proto/src/seed/api/core/pydantic_utilities.py new file mode 100644 index 00000000000..f95015f89bd --- /dev/null +++ b/seed/pydantic/grpc-proto/src/seed/api/core/pydantic_utilities.py @@ -0,0 +1,179 @@ +# This file was auto-generated by Fern from our API Definition. + +# nopycln: file +import datetime as dt +import typing +from collections import defaultdict +from functools import wraps + +import pydantic + +from .datetime_utils import serialize_datetime + +IS_PYDANTIC_V2 = pydantic.VERSION.startswith("2.") + +if IS_PYDANTIC_V2: + # isort will try to reformat the comments on these imports, which breaks mypy + # isort: off + from pydantic.v1.datetime_parse import ( # type: ignore # pyright: ignore[reportMissingImports] # Pydantic v2 + parse_date as parse_date, + ) + from pydantic.v1.datetime_parse import ( # pyright: ignore[reportMissingImports] # Pydantic v2 + parse_datetime as parse_datetime, + ) + from pydantic.v1.json import ( # type: ignore # pyright: ignore[reportMissingImports] # Pydantic v2 + ENCODERS_BY_TYPE as encoders_by_type, + ) + from pydantic.v1.typing import ( # type: ignore # pyright: ignore[reportMissingImports] # Pydantic v2 + get_args as get_args, + ) + from pydantic.v1.typing import get_origin as get_origin # pyright: ignore[reportMissingImports] # Pydantic v2 + from pydantic.v1.typing import ( # pyright: ignore[reportMissingImports] # Pydantic v2 + is_literal_type as is_literal_type, + ) + from pydantic.v1.typing import is_union as is_union # pyright: ignore[reportMissingImports] # Pydantic v2 + from pydantic.v1.fields import ModelField as ModelField # type: ignore # pyright: ignore[reportMissingImports] # Pydantic v2 +else: + from pydantic.datetime_parse import parse_date as parse_date # type: ignore # Pydantic v1 + from pydantic.datetime_parse import parse_datetime as parse_datetime # type: ignore # Pydantic v1 + from pydantic.fields import ModelField as ModelField # type: ignore # Pydantic v1 + from pydantic.json import ENCODERS_BY_TYPE as encoders_by_type # type: ignore # Pydantic v1 + from pydantic.typing import get_args as get_args # type: ignore # Pydantic v1 + from pydantic.typing import get_origin as get_origin # type: ignore # Pydantic v1 + from pydantic.typing import is_literal_type as is_literal_type # type: ignore # Pydantic v1 + from pydantic.typing import is_union as is_union # type: ignore # Pydantic v1 + + # isort: on + + +T = typing.TypeVar("T") +Model = typing.TypeVar("Model", bound=pydantic.BaseModel) + + +def deep_union_pydantic_dicts( + source: typing.Dict[str, typing.Any], destination: typing.Dict[str, typing.Any] +) -> typing.Dict[str, typing.Any]: + for key, value in source.items(): + if isinstance(value, dict): + node = destination.setdefault(key, {}) + deep_union_pydantic_dicts(value, node) + else: + destination[key] = value + + return destination + + +def parse_obj_as(type_: typing.Type[T], object_: typing.Any) -> T: + if IS_PYDANTIC_V2: + adapter = pydantic.TypeAdapter(type_) # type: ignore # Pydantic v2 + return adapter.validate_python(object_) + else: + return pydantic.parse_obj_as(type_, object_) + + +def to_jsonable_with_fallback( + obj: typing.Any, fallback_serializer: typing.Callable[[typing.Any], typing.Any] +) -> typing.Any: + if IS_PYDANTIC_V2: + from pydantic_core import to_jsonable_python + + return to_jsonable_python(obj, fallback=fallback_serializer) + else: + return fallback_serializer(obj) + + +class UniversalBaseModel(pydantic.BaseModel): + class Config: + populate_by_name = True + smart_union = True + allow_population_by_field_name = True + json_encoders = {dt.datetime: serialize_datetime} + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = {"by_alias": True, "exclude_unset": True, **kwargs} + if IS_PYDANTIC_V2: + return super().model_dump_json(**kwargs_with_defaults) # type: ignore # Pydantic v2 + else: + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + kwargs_with_defaults_exclude_unset: typing.Any = {"by_alias": True, "exclude_unset": True, **kwargs} + kwargs_with_defaults_exclude_none: typing.Any = {"by_alias": True, "exclude_none": True, **kwargs} + + if IS_PYDANTIC_V2: + return deep_union_pydantic_dicts( + super().model_dump(**kwargs_with_defaults_exclude_unset), # type: ignore # Pydantic v2 + super().model_dump(**kwargs_with_defaults_exclude_none), # type: ignore # Pydantic v2 + ) + else: + return deep_union_pydantic_dicts( + super().dict(**kwargs_with_defaults_exclude_unset), super().dict(**kwargs_with_defaults_exclude_none) + ) + + +UniversalRootModel: typing.Type[pydantic.BaseModel] +if IS_PYDANTIC_V2: + + class V2RootModel(UniversalBaseModel, pydantic.RootModel): # type: ignore # Pydantic v2 + pass + + UniversalRootModel = V2RootModel +else: + UniversalRootModel = UniversalBaseModel + + +def encode_by_type(o: typing.Any) -> typing.Any: + encoders_by_class_tuples: typing.Dict[ + typing.Callable[[typing.Any], typing.Any], typing.Tuple[typing.Any, ...] + ] = defaultdict(tuple) + for type_, encoder in encoders_by_type.items(): + encoders_by_class_tuples[encoder] += (type_,) + + if type(o) in encoders_by_type: + return encoders_by_type[type(o)](o) + for encoder, classes_tuple in encoders_by_class_tuples.items(): + if isinstance(o, classes_tuple): + return encoder(o) + + +def update_forward_refs(model: typing.Type["Model"], **localns: typing.Any) -> None: + if IS_PYDANTIC_V2: + model.model_rebuild(force=True, raise_errors=False) # type: ignore # Pydantic v2 + else: + model.update_forward_refs(**localns) + + +# Mirrors Pydantic's internal typing +AnyCallable = typing.Callable[..., typing.Any] + + +def universal_root_validator(pre: bool = False) -> typing.Callable[[AnyCallable], AnyCallable]: + def decorator(func: AnyCallable) -> AnyCallable: + @wraps(func) + def validate(*args: typing.Any, **kwargs: typing.Any) -> AnyCallable: + if IS_PYDANTIC_V2: + wrapped_func = pydantic.model_validator("before" if pre else "after")(func) # type: ignore # Pydantic v2 + else: + wrapped_func = pydantic.root_validator(pre=pre)(func) # type: ignore # Pydantic v1 + + return wrapped_func(*args, **kwargs) + + return validate + + return decorator + + +def universal_field_validator(field_name: str, pre: bool = False) -> typing.Callable[[AnyCallable], AnyCallable]: + def decorator(func: AnyCallable) -> AnyCallable: + @wraps(func) + def validate(*args: typing.Any, **kwargs: typing.Any) -> AnyCallable: + if IS_PYDANTIC_V2: + wrapped_func = pydantic.field_validator(field_name, mode="before" if pre else "after")(func) # type: ignore # Pydantic v2 + else: + wrapped_func = pydantic.validator(field_name, pre=pre)(func) + + return wrapped_func(*args, **kwargs) + + return validate + + return decorator diff --git a/seed/pydantic/grpc-proto/src/seed/api/core/serialization.py b/seed/pydantic/grpc-proto/src/seed/api/core/serialization.py new file mode 100644 index 00000000000..8ad5cf8125f --- /dev/null +++ b/seed/pydantic/grpc-proto/src/seed/api/core/serialization.py @@ -0,0 +1,167 @@ +# This file was auto-generated by Fern from our API Definition. + +import collections +import typing + +import typing_extensions + + +class FieldMetadata: + """ + Metadata class used to annotate fields to provide additional information. + + Example: + class MyDict(TypedDict): + field: typing.Annotated[str, FieldMetadata(alias="field_name")] + + Will serialize: `{"field": "value"}` + To: `{"field_name": "value"}` + """ + + alias: str + + def __init__(self, *, alias: str) -> None: + self.alias = alias + + +def convert_and_respect_annotation_metadata( + *, object_: typing.Any, annotation: typing.Any, inner_type: typing.Optional[typing.Any] = None +) -> typing.Any: + """ + Respect the metadata annotations on a field, such as aliasing. This function effectively + manipulates the dict-form of an object to respect the metadata annotations. This is primarily used for + TypedDicts, which cannot support aliasing out of the box, and can be extended for additional + utilities, such as defaults. + + Parameters + ---------- + object_ : typing.Any + + annotation : type + The type we're looking to apply typing annotations from + + inner_type : typing.Optional[type] + + Returns + ------- + typing.Any + """ + + if object_ is None: + return None + if inner_type is None: + inner_type = annotation + + clean_type = _remove_annotations(inner_type) + if typing_extensions.is_typeddict(clean_type) and isinstance(object_, typing.Mapping): + return _convert_typeddict(object_, clean_type) + + if ( + # If you're iterating on a string, do not bother to coerce it to a sequence. + (not isinstance(object_, str)) + and ( + ( + ( + typing_extensions.get_origin(clean_type) == typing.List + or typing_extensions.get_origin(clean_type) == list + or clean_type == typing.List + ) + and isinstance(object_, typing.List) + ) + or ( + ( + typing_extensions.get_origin(clean_type) == typing.Set + or typing_extensions.get_origin(clean_type) == set + or clean_type == typing.Set + ) + and isinstance(object_, typing.Set) + ) + or ( + ( + typing_extensions.get_origin(clean_type) == typing.Sequence + or typing_extensions.get_origin(clean_type) == collections.abc.Sequence + or clean_type == typing.Sequence + ) + and isinstance(object_, typing.Sequence) + ) + ) + ): + inner_type = typing_extensions.get_args(clean_type)[0] + return [ + convert_and_respect_annotation_metadata(object_=item, annotation=annotation, inner_type=inner_type) + for item in object_ + ] + + if typing_extensions.get_origin(clean_type) == typing.Union: + # We should be able to ~relatively~ safely try to convert keys against all + # member types in the union, the edge case here is if one member aliases a field + # of the same name to a different name from another member + # Or if another member aliases a field of the same name that another member does not. + for member in typing_extensions.get_args(clean_type): + object_ = convert_and_respect_annotation_metadata(object_=object_, annotation=annotation, inner_type=member) + return object_ + + annotated_type = _get_annotation(annotation) + if annotated_type is None: + return object_ + + # If the object is not a TypedDict, a Union, or other container (list, set, sequence, etc.) + # Then we can safely call it on the recursive conversion. + return object_ + + +def _convert_typeddict(object_: typing.Mapping[str, object], expected_type: typing.Any) -> typing.Mapping[str, object]: + converted_object: typing.Dict[str, object] = {} + annotations = typing_extensions.get_type_hints(expected_type, include_extras=True) + for key, value in object_.items(): + type_ = annotations.get(key) + if type_ is None: + converted_object[key] = value + else: + converted_object[_alias_key(key, type_)] = convert_and_respect_annotation_metadata( + object_=value, annotation=type_ + ) + return converted_object + + +def _get_annotation(type_: typing.Any) -> typing.Optional[typing.Any]: + maybe_annotated_type = typing_extensions.get_origin(type_) + if maybe_annotated_type is None: + return None + + if maybe_annotated_type == typing_extensions.NotRequired: + type_ = typing_extensions.get_args(type_)[0] + maybe_annotated_type = typing_extensions.get_origin(type_) + + if maybe_annotated_type == typing_extensions.Annotated: + return type_ + + return None + + +def _remove_annotations(type_: typing.Any) -> typing.Any: + maybe_annotated_type = typing_extensions.get_origin(type_) + if maybe_annotated_type is None: + return type_ + + if maybe_annotated_type == typing_extensions.NotRequired: + return _remove_annotations(typing_extensions.get_args(type_)[0]) + + if maybe_annotated_type == typing_extensions.Annotated: + return _remove_annotations(typing_extensions.get_args(type_)[0]) + + return type_ + + +def _alias_key(key: str, type_: typing.Any) -> str: + maybe_annotated_type = _get_annotation(type_) + + if maybe_annotated_type is not None: + # The actual annotations are 1 onward, the first is the annotated type + annotations = typing_extensions.get_args(maybe_annotated_type)[1:] + + for annotation in annotations: + if isinstance(annotation, FieldMetadata) and annotation.alias is not None: + return annotation.alias + + return key diff --git a/seed/pydantic/grpc-proto/src/seed/api/create_response.py b/seed/pydantic/grpc-proto/src/seed/api/create_response.py new file mode 100644 index 00000000000..632016d66e4 --- /dev/null +++ b/seed/pydantic/grpc-proto/src/seed/api/create_response.py @@ -0,0 +1,19 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic + +from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .user_model import UserModel + + +class CreateResponse(UniversalBaseModel): + user: typing.Optional[UserModel] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 + else: + + class Config: + extra = pydantic.Extra.allow diff --git a/seed/pydantic/grpc-proto/src/seed/api/py.typed b/seed/pydantic/grpc-proto/src/seed/api/py.typed new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/pydantic/grpc-proto/src/seed/api/user_model.py b/seed/pydantic/grpc-proto/src/seed/api/user_model.py new file mode 100644 index 00000000000..c3bea832343 --- /dev/null +++ b/seed/pydantic/grpc-proto/src/seed/api/user_model.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic + +from .core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class UserModel(UniversalBaseModel): + username: typing.Optional[str] = None + email: typing.Optional[str] = None + age: typing.Optional[int] = None + weight: typing.Optional[float] = None + metadata: typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 + else: + + class Config: + extra = pydantic.Extra.allow diff --git a/seed/pydantic/grpc-proto/tests/custom/test_client.py b/seed/pydantic/grpc-proto/tests/custom/test_client.py new file mode 100644 index 00000000000..60a58e64c27 --- /dev/null +++ b/seed/pydantic/grpc-proto/tests/custom/test_client.py @@ -0,0 +1,6 @@ +import pytest + +# Get started with writing tests with pytest at https://docs.pytest.org +@pytest.mark.skip(reason="Unimplemented") +def test_client() -> None: + assert True == True diff --git a/seed/pydantic/grpc/.github/workflows/ci.yml b/seed/pydantic/grpc/.github/workflows/ci.yml new file mode 100644 index 00000000000..17b9d4fa0e8 --- /dev/null +++ b/seed/pydantic/grpc/.github/workflows/ci.yml @@ -0,0 +1,61 @@ +name: ci + +on: [push] +jobs: + compile: + runs-on: ubuntu-20.04 + steps: + - name: Checkout repo + uses: actions/checkout@v3 + - name: Set up python + uses: actions/setup-python@v4 + with: + python-version: 3.8 + - name: Bootstrap poetry + run: | + curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 + - name: Install dependencies + run: poetry install + - name: Compile + run: poetry run mypy . + test: + runs-on: ubuntu-20.04 + steps: + - name: Checkout repo + uses: actions/checkout@v3 + - name: Set up python + uses: actions/setup-python@v4 + with: + python-version: 3.8 + - name: Bootstrap poetry + run: | + curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 + - name: Install dependencies + run: poetry install + + - name: Test + run: poetry run pytest ./tests/custom/ + + publish: + needs: [compile, test] + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + runs-on: ubuntu-20.04 + steps: + - name: Checkout repo + uses: actions/checkout@v3 + - name: Set up python + uses: actions/setup-python@v4 + with: + python-version: 3.8 + - name: Bootstrap poetry + run: | + curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 + - name: Install dependencies + run: poetry install + - name: Publish to pypi + run: | + poetry config repositories.remote + poetry --no-interaction -v publish --build --repository remote --username "$" --password "$" + env: + : ${{ secrets. }} + : ${{ secrets. }} diff --git a/seed/pydantic/grpc/.gitignore b/seed/pydantic/grpc/.gitignore new file mode 100644 index 00000000000..42cb863501e --- /dev/null +++ b/seed/pydantic/grpc/.gitignore @@ -0,0 +1,4 @@ +dist/ +.mypy_cache/ +__pycache__/ +poetry.toml diff --git a/seed/pydantic/grpc/.mock/definition/api.yml b/seed/pydantic/grpc/.mock/definition/api.yml new file mode 100644 index 00000000000..15a9f823724 --- /dev/null +++ b/seed/pydantic/grpc/.mock/definition/api.yml @@ -0,0 +1,4 @@ +name: api + +error-discrimination: + strategy: status-code \ No newline at end of file diff --git a/seed/pydantic/grpc/.mock/definition/user.yml b/seed/pydantic/grpc/.mock/definition/user.yml new file mode 100644 index 00000000000..80223300afc --- /dev/null +++ b/seed/pydantic/grpc/.mock/definition/user.yml @@ -0,0 +1,61 @@ +types: + Metadata: + type: map> + encoding: + proto: + type: google.protobuf.Struct + + MetadataValue: + discriminated: false + union: + - double + - string + - boolean + - list + encoding: + proto: + type: google.protobuf.Value + + User: + properties: + id: string + username: string + email: optional + age: optional + weight: optional + metadata: optional + + CreateUserResponse: + properties: + user: User + +service: + auth: false + base-path: / + transport: + grpc: + service-name: UserService + endpoints: + createUser: + method: POST + path: /users + request: + name: CreateUserRequest + body: + properties: + username: string + email: optional + age: optional + weight: optional + response: CreateUserResponse + + getUser: + method: GET + path: /users + request: + name: GetUserRequest + query-parameters: + username: optional + age: optional + weight: optional + response: User diff --git a/seed/pydantic/grpc/.mock/fern.config.json b/seed/pydantic/grpc/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/pydantic/grpc/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/pydantic/grpc/.mock/generators.yml b/seed/pydantic/grpc/.mock/generators.yml new file mode 100644 index 00000000000..f62dfba5843 --- /dev/null +++ b/seed/pydantic/grpc/.mock/generators.yml @@ -0,0 +1,4 @@ +api: + - proto: + root: proto + target: proto/user/v1/user.proto \ No newline at end of file diff --git a/seed/pydantic/grpc/.mock/proto/google/api/annotations.proto b/seed/pydantic/grpc/.mock/proto/google/api/annotations.proto new file mode 100644 index 00000000000..efdab3db6ca --- /dev/null +++ b/seed/pydantic/grpc/.mock/proto/google/api/annotations.proto @@ -0,0 +1,31 @@ +// Copyright 2015 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/api/http.proto"; +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "AnnotationsProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // See `HttpRule`. + HttpRule http = 72295728; +} diff --git a/seed/pydantic/grpc/.mock/proto/google/api/field_behavior.proto b/seed/pydantic/grpc/.mock/proto/google/api/field_behavior.proto new file mode 100644 index 00000000000..344cb0b1fc2 --- /dev/null +++ b/seed/pydantic/grpc/.mock/proto/google/api/field_behavior.proto @@ -0,0 +1,104 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "FieldBehaviorProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.FieldOptions { + // A designation of a specific field behavior (required, output only, etc.) + // in protobuf messages. + // + // Examples: + // + // string name = 1 [(google.api.field_behavior) = REQUIRED]; + // State state = 1 [(google.api.field_behavior) = OUTPUT_ONLY]; + // google.protobuf.Duration ttl = 1 + // [(google.api.field_behavior) = INPUT_ONLY]; + // google.protobuf.Timestamp expire_time = 1 + // [(google.api.field_behavior) = OUTPUT_ONLY, + // (google.api.field_behavior) = IMMUTABLE]; + repeated google.api.FieldBehavior field_behavior = 1052; +} + +// An indicator of the behavior of a given field (for example, that a field +// is required in requests, or given as output but ignored as input). +// This **does not** change the behavior in protocol buffers itself; it only +// denotes the behavior and may affect how API tooling handles the field. +// +// Note: This enum **may** receive new values in the future. +enum FieldBehavior { + // Conventional default for enums. Do not use this. + FIELD_BEHAVIOR_UNSPECIFIED = 0; + + // Specifically denotes a field as optional. + // While all fields in protocol buffers are optional, this may be specified + // for emphasis if appropriate. + OPTIONAL = 1; + + // Denotes a field as required. + // This indicates that the field **must** be provided as part of the request, + // and failure to do so will cause an error (usually `INVALID_ARGUMENT`). + REQUIRED = 2; + + // Denotes a field as output only. + // This indicates that the field is provided in responses, but including the + // field in a request does nothing (the server *must* ignore it and + // *must not* throw an error as a result of the field's presence). + OUTPUT_ONLY = 3; + + // Denotes a field as input only. + // This indicates that the field is provided in requests, and the + // corresponding field is not included in output. + INPUT_ONLY = 4; + + // Denotes a field as immutable. + // This indicates that the field may be set once in a request to create a + // resource, but may not be changed thereafter. + IMMUTABLE = 5; + + // Denotes that a (repeated) field is an unordered list. + // This indicates that the service may provide the elements of the list + // in any arbitrary order, rather than the order the user originally + // provided. Additionally, the list's order may or may not be stable. + UNORDERED_LIST = 6; + + // Denotes that this field returns a non-empty default value if not set. + // This indicates that if the user provides the empty value in a request, + // a non-empty value will be returned. The user will not be aware of what + // non-empty value to expect. + NON_EMPTY_DEFAULT = 7; + + // Denotes that the field in a resource (a message annotated with + // google.api.resource) is used in the resource name to uniquely identify the + // resource. For AIP-compliant APIs, this should only be applied to the + // `name` field on the resource. + // + // This behavior should not be applied to references to other resources within + // the message. + // + // The identifier field of resources often have different field behavior + // depending on the request it is embedded in (e.g. for Create methods name + // is optional and unused, while for Update methods it is required). Instead + // of method-specific annotations, only `IDENTIFIER` is required. + IDENTIFIER = 8; +} diff --git a/seed/pydantic/grpc/.mock/proto/google/api/http.proto b/seed/pydantic/grpc/.mock/proto/google/api/http.proto new file mode 100644 index 00000000000..31d867a27d5 --- /dev/null +++ b/seed/pydantic/grpc/.mock/proto/google/api/http.proto @@ -0,0 +1,379 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "HttpProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Defines the HTTP configuration for an API service. It contains a list of +// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method +// to one or more HTTP REST API methods. +message Http { + // A list of HTTP configuration rules that apply to individual API methods. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated HttpRule rules = 1; + + // When set to true, URL path parameters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // + // The default behavior is to not decode RFC 6570 reserved characters in multi + // segment matches. + bool fully_decode_reserved_expansion = 2; +} + +// # gRPC Transcoding +// +// gRPC Transcoding is a feature for mapping between a gRPC method and one or +// more HTTP REST endpoints. It allows developers to build a single API service +// that supports both gRPC APIs and REST APIs. Many systems, including [Google +// APIs](https://github.com/googleapis/googleapis), +// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC +// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), +// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature +// and use it for large scale production services. +// +// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies +// how different portions of the gRPC request message are mapped to the URL +// path, URL query parameters, and HTTP request body. It also controls how the +// gRPC response message is mapped to the HTTP response body. `HttpRule` is +// typically specified as an `google.api.http` annotation on the gRPC method. +// +// Each mapping specifies a URL path template and an HTTP method. The path +// template may refer to one or more fields in the gRPC request message, as long +// as each field is a non-repeated field with a primitive (non-message) type. +// The path template controls how fields of the request message are mapped to +// the URL path. +// +// Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/{name=messages/*}" +// }; +// } +// } +// message GetMessageRequest { +// string name = 1; // Mapped to URL path. +// } +// message Message { +// string text = 1; // The resource content. +// } +// +// This enables an HTTP REST to gRPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` +// +// Any fields in the request message which are not bound by the path template +// automatically become HTTP query parameters if there is no HTTP request body. +// For example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get:"/v1/messages/{message_id}" +// }; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // Mapped to URL path. +// int64 revision = 2; // Mapped to URL query parameter `revision`. +// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. +// } +// +// This enables a HTTP JSON to RPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | +// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: +// "foo"))` +// +// Note that fields which are mapped to URL query parameters must have a +// primitive type or a repeated primitive type or a non-repeated message type. +// In the case of a repeated type, the parameter can be repeated in the URL +// as `...?param=A¶m=B`. In the case of a message type, each field of the +// message is mapped to a separate parameter, such as +// `...?foo.a=A&foo.b=B&foo.c=C`. +// +// For HTTP methods that allow a request body, the `body` field +// specifies the mapping. Consider a REST update method on the +// message resource collection: +// +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } +// +// The following HTTP JSON to RPC mapping is enabled, where the +// representation of the JSON in the request body is determined by +// protos JSON encoding: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" message { text: "Hi!" })` +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. This enables the following alternative definition of +// the update method: +// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } +// +// +// The following HTTP JSON to RPC mapping is enabled: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" text: "Hi!")` +// +// Note that when using `*` in the body mapping, it is not possible to +// have HTTP parameters, as all fields not bound by the path end in +// the body. This makes this option more rarely used in practice when +// defining REST APIs. The common usage of `*` is in custom methods +// which don't use the URL at all for transferring data. +// +// It is possible to define multiple HTTP methods for one RPC by using +// the `additional_bindings` option. Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } +// +// This enables the following two alternative HTTP JSON to RPC mappings: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` +// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: +// "123456")` +// +// ## Rules for HTTP mapping +// +// 1. Leaf request fields (recursive expansion nested messages in the request +// message) are classified into three categories: +// - Fields referred by the path template. They are passed via the URL path. +// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They +// are passed via the HTTP +// request body. +// - All other fields are passed via the URL query parameters, and the +// parameter name is the field path in the request message. A repeated +// field can be represented as multiple query parameters under the same +// name. +// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL +// query parameter, all fields +// are passed via URL path and HTTP request body. +// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP +// request body, all +// fields are passed via URL path and URL query parameters. +// +// ### Path template syntax +// +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +// +// The syntax `*` matches a single URL path segment. The syntax `**` matches +// zero or more URL path segments, which must be the last part of the URL path +// except the `Verb`. +// +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable +// matches a single path segment, its template may be omitted, e.g. `{var}` +// is equivalent to `{var=*}`. +// +// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` +// contains any reserved character, such characters should be percent-encoded +// before the matching. +// +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path on the client +// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The +// server side does the reverse decoding. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{var}`. +// +// If a variable contains multiple path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path on the +// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. +// The server side does the reverse decoding, except "%2F" and "%2f" are left +// unchanged. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{+var}`. +// +// ## Using gRPC API Service Configuration +// +// gRPC API Service Configuration (service config) is a configuration language +// for configuring a gRPC service to become a user-facing product. The +// service config is simply the YAML representation of the `google.api.Service` +// proto message. +// +// As an alternative to annotating your proto file, you can configure gRPC +// transcoding in your service config YAML files. You do this by specifying a +// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same +// effect as the proto annotation. This can be particularly useful if you +// have a proto that is reused in multiple services. Note that any transcoding +// specified in the service config will override any matching transcoding +// configuration in the proto. +// +// Example: +// +// http: +// rules: +// # Selects a gRPC method and applies HttpRule to it. +// - selector: example.v1.Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} +// +// ## Special notes +// +// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the +// proto to JSON conversion must follow the [proto3 +// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). +// +// While the single segment variable follows the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String +// Expansion, the multi segment variable **does not** follow RFC 6570 Section +// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding +// for multi segment variables. +// +// The path variables **must not** refer to any repeated or mapped field, +// because client libraries are not capable of handling such variable expansion. +// +// The path variables **must not** capture the leading "/" character. The reason +// is that the most common use case "{var}" does not capture the leading "/" +// character. For consistency, all path variables must share the same behavior. +// +// Repeated message fields must not be mapped to URL query parameters, because +// no client library can support such complicated mapping. +// +// If an API needs to use a JSON array for request or response body, it can map +// the request or response body to a repeated field. However, some gRPC +// Transcoding implementations may not support this feature. +message HttpRule { + // Selects a method to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax + // details. + string selector = 1; + + // Determines the URL pattern is matched by this rules. This pattern can be + // used with any of the {get|put|post|delete|patch} methods. A custom method + // can be defined using the 'custom' field. + oneof pattern { + // Maps to HTTP GET. Used for listing and getting information about + // resources. + string get = 2; + + // Maps to HTTP PUT. Used for replacing a resource. + string put = 3; + + // Maps to HTTP POST. Used for creating a resource or performing an action. + string post = 4; + + // Maps to HTTP DELETE. Used for deleting a resource. + string delete = 5; + + // Maps to HTTP PATCH. Used for updating a resource. + string patch = 6; + + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. + CustomHttpPattern custom = 8; + } + + // The name of the request field whose value is mapped to the HTTP request + // body, or `*` for mapping all request fields not captured by the path + // pattern to the HTTP body, or omitted for not having any HTTP request body. + // + // NOTE: the referred field must be present at the top-level of the request + // message type. + string body = 7; + + // Optional. The name of the response field whose value is mapped to the HTTP + // response body. When omitted, the entire response message will be used + // as the HTTP response body. + // + // NOTE: The referred field must be present at the top-level of the response + // message type. + string response_body = 12; + + // Additional HTTP bindings for the selector. Nested bindings must + // not contain an `additional_bindings` field themselves (that is, + // the nesting may only be one level deep). + repeated HttpRule additional_bindings = 11; +} + +// A custom pattern is used for defining custom HTTP verb. +message CustomHttpPattern { + // The name of this custom HTTP verb. + string kind = 1; + + // The path matched by this custom verb. + string path = 2; +} diff --git a/seed/pydantic/grpc/.mock/proto/user/v1/user.proto b/seed/pydantic/grpc/.mock/proto/user/v1/user.proto new file mode 100644 index 00000000000..adab9ceefb9 --- /dev/null +++ b/seed/pydantic/grpc/.mock/proto/user/v1/user.proto @@ -0,0 +1,55 @@ +syntax = "proto3"; + +package user.v1; + +import "google/api/annotations.proto"; +import "google/api/field_behavior.proto"; +import "google/protobuf/struct.proto"; + +option csharp_namespace = "User.V1"; + +message User { + string username = 1 [ + (google.api.field_behavior) = REQUIRED + ]; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateUserRequest { + string username = 1 [ + (google.api.field_behavior) = REQUIRED + ]; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateUserResponse { + User user = 1; +} + +message GetUserRequest { + string username = 1; + string email = 2; + uint32 age = 3; + float weight = 4; +} + +service UserService { + rpc CreateUser(CreateUserRequest) returns (CreateUserResponse) { + option (google.api.http) = { + post: "/users" + body: "*" + }; + } + + rpc GetUser(GetUserRequest) returns (User) { + option (google.api.http) = { + get: "/users" + }; + } +} diff --git a/seed/pydantic/grpc/README.md b/seed/pydantic/grpc/README.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/pydantic/grpc/pyproject.toml b/seed/pydantic/grpc/pyproject.toml new file mode 100644 index 00000000000..cbfddba5577 --- /dev/null +++ b/seed/pydantic/grpc/pyproject.toml @@ -0,0 +1,55 @@ +[tool.poetry] +name = "fern_grpc" +version = "0.0.1" +description = "" +readme = "README.md" +authors = [] +keywords = [] + +classifiers = [ + "Intended Audience :: Developers", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Operating System :: OS Independent", + "Operating System :: POSIX", + "Operating System :: MacOS", + "Operating System :: POSIX :: Linux", + "Operating System :: Microsoft :: Windows", + "Topic :: Software Development :: Libraries :: Python Modules", + "Typing :: Typed" +] +packages = [ + { include = "seed/api", from = "src"} +] + +[project.urls] +Repository = 'https://github.com/grpc/fern' + +[tool.poetry.dependencies] +python = "^3.8" +pydantic = ">= 1.9.2" +pydantic-core = "^2.18.2" + +[tool.poetry.dev-dependencies] +mypy = "1.0.1" +pytest = "^7.4.0" +pytest-asyncio = "^0.23.5" +python-dateutil = "^2.9.0" +types-python-dateutil = "^2.9.0.20240316" + +[tool.pytest.ini_options] +testpaths = [ "tests" ] +asyncio_mode = "auto" + +[tool.mypy] +plugins = ["pydantic.mypy"] + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/seed/pydantic/grpc/snippet-templates.json b/seed/pydantic/grpc/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/pydantic/grpc/snippet.json b/seed/pydantic/grpc/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/pydantic/grpc/src/seed/api/__init__.py b/seed/pydantic/grpc/src/seed/api/__init__.py new file mode 100644 index 00000000000..5cb4878e904 --- /dev/null +++ b/seed/pydantic/grpc/src/seed/api/__init__.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +from .resources import CreateUserResponse, Metadata, MetadataValue, User, user + +__all__ = ["CreateUserResponse", "Metadata", "MetadataValue", "User", "user"] diff --git a/seed/pydantic/grpc/src/seed/api/core/__init__.py b/seed/pydantic/grpc/src/seed/api/core/__init__.py new file mode 100644 index 00000000000..85460274fba --- /dev/null +++ b/seed/pydantic/grpc/src/seed/api/core/__init__.py @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +from .datetime_utils import serialize_datetime +from .pydantic_utilities import ( + IS_PYDANTIC_V2, + UniversalBaseModel, + UniversalRootModel, + deep_union_pydantic_dicts, + parse_obj_as, + universal_field_validator, + universal_root_validator, + update_forward_refs, +) +from .serialization import FieldMetadata + +__all__ = [ + "FieldMetadata", + "IS_PYDANTIC_V2", + "UniversalBaseModel", + "UniversalRootModel", + "deep_union_pydantic_dicts", + "parse_obj_as", + "serialize_datetime", + "universal_field_validator", + "universal_root_validator", + "update_forward_refs", +] diff --git a/seed/pydantic/grpc/src/seed/api/core/datetime_utils.py b/seed/pydantic/grpc/src/seed/api/core/datetime_utils.py new file mode 100644 index 00000000000..7c9864a944c --- /dev/null +++ b/seed/pydantic/grpc/src/seed/api/core/datetime_utils.py @@ -0,0 +1,28 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt + + +def serialize_datetime(v: dt.datetime) -> str: + """ + Serialize a datetime including timezone info. + + Uses the timezone info provided if present, otherwise uses the current runtime's timezone info. + + UTC datetimes end in "Z" while all other timezones are represented as offset from UTC, e.g. +05:00. + """ + + def _serialize_zoned_datetime(v: dt.datetime) -> str: + if v.tzinfo is not None and v.tzinfo.tzname(None) == dt.timezone.utc.tzname(None): + # UTC is a special case where we use "Z" at the end instead of "+00:00" + return v.isoformat().replace("+00:00", "Z") + else: + # Delegate to the typical +/- offset format + return v.isoformat() + + if v.tzinfo is not None: + return _serialize_zoned_datetime(v) + else: + local_tz = dt.datetime.now().astimezone().tzinfo + localized_dt = v.replace(tzinfo=local_tz) + return _serialize_zoned_datetime(localized_dt) diff --git a/seed/pydantic/grpc/src/seed/api/core/pydantic_utilities.py b/seed/pydantic/grpc/src/seed/api/core/pydantic_utilities.py new file mode 100644 index 00000000000..f95015f89bd --- /dev/null +++ b/seed/pydantic/grpc/src/seed/api/core/pydantic_utilities.py @@ -0,0 +1,179 @@ +# This file was auto-generated by Fern from our API Definition. + +# nopycln: file +import datetime as dt +import typing +from collections import defaultdict +from functools import wraps + +import pydantic + +from .datetime_utils import serialize_datetime + +IS_PYDANTIC_V2 = pydantic.VERSION.startswith("2.") + +if IS_PYDANTIC_V2: + # isort will try to reformat the comments on these imports, which breaks mypy + # isort: off + from pydantic.v1.datetime_parse import ( # type: ignore # pyright: ignore[reportMissingImports] # Pydantic v2 + parse_date as parse_date, + ) + from pydantic.v1.datetime_parse import ( # pyright: ignore[reportMissingImports] # Pydantic v2 + parse_datetime as parse_datetime, + ) + from pydantic.v1.json import ( # type: ignore # pyright: ignore[reportMissingImports] # Pydantic v2 + ENCODERS_BY_TYPE as encoders_by_type, + ) + from pydantic.v1.typing import ( # type: ignore # pyright: ignore[reportMissingImports] # Pydantic v2 + get_args as get_args, + ) + from pydantic.v1.typing import get_origin as get_origin # pyright: ignore[reportMissingImports] # Pydantic v2 + from pydantic.v1.typing import ( # pyright: ignore[reportMissingImports] # Pydantic v2 + is_literal_type as is_literal_type, + ) + from pydantic.v1.typing import is_union as is_union # pyright: ignore[reportMissingImports] # Pydantic v2 + from pydantic.v1.fields import ModelField as ModelField # type: ignore # pyright: ignore[reportMissingImports] # Pydantic v2 +else: + from pydantic.datetime_parse import parse_date as parse_date # type: ignore # Pydantic v1 + from pydantic.datetime_parse import parse_datetime as parse_datetime # type: ignore # Pydantic v1 + from pydantic.fields import ModelField as ModelField # type: ignore # Pydantic v1 + from pydantic.json import ENCODERS_BY_TYPE as encoders_by_type # type: ignore # Pydantic v1 + from pydantic.typing import get_args as get_args # type: ignore # Pydantic v1 + from pydantic.typing import get_origin as get_origin # type: ignore # Pydantic v1 + from pydantic.typing import is_literal_type as is_literal_type # type: ignore # Pydantic v1 + from pydantic.typing import is_union as is_union # type: ignore # Pydantic v1 + + # isort: on + + +T = typing.TypeVar("T") +Model = typing.TypeVar("Model", bound=pydantic.BaseModel) + + +def deep_union_pydantic_dicts( + source: typing.Dict[str, typing.Any], destination: typing.Dict[str, typing.Any] +) -> typing.Dict[str, typing.Any]: + for key, value in source.items(): + if isinstance(value, dict): + node = destination.setdefault(key, {}) + deep_union_pydantic_dicts(value, node) + else: + destination[key] = value + + return destination + + +def parse_obj_as(type_: typing.Type[T], object_: typing.Any) -> T: + if IS_PYDANTIC_V2: + adapter = pydantic.TypeAdapter(type_) # type: ignore # Pydantic v2 + return adapter.validate_python(object_) + else: + return pydantic.parse_obj_as(type_, object_) + + +def to_jsonable_with_fallback( + obj: typing.Any, fallback_serializer: typing.Callable[[typing.Any], typing.Any] +) -> typing.Any: + if IS_PYDANTIC_V2: + from pydantic_core import to_jsonable_python + + return to_jsonable_python(obj, fallback=fallback_serializer) + else: + return fallback_serializer(obj) + + +class UniversalBaseModel(pydantic.BaseModel): + class Config: + populate_by_name = True + smart_union = True + allow_population_by_field_name = True + json_encoders = {dt.datetime: serialize_datetime} + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = {"by_alias": True, "exclude_unset": True, **kwargs} + if IS_PYDANTIC_V2: + return super().model_dump_json(**kwargs_with_defaults) # type: ignore # Pydantic v2 + else: + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + kwargs_with_defaults_exclude_unset: typing.Any = {"by_alias": True, "exclude_unset": True, **kwargs} + kwargs_with_defaults_exclude_none: typing.Any = {"by_alias": True, "exclude_none": True, **kwargs} + + if IS_PYDANTIC_V2: + return deep_union_pydantic_dicts( + super().model_dump(**kwargs_with_defaults_exclude_unset), # type: ignore # Pydantic v2 + super().model_dump(**kwargs_with_defaults_exclude_none), # type: ignore # Pydantic v2 + ) + else: + return deep_union_pydantic_dicts( + super().dict(**kwargs_with_defaults_exclude_unset), super().dict(**kwargs_with_defaults_exclude_none) + ) + + +UniversalRootModel: typing.Type[pydantic.BaseModel] +if IS_PYDANTIC_V2: + + class V2RootModel(UniversalBaseModel, pydantic.RootModel): # type: ignore # Pydantic v2 + pass + + UniversalRootModel = V2RootModel +else: + UniversalRootModel = UniversalBaseModel + + +def encode_by_type(o: typing.Any) -> typing.Any: + encoders_by_class_tuples: typing.Dict[ + typing.Callable[[typing.Any], typing.Any], typing.Tuple[typing.Any, ...] + ] = defaultdict(tuple) + for type_, encoder in encoders_by_type.items(): + encoders_by_class_tuples[encoder] += (type_,) + + if type(o) in encoders_by_type: + return encoders_by_type[type(o)](o) + for encoder, classes_tuple in encoders_by_class_tuples.items(): + if isinstance(o, classes_tuple): + return encoder(o) + + +def update_forward_refs(model: typing.Type["Model"], **localns: typing.Any) -> None: + if IS_PYDANTIC_V2: + model.model_rebuild(force=True, raise_errors=False) # type: ignore # Pydantic v2 + else: + model.update_forward_refs(**localns) + + +# Mirrors Pydantic's internal typing +AnyCallable = typing.Callable[..., typing.Any] + + +def universal_root_validator(pre: bool = False) -> typing.Callable[[AnyCallable], AnyCallable]: + def decorator(func: AnyCallable) -> AnyCallable: + @wraps(func) + def validate(*args: typing.Any, **kwargs: typing.Any) -> AnyCallable: + if IS_PYDANTIC_V2: + wrapped_func = pydantic.model_validator("before" if pre else "after")(func) # type: ignore # Pydantic v2 + else: + wrapped_func = pydantic.root_validator(pre=pre)(func) # type: ignore # Pydantic v1 + + return wrapped_func(*args, **kwargs) + + return validate + + return decorator + + +def universal_field_validator(field_name: str, pre: bool = False) -> typing.Callable[[AnyCallable], AnyCallable]: + def decorator(func: AnyCallable) -> AnyCallable: + @wraps(func) + def validate(*args: typing.Any, **kwargs: typing.Any) -> AnyCallable: + if IS_PYDANTIC_V2: + wrapped_func = pydantic.field_validator(field_name, mode="before" if pre else "after")(func) # type: ignore # Pydantic v2 + else: + wrapped_func = pydantic.validator(field_name, pre=pre)(func) + + return wrapped_func(*args, **kwargs) + + return validate + + return decorator diff --git a/seed/pydantic/grpc/src/seed/api/core/serialization.py b/seed/pydantic/grpc/src/seed/api/core/serialization.py new file mode 100644 index 00000000000..8ad5cf8125f --- /dev/null +++ b/seed/pydantic/grpc/src/seed/api/core/serialization.py @@ -0,0 +1,167 @@ +# This file was auto-generated by Fern from our API Definition. + +import collections +import typing + +import typing_extensions + + +class FieldMetadata: + """ + Metadata class used to annotate fields to provide additional information. + + Example: + class MyDict(TypedDict): + field: typing.Annotated[str, FieldMetadata(alias="field_name")] + + Will serialize: `{"field": "value"}` + To: `{"field_name": "value"}` + """ + + alias: str + + def __init__(self, *, alias: str) -> None: + self.alias = alias + + +def convert_and_respect_annotation_metadata( + *, object_: typing.Any, annotation: typing.Any, inner_type: typing.Optional[typing.Any] = None +) -> typing.Any: + """ + Respect the metadata annotations on a field, such as aliasing. This function effectively + manipulates the dict-form of an object to respect the metadata annotations. This is primarily used for + TypedDicts, which cannot support aliasing out of the box, and can be extended for additional + utilities, such as defaults. + + Parameters + ---------- + object_ : typing.Any + + annotation : type + The type we're looking to apply typing annotations from + + inner_type : typing.Optional[type] + + Returns + ------- + typing.Any + """ + + if object_ is None: + return None + if inner_type is None: + inner_type = annotation + + clean_type = _remove_annotations(inner_type) + if typing_extensions.is_typeddict(clean_type) and isinstance(object_, typing.Mapping): + return _convert_typeddict(object_, clean_type) + + if ( + # If you're iterating on a string, do not bother to coerce it to a sequence. + (not isinstance(object_, str)) + and ( + ( + ( + typing_extensions.get_origin(clean_type) == typing.List + or typing_extensions.get_origin(clean_type) == list + or clean_type == typing.List + ) + and isinstance(object_, typing.List) + ) + or ( + ( + typing_extensions.get_origin(clean_type) == typing.Set + or typing_extensions.get_origin(clean_type) == set + or clean_type == typing.Set + ) + and isinstance(object_, typing.Set) + ) + or ( + ( + typing_extensions.get_origin(clean_type) == typing.Sequence + or typing_extensions.get_origin(clean_type) == collections.abc.Sequence + or clean_type == typing.Sequence + ) + and isinstance(object_, typing.Sequence) + ) + ) + ): + inner_type = typing_extensions.get_args(clean_type)[0] + return [ + convert_and_respect_annotation_metadata(object_=item, annotation=annotation, inner_type=inner_type) + for item in object_ + ] + + if typing_extensions.get_origin(clean_type) == typing.Union: + # We should be able to ~relatively~ safely try to convert keys against all + # member types in the union, the edge case here is if one member aliases a field + # of the same name to a different name from another member + # Or if another member aliases a field of the same name that another member does not. + for member in typing_extensions.get_args(clean_type): + object_ = convert_and_respect_annotation_metadata(object_=object_, annotation=annotation, inner_type=member) + return object_ + + annotated_type = _get_annotation(annotation) + if annotated_type is None: + return object_ + + # If the object is not a TypedDict, a Union, or other container (list, set, sequence, etc.) + # Then we can safely call it on the recursive conversion. + return object_ + + +def _convert_typeddict(object_: typing.Mapping[str, object], expected_type: typing.Any) -> typing.Mapping[str, object]: + converted_object: typing.Dict[str, object] = {} + annotations = typing_extensions.get_type_hints(expected_type, include_extras=True) + for key, value in object_.items(): + type_ = annotations.get(key) + if type_ is None: + converted_object[key] = value + else: + converted_object[_alias_key(key, type_)] = convert_and_respect_annotation_metadata( + object_=value, annotation=type_ + ) + return converted_object + + +def _get_annotation(type_: typing.Any) -> typing.Optional[typing.Any]: + maybe_annotated_type = typing_extensions.get_origin(type_) + if maybe_annotated_type is None: + return None + + if maybe_annotated_type == typing_extensions.NotRequired: + type_ = typing_extensions.get_args(type_)[0] + maybe_annotated_type = typing_extensions.get_origin(type_) + + if maybe_annotated_type == typing_extensions.Annotated: + return type_ + + return None + + +def _remove_annotations(type_: typing.Any) -> typing.Any: + maybe_annotated_type = typing_extensions.get_origin(type_) + if maybe_annotated_type is None: + return type_ + + if maybe_annotated_type == typing_extensions.NotRequired: + return _remove_annotations(typing_extensions.get_args(type_)[0]) + + if maybe_annotated_type == typing_extensions.Annotated: + return _remove_annotations(typing_extensions.get_args(type_)[0]) + + return type_ + + +def _alias_key(key: str, type_: typing.Any) -> str: + maybe_annotated_type = _get_annotation(type_) + + if maybe_annotated_type is not None: + # The actual annotations are 1 onward, the first is the annotated type + annotations = typing_extensions.get_args(maybe_annotated_type)[1:] + + for annotation in annotations: + if isinstance(annotation, FieldMetadata) and annotation.alias is not None: + return annotation.alias + + return key diff --git a/seed/pydantic/grpc/src/seed/api/py.typed b/seed/pydantic/grpc/src/seed/api/py.typed new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/pydantic/grpc/src/seed/api/resources/__init__.py b/seed/pydantic/grpc/src/seed/api/resources/__init__.py new file mode 100644 index 00000000000..616a09fbcd1 --- /dev/null +++ b/seed/pydantic/grpc/src/seed/api/resources/__init__.py @@ -0,0 +1,6 @@ +# This file was auto-generated by Fern from our API Definition. + +from . import user +from .user import CreateUserResponse, Metadata, MetadataValue, User + +__all__ = ["CreateUserResponse", "Metadata", "MetadataValue", "User", "user"] diff --git a/seed/pydantic/grpc/src/seed/api/resources/user/__init__.py b/seed/pydantic/grpc/src/seed/api/resources/user/__init__.py new file mode 100644 index 00000000000..25fbdf08a23 --- /dev/null +++ b/seed/pydantic/grpc/src/seed/api/resources/user/__init__.py @@ -0,0 +1,8 @@ +# This file was auto-generated by Fern from our API Definition. + +from .create_user_response import CreateUserResponse +from .metadata import Metadata +from .metadata_value import MetadataValue +from .user import User + +__all__ = ["CreateUserResponse", "Metadata", "MetadataValue", "User"] diff --git a/seed/pydantic/grpc/src/seed/api/resources/user/create_user_response.py b/seed/pydantic/grpc/src/seed/api/resources/user/create_user_response.py new file mode 100644 index 00000000000..f51cd2b6ded --- /dev/null +++ b/seed/pydantic/grpc/src/seed/api/resources/user/create_user_response.py @@ -0,0 +1,19 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic + +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .user import User + + +class CreateUserResponse(UniversalBaseModel): + user: User + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 + else: + + class Config: + extra = pydantic.Extra.allow diff --git a/seed/pydantic/grpc/src/seed/api/resources/user/metadata.py b/seed/pydantic/grpc/src/seed/api/resources/user/metadata.py new file mode 100644 index 00000000000..511c4984799 --- /dev/null +++ b/seed/pydantic/grpc/src/seed/api/resources/user/metadata.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from .metadata_value import MetadataValue + +Metadata = typing.Dict[str, typing.Optional[MetadataValue]] diff --git a/seed/pydantic/grpc/src/seed/api/resources/user/metadata_value.py b/seed/pydantic/grpc/src/seed/api/resources/user/metadata_value.py new file mode 100644 index 00000000000..28b9ba3e1b6 --- /dev/null +++ b/seed/pydantic/grpc/src/seed/api/resources/user/metadata_value.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +MetadataValue = typing.Union[float, str, bool, typing.List[MetadataValue]] diff --git a/seed/pydantic/grpc/src/seed/api/resources/user/user.py b/seed/pydantic/grpc/src/seed/api/resources/user/user.py new file mode 100644 index 00000000000..73e2789a1de --- /dev/null +++ b/seed/pydantic/grpc/src/seed/api/resources/user/user.py @@ -0,0 +1,24 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic + +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .metadata import Metadata + + +class User(UniversalBaseModel): + id: str + username: str + email: typing.Optional[str] = None + age: typing.Optional[int] = None + weight: typing.Optional[float] = None + metadata: typing.Optional[Metadata] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow") # type: ignore # Pydantic v2 + else: + + class Config: + extra = pydantic.Extra.allow diff --git a/seed/pydantic/grpc/tests/custom/test_client.py b/seed/pydantic/grpc/tests/custom/test_client.py new file mode 100644 index 00000000000..60a58e64c27 --- /dev/null +++ b/seed/pydantic/grpc/tests/custom/test_client.py @@ -0,0 +1,6 @@ +import pytest + +# Get started with writing tests with pytest at https://docs.pytest.org +@pytest.mark.skip(reason="Unimplemented") +def test_client() -> None: + assert True == True diff --git a/seed/python-sdk/alias-extends/src/seed/core/http_client.py b/seed/python-sdk/alias-extends/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/alias-extends/src/seed/core/http_client.py +++ b/seed/python-sdk/alias-extends/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/alias-extends/tests/utils/test_http_client.py b/seed/python-sdk/alias-extends/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/alias-extends/tests/utils/test_http_client.py +++ b/seed/python-sdk/alias-extends/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/alias/src/seed/core/http_client.py b/seed/python-sdk/alias/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/alias/src/seed/core/http_client.py +++ b/seed/python-sdk/alias/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/alias/tests/utils/test_http_client.py b/seed/python-sdk/alias/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/alias/tests/utils/test_http_client.py +++ b/seed/python-sdk/alias/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/api-wide-base-path/src/seed/core/http_client.py b/seed/python-sdk/api-wide-base-path/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/api-wide-base-path/src/seed/core/http_client.py +++ b/seed/python-sdk/api-wide-base-path/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/api-wide-base-path/tests/utils/test_http_client.py b/seed/python-sdk/api-wide-base-path/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/api-wide-base-path/tests/utils/test_http_client.py +++ b/seed/python-sdk/api-wide-base-path/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/audiences/src/seed/core/http_client.py b/seed/python-sdk/audiences/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/audiences/src/seed/core/http_client.py +++ b/seed/python-sdk/audiences/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/audiences/tests/utils/test_http_client.py b/seed/python-sdk/audiences/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/audiences/tests/utils/test_http_client.py +++ b/seed/python-sdk/audiences/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/auth-environment-variables/src/seed/core/http_client.py b/seed/python-sdk/auth-environment-variables/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/auth-environment-variables/src/seed/core/http_client.py +++ b/seed/python-sdk/auth-environment-variables/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/auth-environment-variables/tests/utils/test_http_client.py b/seed/python-sdk/auth-environment-variables/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/auth-environment-variables/tests/utils/test_http_client.py +++ b/seed/python-sdk/auth-environment-variables/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/basic-auth-environment-variables/src/seed/core/http_client.py b/seed/python-sdk/basic-auth-environment-variables/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/basic-auth-environment-variables/src/seed/core/http_client.py +++ b/seed/python-sdk/basic-auth-environment-variables/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/basic-auth-environment-variables/tests/utils/test_http_client.py b/seed/python-sdk/basic-auth-environment-variables/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/basic-auth-environment-variables/tests/utils/test_http_client.py +++ b/seed/python-sdk/basic-auth-environment-variables/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/basic-auth/src/seed/core/http_client.py b/seed/python-sdk/basic-auth/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/basic-auth/src/seed/core/http_client.py +++ b/seed/python-sdk/basic-auth/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/basic-auth/tests/utils/test_http_client.py b/seed/python-sdk/basic-auth/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/basic-auth/tests/utils/test_http_client.py +++ b/seed/python-sdk/basic-auth/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/bearer-token-environment-variable/src/seed/core/http_client.py b/seed/python-sdk/bearer-token-environment-variable/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/bearer-token-environment-variable/src/seed/core/http_client.py +++ b/seed/python-sdk/bearer-token-environment-variable/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/bearer-token-environment-variable/tests/utils/test_http_client.py b/seed/python-sdk/bearer-token-environment-variable/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/bearer-token-environment-variable/tests/utils/test_http_client.py +++ b/seed/python-sdk/bearer-token-environment-variable/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/bytes/src/seed/core/http_client.py b/seed/python-sdk/bytes/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/bytes/src/seed/core/http_client.py +++ b/seed/python-sdk/bytes/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/bytes/tests/utils/test_http_client.py b/seed/python-sdk/bytes/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/bytes/tests/utils/test_http_client.py +++ b/seed/python-sdk/bytes/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/circular-references-advanced/src/seed/core/http_client.py b/seed/python-sdk/circular-references-advanced/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/circular-references-advanced/src/seed/core/http_client.py +++ b/seed/python-sdk/circular-references-advanced/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/circular-references-advanced/tests/utils/test_http_client.py b/seed/python-sdk/circular-references-advanced/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/circular-references-advanced/tests/utils/test_http_client.py +++ b/seed/python-sdk/circular-references-advanced/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/circular-references/src/seed/core/http_client.py b/seed/python-sdk/circular-references/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/circular-references/src/seed/core/http_client.py +++ b/seed/python-sdk/circular-references/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/circular-references/tests/utils/test_http_client.py b/seed/python-sdk/circular-references/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/circular-references/tests/utils/test_http_client.py +++ b/seed/python-sdk/circular-references/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/code-samples/src/seed/core/http_client.py b/seed/python-sdk/code-samples/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/code-samples/src/seed/core/http_client.py +++ b/seed/python-sdk/code-samples/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/code-samples/tests/utils/test_http_client.py b/seed/python-sdk/code-samples/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/code-samples/tests/utils/test_http_client.py +++ b/seed/python-sdk/code-samples/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/custom-auth/src/seed/core/http_client.py b/seed/python-sdk/custom-auth/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/custom-auth/src/seed/core/http_client.py +++ b/seed/python-sdk/custom-auth/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/custom-auth/tests/utils/test_http_client.py b/seed/python-sdk/custom-auth/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/custom-auth/tests/utils/test_http_client.py +++ b/seed/python-sdk/custom-auth/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/enum/no-custom-config/src/seed/core/http_client.py b/seed/python-sdk/enum/no-custom-config/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/enum/no-custom-config/src/seed/core/http_client.py +++ b/seed/python-sdk/enum/no-custom-config/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/enum/no-custom-config/tests/utils/test_http_client.py b/seed/python-sdk/enum/no-custom-config/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/enum/no-custom-config/tests/utils/test_http_client.py +++ b/seed/python-sdk/enum/no-custom-config/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/enum/strenum/src/seed/core/http_client.py b/seed/python-sdk/enum/strenum/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/enum/strenum/src/seed/core/http_client.py +++ b/seed/python-sdk/enum/strenum/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/enum/strenum/tests/utils/test_http_client.py b/seed/python-sdk/enum/strenum/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/enum/strenum/tests/utils/test_http_client.py +++ b/seed/python-sdk/enum/strenum/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/error-property/src/seed/core/http_client.py b/seed/python-sdk/error-property/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/error-property/src/seed/core/http_client.py +++ b/seed/python-sdk/error-property/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/error-property/tests/utils/test_http_client.py b/seed/python-sdk/error-property/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/error-property/tests/utils/test_http_client.py +++ b/seed/python-sdk/error-property/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/examples/client-filename/src/seed/core/http_client.py b/seed/python-sdk/examples/client-filename/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/examples/client-filename/src/seed/core/http_client.py +++ b/seed/python-sdk/examples/client-filename/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/examples/client-filename/tests/utils/test_http_client.py b/seed/python-sdk/examples/client-filename/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/examples/client-filename/tests/utils/test_http_client.py +++ b/seed/python-sdk/examples/client-filename/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/examples/no-custom-config/src/seed/core/http_client.py b/seed/python-sdk/examples/no-custom-config/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/examples/no-custom-config/src/seed/core/http_client.py +++ b/seed/python-sdk/examples/no-custom-config/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/examples/no-custom-config/tests/utils/test_http_client.py b/seed/python-sdk/examples/no-custom-config/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/examples/no-custom-config/tests/utils/test_http_client.py +++ b/seed/python-sdk/examples/no-custom-config/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/examples/readme/src/seed/core/http_client.py b/seed/python-sdk/examples/readme/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/examples/readme/src/seed/core/http_client.py +++ b/seed/python-sdk/examples/readme/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/examples/readme/tests/utils/test_http_client.py b/seed/python-sdk/examples/readme/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/examples/readme/tests/utils/test_http_client.py +++ b/seed/python-sdk/examples/readme/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/exhaustive/deps_with_min_python_version/src/seed/core/http_client.py b/seed/python-sdk/exhaustive/deps_with_min_python_version/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/exhaustive/deps_with_min_python_version/src/seed/core/http_client.py +++ b/seed/python-sdk/exhaustive/deps_with_min_python_version/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/exhaustive/deps_with_min_python_version/tests/utils/test_http_client.py b/seed/python-sdk/exhaustive/deps_with_min_python_version/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/exhaustive/deps_with_min_python_version/tests/utils/test_http_client.py +++ b/seed/python-sdk/exhaustive/deps_with_min_python_version/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/exhaustive/extra_dependencies/src/seed/core/http_client.py b/seed/python-sdk/exhaustive/extra_dependencies/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/exhaustive/extra_dependencies/src/seed/core/http_client.py +++ b/seed/python-sdk/exhaustive/extra_dependencies/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/exhaustive/extra_dependencies/tests/utils/test_http_client.py b/seed/python-sdk/exhaustive/extra_dependencies/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/exhaustive/extra_dependencies/tests/utils/test_http_client.py +++ b/seed/python-sdk/exhaustive/extra_dependencies/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/exhaustive/extra_dev_dependencies/src/seed/core/http_client.py b/seed/python-sdk/exhaustive/extra_dev_dependencies/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/exhaustive/extra_dev_dependencies/src/seed/core/http_client.py +++ b/seed/python-sdk/exhaustive/extra_dev_dependencies/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/exhaustive/extra_dev_dependencies/tests/utils/test_http_client.py b/seed/python-sdk/exhaustive/extra_dev_dependencies/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/exhaustive/extra_dev_dependencies/tests/utils/test_http_client.py +++ b/seed/python-sdk/exhaustive/extra_dev_dependencies/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/exhaustive/five-second-timeout/src/seed/core/http_client.py b/seed/python-sdk/exhaustive/five-second-timeout/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/exhaustive/five-second-timeout/src/seed/core/http_client.py +++ b/seed/python-sdk/exhaustive/five-second-timeout/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/exhaustive/five-second-timeout/tests/utils/test_http_client.py b/seed/python-sdk/exhaustive/five-second-timeout/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/exhaustive/five-second-timeout/tests/utils/test_http_client.py +++ b/seed/python-sdk/exhaustive/five-second-timeout/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/exhaustive/follow_redirects_by_default/src/seed/core/http_client.py b/seed/python-sdk/exhaustive/follow_redirects_by_default/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/exhaustive/follow_redirects_by_default/src/seed/core/http_client.py +++ b/seed/python-sdk/exhaustive/follow_redirects_by_default/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/exhaustive/follow_redirects_by_default/tests/utils/test_http_client.py b/seed/python-sdk/exhaustive/follow_redirects_by_default/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/exhaustive/follow_redirects_by_default/tests/utils/test_http_client.py +++ b/seed/python-sdk/exhaustive/follow_redirects_by_default/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/exhaustive/improved_imports/src/seed/core/http_client.py b/seed/python-sdk/exhaustive/improved_imports/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/exhaustive/improved_imports/src/seed/core/http_client.py +++ b/seed/python-sdk/exhaustive/improved_imports/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/exhaustive/improved_imports/tests/utils/test_http_client.py b/seed/python-sdk/exhaustive/improved_imports/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/exhaustive/improved_imports/tests/utils/test_http_client.py +++ b/seed/python-sdk/exhaustive/improved_imports/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/exhaustive/infinite-timeout/src/seed/core/http_client.py b/seed/python-sdk/exhaustive/infinite-timeout/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/exhaustive/infinite-timeout/src/seed/core/http_client.py +++ b/seed/python-sdk/exhaustive/infinite-timeout/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/exhaustive/infinite-timeout/tests/utils/test_http_client.py b/seed/python-sdk/exhaustive/infinite-timeout/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/exhaustive/infinite-timeout/tests/utils/test_http_client.py +++ b/seed/python-sdk/exhaustive/infinite-timeout/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/exhaustive/inline_request_params/src/seed/core/http_client.py b/seed/python-sdk/exhaustive/inline_request_params/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/exhaustive/inline_request_params/src/seed/core/http_client.py +++ b/seed/python-sdk/exhaustive/inline_request_params/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/exhaustive/inline_request_params/tests/utils/test_http_client.py b/seed/python-sdk/exhaustive/inline_request_params/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/exhaustive/inline_request_params/tests/utils/test_http_client.py +++ b/seed/python-sdk/exhaustive/inline_request_params/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/exhaustive/no-custom-config/src/seed/core/http_client.py b/seed/python-sdk/exhaustive/no-custom-config/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/exhaustive/no-custom-config/src/seed/core/http_client.py +++ b/seed/python-sdk/exhaustive/no-custom-config/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/exhaustive/no-custom-config/tests/utils/test_http_client.py b/seed/python-sdk/exhaustive/no-custom-config/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/exhaustive/no-custom-config/tests/utils/test_http_client.py +++ b/seed/python-sdk/exhaustive/no-custom-config/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/exhaustive/pydantic-extra-fields/src/seed/core/http_client.py b/seed/python-sdk/exhaustive/pydantic-extra-fields/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/exhaustive/pydantic-extra-fields/src/seed/core/http_client.py +++ b/seed/python-sdk/exhaustive/pydantic-extra-fields/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/exhaustive/pydantic-extra-fields/tests/utils/test_http_client.py b/seed/python-sdk/exhaustive/pydantic-extra-fields/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/exhaustive/pydantic-extra-fields/tests/utils/test_http_client.py +++ b/seed/python-sdk/exhaustive/pydantic-extra-fields/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/exhaustive/pydantic-v1-wrapped/src/seed/core/http_client.py b/seed/python-sdk/exhaustive/pydantic-v1-wrapped/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/exhaustive/pydantic-v1-wrapped/src/seed/core/http_client.py +++ b/seed/python-sdk/exhaustive/pydantic-v1-wrapped/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/exhaustive/pydantic-v1-wrapped/tests/utils/test_http_client.py b/seed/python-sdk/exhaustive/pydantic-v1-wrapped/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/exhaustive/pydantic-v1-wrapped/tests/utils/test_http_client.py +++ b/seed/python-sdk/exhaustive/pydantic-v1-wrapped/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/exhaustive/pydantic-v1/src/seed/core/http_client.py b/seed/python-sdk/exhaustive/pydantic-v1/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/exhaustive/pydantic-v1/src/seed/core/http_client.py +++ b/seed/python-sdk/exhaustive/pydantic-v1/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/exhaustive/pydantic-v1/tests/utils/test_http_client.py b/seed/python-sdk/exhaustive/pydantic-v1/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/exhaustive/pydantic-v1/tests/utils/test_http_client.py +++ b/seed/python-sdk/exhaustive/pydantic-v1/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/exhaustive/skip-pydantic-validation/src/seed/core/http_client.py b/seed/python-sdk/exhaustive/skip-pydantic-validation/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/exhaustive/skip-pydantic-validation/src/seed/core/http_client.py +++ b/seed/python-sdk/exhaustive/skip-pydantic-validation/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/exhaustive/skip-pydantic-validation/tests/utils/test_http_client.py b/seed/python-sdk/exhaustive/skip-pydantic-validation/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/exhaustive/skip-pydantic-validation/tests/utils/test_http_client.py +++ b/seed/python-sdk/exhaustive/skip-pydantic-validation/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/exhaustive/union-utils/src/seed/core/http_client.py b/seed/python-sdk/exhaustive/union-utils/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/exhaustive/union-utils/src/seed/core/http_client.py +++ b/seed/python-sdk/exhaustive/union-utils/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/exhaustive/union-utils/tests/utils/test_http_client.py b/seed/python-sdk/exhaustive/union-utils/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/exhaustive/union-utils/tests/utils/test_http_client.py +++ b/seed/python-sdk/exhaustive/union-utils/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/extends/src/seed/core/http_client.py b/seed/python-sdk/extends/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/extends/src/seed/core/http_client.py +++ b/seed/python-sdk/extends/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/extends/tests/utils/test_http_client.py b/seed/python-sdk/extends/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/extends/tests/utils/test_http_client.py +++ b/seed/python-sdk/extends/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/extra-properties/src/seed/core/http_client.py b/seed/python-sdk/extra-properties/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/extra-properties/src/seed/core/http_client.py +++ b/seed/python-sdk/extra-properties/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/extra-properties/tests/utils/test_http_client.py b/seed/python-sdk/extra-properties/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/extra-properties/tests/utils/test_http_client.py +++ b/seed/python-sdk/extra-properties/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/file-download/src/seed/core/http_client.py b/seed/python-sdk/file-download/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/file-download/src/seed/core/http_client.py +++ b/seed/python-sdk/file-download/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/file-download/tests/utils/test_http_client.py b/seed/python-sdk/file-download/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/file-download/tests/utils/test_http_client.py +++ b/seed/python-sdk/file-download/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/file-upload/src/seed/core/http_client.py b/seed/python-sdk/file-upload/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/file-upload/src/seed/core/http_client.py +++ b/seed/python-sdk/file-upload/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/file-upload/tests/utils/test_http_client.py b/seed/python-sdk/file-upload/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/file-upload/tests/utils/test_http_client.py +++ b/seed/python-sdk/file-upload/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/folders/src/seed/core/http_client.py b/seed/python-sdk/folders/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/folders/src/seed/core/http_client.py +++ b/seed/python-sdk/folders/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/folders/tests/utils/test_http_client.py b/seed/python-sdk/folders/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/folders/tests/utils/test_http_client.py +++ b/seed/python-sdk/folders/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/grpc-proto/.github/workflows/ci.yml b/seed/python-sdk/grpc-proto/.github/workflows/ci.yml new file mode 100644 index 00000000000..b7316b8cab7 --- /dev/null +++ b/seed/python-sdk/grpc-proto/.github/workflows/ci.yml @@ -0,0 +1,63 @@ +name: ci + +on: [push] +jobs: + compile: + runs-on: ubuntu-20.04 + steps: + - name: Checkout repo + uses: actions/checkout@v3 + - name: Set up python + uses: actions/setup-python@v4 + with: + python-version: 3.8 + - name: Bootstrap poetry + run: | + curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 + - name: Install dependencies + run: poetry install + - name: Compile + run: poetry run mypy . + test: + runs-on: ubuntu-20.04 + steps: + - name: Checkout repo + uses: actions/checkout@v3 + - name: Set up python + uses: actions/setup-python@v4 + with: + python-version: 3.8 + - name: Bootstrap poetry + run: | + curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 + - name: Install dependencies + run: poetry install + + - name: Install Fern + run: npm install -g fern-api + - name: Test + run: fern test --command "poetry run pytest -rP ." + + publish: + needs: [compile, test] + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + runs-on: ubuntu-20.04 + steps: + - name: Checkout repo + uses: actions/checkout@v3 + - name: Set up python + uses: actions/setup-python@v4 + with: + python-version: 3.8 + - name: Bootstrap poetry + run: | + curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 + - name: Install dependencies + run: poetry install + - name: Publish to pypi + run: | + poetry config repositories.remote + poetry --no-interaction -v publish --build --repository remote --username "$" --password "$" + env: + : ${{ secrets. }} + : ${{ secrets. }} diff --git a/seed/python-sdk/grpc-proto/.gitignore b/seed/python-sdk/grpc-proto/.gitignore new file mode 100644 index 00000000000..42cb863501e --- /dev/null +++ b/seed/python-sdk/grpc-proto/.gitignore @@ -0,0 +1,4 @@ +dist/ +.mypy_cache/ +__pycache__/ +poetry.toml diff --git a/seed/python-sdk/grpc-proto/.mock/fern.config.json b/seed/python-sdk/grpc-proto/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/python-sdk/grpc-proto/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/python-sdk/grpc-proto/.mock/generators.yml b/seed/python-sdk/grpc-proto/.mock/generators.yml new file mode 100644 index 00000000000..902c4f50168 --- /dev/null +++ b/seed/python-sdk/grpc-proto/.mock/generators.yml @@ -0,0 +1,5 @@ +api: + - proto: + root: proto + target: proto/user/v1/user.proto + local-generation: true \ No newline at end of file diff --git a/seed/python-sdk/grpc-proto/.mock/proto/google/api/annotations.proto b/seed/python-sdk/grpc-proto/.mock/proto/google/api/annotations.proto new file mode 100644 index 00000000000..8ff42098404 --- /dev/null +++ b/seed/python-sdk/grpc-proto/.mock/proto/google/api/annotations.proto @@ -0,0 +1,31 @@ +// Copyright 2015 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/api/http.proto"; +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "AnnotationsProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // See `HttpRule`. + HttpRule http = 72295728; +} \ No newline at end of file diff --git a/seed/python-sdk/grpc-proto/.mock/proto/google/api/field_behavior.proto b/seed/python-sdk/grpc-proto/.mock/proto/google/api/field_behavior.proto new file mode 100644 index 00000000000..128799c558d --- /dev/null +++ b/seed/python-sdk/grpc-proto/.mock/proto/google/api/field_behavior.proto @@ -0,0 +1,104 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "FieldBehaviorProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.FieldOptions { + // A designation of a specific field behavior (required, output only, etc.) + // in protobuf messages. + // + // Examples: + // + // string name = 1 [(google.api.field_behavior) = REQUIRED]; + // State state = 1 [(google.api.field_behavior) = OUTPUT_ONLY]; + // google.protobuf.Duration ttl = 1 + // [(google.api.field_behavior) = INPUT_ONLY]; + // google.protobuf.Timestamp expire_time = 1 + // [(google.api.field_behavior) = OUTPUT_ONLY, + // (google.api.field_behavior) = IMMUTABLE]; + repeated google.api.FieldBehavior field_behavior = 1052; +} + +// An indicator of the behavior of a given field (for example, that a field +// is required in requests, or given as output but ignored as input). +// This **does not** change the behavior in protocol buffers itself; it only +// denotes the behavior and may affect how API tooling handles the field. +// +// Note: This enum **may** receive new values in the future. +enum FieldBehavior { + // Conventional default for enums. Do not use this. + FIELD_BEHAVIOR_UNSPECIFIED = 0; + + // Specifically denotes a field as optional. + // While all fields in protocol buffers are optional, this may be specified + // for emphasis if appropriate. + OPTIONAL = 1; + + // Denotes a field as required. + // This indicates that the field **must** be provided as part of the request, + // and failure to do so will cause an error (usually `INVALID_ARGUMENT`). + REQUIRED = 2; + + // Denotes a field as output only. + // This indicates that the field is provided in responses, but including the + // field in a request does nothing (the server *must* ignore it and + // *must not* throw an error as a result of the field's presence). + OUTPUT_ONLY = 3; + + // Denotes a field as input only. + // This indicates that the field is provided in requests, and the + // corresponding field is not included in output. + INPUT_ONLY = 4; + + // Denotes a field as immutable. + // This indicates that the field may be set once in a request to create a + // resource, but may not be changed thereafter. + IMMUTABLE = 5; + + // Denotes that a (repeated) field is an unordered list. + // This indicates that the service may provide the elements of the list + // in any arbitrary order, rather than the order the user originally + // provided. Additionally, the list's order may or may not be stable. + UNORDERED_LIST = 6; + + // Denotes that this field returns a non-empty default value if not set. + // This indicates that if the user provides the empty value in a request, + // a non-empty value will be returned. The user will not be aware of what + // non-empty value to expect. + NON_EMPTY_DEFAULT = 7; + + // Denotes that the field in a resource (a message annotated with + // google.api.resource) is used in the resource name to uniquely identify the + // resource. For AIP-compliant APIs, this should only be applied to the + // `name` field on the resource. + // + // This behavior should not be applied to references to other resources within + // the message. + // + // The identifier field of resources often have different field behavior + // depending on the request it is embedded in (e.g. for Create methods name + // is optional and unused, while for Update methods it is required). Instead + // of method-specific annotations, only `IDENTIFIER` is required. + IDENTIFIER = 8; +} \ No newline at end of file diff --git a/seed/python-sdk/grpc-proto/.mock/proto/google/api/http.proto b/seed/python-sdk/grpc-proto/.mock/proto/google/api/http.proto new file mode 100644 index 00000000000..c8392381eb9 --- /dev/null +++ b/seed/python-sdk/grpc-proto/.mock/proto/google/api/http.proto @@ -0,0 +1,379 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "HttpProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Defines the HTTP configuration for an API service. It contains a list of +// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method +// to one or more HTTP REST API methods. +message Http { + // A list of HTTP configuration rules that apply to individual API methods. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated HttpRule rules = 1; + + // When set to true, URL path parameters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // + // The default behavior is to not decode RFC 6570 reserved characters in multi + // segment matches. + bool fully_decode_reserved_expansion = 2; +} + +// # gRPC Transcoding +// +// gRPC Transcoding is a feature for mapping between a gRPC method and one or +// more HTTP REST endpoints. It allows developers to build a single API service +// that supports both gRPC APIs and REST APIs. Many systems, including [Google +// APIs](https://github.com/googleapis/googleapis), +// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC +// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), +// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature +// and use it for large scale production services. +// +// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies +// how different portions of the gRPC request message are mapped to the URL +// path, URL query parameters, and HTTP request body. It also controls how the +// gRPC response message is mapped to the HTTP response body. `HttpRule` is +// typically specified as an `google.api.http` annotation on the gRPC method. +// +// Each mapping specifies a URL path template and an HTTP method. The path +// template may refer to one or more fields in the gRPC request message, as long +// as each field is a non-repeated field with a primitive (non-message) type. +// The path template controls how fields of the request message are mapped to +// the URL path. +// +// Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/{name=messages/*}" +// }; +// } +// } +// message GetMessageRequest { +// string name = 1; // Mapped to URL path. +// } +// message Message { +// string text = 1; // The resource content. +// } +// +// This enables an HTTP REST to gRPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` +// +// Any fields in the request message which are not bound by the path template +// automatically become HTTP query parameters if there is no HTTP request body. +// For example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get:"/v1/messages/{message_id}" +// }; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // Mapped to URL path. +// int64 revision = 2; // Mapped to URL query parameter `revision`. +// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. +// } +// +// This enables a HTTP JSON to RPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | +// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: +// "foo"))` +// +// Note that fields which are mapped to URL query parameters must have a +// primitive type or a repeated primitive type or a non-repeated message type. +// In the case of a repeated type, the parameter can be repeated in the URL +// as `...?param=A¶m=B`. In the case of a message type, each field of the +// message is mapped to a separate parameter, such as +// `...?foo.a=A&foo.b=B&foo.c=C`. +// +// For HTTP methods that allow a request body, the `body` field +// specifies the mapping. Consider a REST update method on the +// message resource collection: +// +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } +// +// The following HTTP JSON to RPC mapping is enabled, where the +// representation of the JSON in the request body is determined by +// protos JSON encoding: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" message { text: "Hi!" })` +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. This enables the following alternative definition of +// the update method: +// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } +// +// +// The following HTTP JSON to RPC mapping is enabled: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" text: "Hi!")` +// +// Note that when using `*` in the body mapping, it is not possible to +// have HTTP parameters, as all fields not bound by the path end in +// the body. This makes this option more rarely used in practice when +// defining REST APIs. The common usage of `*` is in custom methods +// which don't use the URL at all for transferring data. +// +// It is possible to define multiple HTTP methods for one RPC by using +// the `additional_bindings` option. Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } +// +// This enables the following two alternative HTTP JSON to RPC mappings: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` +// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: +// "123456")` +// +// ## Rules for HTTP mapping +// +// 1. Leaf request fields (recursive expansion nested messages in the request +// message) are classified into three categories: +// - Fields referred by the path template. They are passed via the URL path. +// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They +// are passed via the HTTP +// request body. +// - All other fields are passed via the URL query parameters, and the +// parameter name is the field path in the request message. A repeated +// field can be represented as multiple query parameters under the same +// name. +// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL +// query parameter, all fields +// are passed via URL path and HTTP request body. +// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP +// request body, all +// fields are passed via URL path and URL query parameters. +// +// ### Path template syntax +// +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +// +// The syntax `*` matches a single URL path segment. The syntax `**` matches +// zero or more URL path segments, which must be the last part of the URL path +// except the `Verb`. +// +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable +// matches a single path segment, its template may be omitted, e.g. `{var}` +// is equivalent to `{var=*}`. +// +// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` +// contains any reserved character, such characters should be percent-encoded +// before the matching. +// +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path on the client +// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The +// server side does the reverse decoding. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{var}`. +// +// If a variable contains multiple path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path on the +// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. +// The server side does the reverse decoding, except "%2F" and "%2f" are left +// unchanged. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{+var}`. +// +// ## Using gRPC API Service Configuration +// +// gRPC API Service Configuration (service config) is a configuration language +// for configuring a gRPC service to become a user-facing product. The +// service config is simply the YAML representation of the `google.api.Service` +// proto message. +// +// As an alternative to annotating your proto file, you can configure gRPC +// transcoding in your service config YAML files. You do this by specifying a +// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same +// effect as the proto annotation. This can be particularly useful if you +// have a proto that is reused in multiple services. Note that any transcoding +// specified in the service config will override any matching transcoding +// configuration in the proto. +// +// Example: +// +// http: +// rules: +// # Selects a gRPC method and applies HttpRule to it. +// - selector: example.v1.Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} +// +// ## Special notes +// +// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the +// proto to JSON conversion must follow the [proto3 +// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). +// +// While the single segment variable follows the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String +// Expansion, the multi segment variable **does not** follow RFC 6570 Section +// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding +// for multi segment variables. +// +// The path variables **must not** refer to any repeated or mapped field, +// because client libraries are not capable of handling such variable expansion. +// +// The path variables **must not** capture the leading "/" character. The reason +// is that the most common use case "{var}" does not capture the leading "/" +// character. For consistency, all path variables must share the same behavior. +// +// Repeated message fields must not be mapped to URL query parameters, because +// no client library can support such complicated mapping. +// +// If an API needs to use a JSON array for request or response body, it can map +// the request or response body to a repeated field. However, some gRPC +// Transcoding implementations may not support this feature. +message HttpRule { + // Selects a method to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax + // details. + string selector = 1; + + // Determines the URL pattern is matched by this rules. This pattern can be + // used with any of the {get|put|post|delete|patch} methods. A custom method + // can be defined using the 'custom' field. + oneof pattern { + // Maps to HTTP GET. Used for listing and getting information about + // resources. + string get = 2; + + // Maps to HTTP PUT. Used for replacing a resource. + string put = 3; + + // Maps to HTTP POST. Used for creating a resource or performing an action. + string post = 4; + + // Maps to HTTP DELETE. Used for deleting a resource. + string delete = 5; + + // Maps to HTTP PATCH. Used for updating a resource. + string patch = 6; + + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. + CustomHttpPattern custom = 8; + } + + // The name of the request field whose value is mapped to the HTTP request + // body, or `*` for mapping all request fields not captured by the path + // pattern to the HTTP body, or omitted for not having any HTTP request body. + // + // NOTE: the referred field must be present at the top-level of the request + // message type. + string body = 7; + + // Optional. The name of the response field whose value is mapped to the HTTP + // response body. When omitted, the entire response message will be used + // as the HTTP response body. + // + // NOTE: The referred field must be present at the top-level of the response + // message type. + string response_body = 12; + + // Additional HTTP bindings for the selector. Nested bindings must + // not contain an `additional_bindings` field themselves (that is, + // the nesting may only be one level deep). + repeated HttpRule additional_bindings = 11; +} + +// A custom pattern is used for defining custom HTTP verb. +message CustomHttpPattern { + // The name of this custom HTTP verb. + string kind = 1; + + // The path matched by this custom verb. + string path = 2; +} \ No newline at end of file diff --git a/seed/python-sdk/grpc-proto/.mock/proto/user/v1/user.proto b/seed/python-sdk/grpc-proto/.mock/proto/user/v1/user.proto new file mode 100644 index 00000000000..a3ad0195bd4 --- /dev/null +++ b/seed/python-sdk/grpc-proto/.mock/proto/user/v1/user.proto @@ -0,0 +1,39 @@ +syntax = "proto3"; + +package user.v1; + +import "google/api/annotations.proto"; +import "google/api/field_behavior.proto"; +import "google/protobuf/struct.proto"; + +option go_package = "user/v1"; +option csharp_namespace = "User.V1"; + +message UserModel { + string username = 1; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateRequest { + string username = 1; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateResponse { + UserModel user = 1; +} + +service User { + rpc Create(CreateRequest) returns (CreateResponse) { + option (google.api.http) = { + post: "/users" + body: "*" + }; + } +} \ No newline at end of file diff --git a/seed/python-sdk/grpc-proto/README.md b/seed/python-sdk/grpc-proto/README.md new file mode 100644 index 00000000000..26160379a53 --- /dev/null +++ b/seed/python-sdk/grpc-proto/README.md @@ -0,0 +1,130 @@ +# Seed Python Library + +[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-SDK%20generated%20by%20Fern-brightgreen)](https://github.com/fern-api/fern) +[![pypi](https://img.shields.io/pypi/v/fern_grpc-proto)](https://pypi.python.org/pypi/fern_grpc-proto) + +The Seed Python library provides convenient access to the Seed API from Python. + +## Installation + +```sh +pip install fern_grpc-proto +``` + +## Usage + +Instantiate and use the client with the following: + +```python +from seed import SeedApi + +client = SeedApi( + base_url="https://yourhost.com/path/to/api", +) +client.user.create() +``` + +## Async Client + +The SDK also exports an `async` client so that you can make non-blocking calls to our API. + +```python +import asyncio + +from seed import AsyncSeedApi + +client = AsyncSeedApi( + base_url="https://yourhost.com/path/to/api", +) + + +async def main() -> None: + await client.user.create() + + +asyncio.run(main()) +``` + +## Exception Handling + +When the API returns a non-success status code (4xx or 5xx response), a subclass of the following error +will be thrown. + +```python +from seed.core.api_error import ApiError + +try: + client.user.create() +except ApiError as e: + print(e.status_code) + print(e.body) +``` + +## Advanced + +### Retries + +The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long +as the request is deemed retriable and the number of retry attempts has not grown larger than the configured +retry limit (default: 2). + +A request is deemed retriable when any of the following HTTP status codes is returned: + +- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) +- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) +- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) + +Use the `max_retries` request option to configure this behavior. + +```python +client.user.create({ + "max_retries": 1 +}) +``` + +### Timeouts + +The SDK defaults to a 60 second timeout. You can configure this with a timeout option at the client or request level. + +```python + +from seed import SeedApi + +client = SeedApi( + ..., + timeout=20.0, +) + + +# Override timeout for a specific method +client.user.create({ + "timeout_in_seconds": 1 +}) +``` + +### Custom Client + +You can override the `httpx` client to customize it for your use-case. Some common use-cases include support for proxies +and transports. +```python +import httpx +from seed import SeedApi + +client = SeedApi( + ..., + httpx_client=httpx.Client( + proxies="http://my.test.proxy.example.com", + transport=httpx.HTTPTransport(local_address="0.0.0.0"), + ), +) +``` + +## Contributing + +While we value open-source contributions to this SDK, this library is generated programmatically. +Additions made directly to this library would have to be moved over to our generation code, +otherwise they would be overwritten upon the next generated release. Feel free to open a PR as +a proof of concept, but know that we will not be able to merge it as-is. We suggest opening +an issue first to discuss with us! + +On the other hand, contributions to the README are always very welcome! diff --git a/seed/python-sdk/grpc-proto/pyproject.toml b/seed/python-sdk/grpc-proto/pyproject.toml new file mode 100644 index 00000000000..fbdb348272d --- /dev/null +++ b/seed/python-sdk/grpc-proto/pyproject.toml @@ -0,0 +1,57 @@ +[tool.poetry] +name = "fern_grpc-proto" +version = "0.0.1" +description = "" +readme = "README.md" +authors = [] +keywords = [] + +classifiers = [ + "Intended Audience :: Developers", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Operating System :: OS Independent", + "Operating System :: POSIX", + "Operating System :: MacOS", + "Operating System :: POSIX :: Linux", + "Operating System :: Microsoft :: Windows", + "Topic :: Software Development :: Libraries :: Python Modules", + "Typing :: Typed" +] +packages = [ + { include = "seed", from = "src"} +] + +[project.urls] +Repository = 'https://github.com/grpc-proto/fern' + +[tool.poetry.dependencies] +python = "^3.8" +httpx = ">=0.21.2" +pydantic = ">= 1.9.2" +pydantic-core = "^2.18.2" +typing_extensions = ">= 4.0.0" + +[tool.poetry.dev-dependencies] +mypy = "1.0.1" +pytest = "^7.4.0" +pytest-asyncio = "^0.23.5" +python-dateutil = "^2.9.0" +types-python-dateutil = "^2.9.0.20240316" + +[tool.pytest.ini_options] +testpaths = [ "tests" ] +asyncio_mode = "auto" + +[tool.mypy] +plugins = ["pydantic.mypy"] + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/seed/python-sdk/grpc-proto/reference.md b/seed/python-sdk/grpc-proto/reference.md new file mode 100644 index 00000000000..c1355e9424a --- /dev/null +++ b/seed/python-sdk/grpc-proto/reference.md @@ -0,0 +1,88 @@ +# Reference +## User +

client.user.create(...) +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from seed import SeedApi + +client = SeedApi( + base_url="https://yourhost.com/path/to/api", +) +client.user.create() + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**username:** `typing.Optional[str]` + +
+
+ +
+
+ +**email:** `typing.Optional[str]` + +
+
+ +
+
+ +**age:** `typing.Optional[int]` + +
+
+ +
+
+ +**weight:** `typing.Optional[float]` + +
+
+ +
+
+ +**metadata:** `typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]]` + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ diff --git a/seed/python-sdk/grpc-proto/snippet-templates.json b/seed/python-sdk/grpc-proto/snippet-templates.json new file mode 100644 index 00000000000..9cfdee01acf --- /dev/null +++ b/seed/python-sdk/grpc-proto/snippet-templates.json @@ -0,0 +1,270 @@ +[ + { + "sdk": { + "package": "fern_grpc-proto", + "version": "0.0.1", + "type": "python" + }, + "endpointId": { + "path": "/users", + "method": "POST", + "identifierOverride": "endpoint_user.create" + }, + "snippetTemplate": { + "clientInstantiation": { + "imports": [ + "from seed import SeedApi" + ], + "isOptional": true, + "templateString": "client = SeedApi(\n base_url=\"https://yourhost.com/path/to/api\",\n)", + "templateInputs": [], + "inputDelimiter": ",", + "type": "generic" + }, + "functionInvocation": { + "imports": [], + "isOptional": true, + "templateString": "client.user.create(\n\t$FERN_INPUT\n)", + "templateInputs": [ + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "username=$FERN_INPUT", + "templateInputs": [ + { + "location": "BODY", + "path": "username", + "type": "payload" + } + ], + "type": "generic" + } + }, + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "email=$FERN_INPUT", + "templateInputs": [ + { + "location": "BODY", + "path": "email", + "type": "payload" + } + ], + "type": "generic" + } + }, + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "age=$FERN_INPUT", + "templateInputs": [ + { + "location": "BODY", + "path": "age", + "type": "payload" + } + ], + "type": "generic" + } + }, + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "weight=$FERN_INPUT", + "templateInputs": [ + { + "location": "BODY", + "path": "weight", + "type": "payload" + } + ], + "type": "generic" + } + }, + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "containerTemplateString": "metadata={\n\t\t$FERN_INPUT\n\t}", + "delimiter": ",\n\t\t", + "keyTemplate": { + "imports": [], + "isOptional": true, + "templateString": "$FERN_INPUT", + "templateInputs": [ + { + "location": "RELATIVE", + "path": null, + "type": "payload" + } + ], + "type": "generic" + }, + "valueTemplate": { + "imports": [], + "isOptional": true, + "templateString": "$FERN_INPUT", + "templateInputs": [ + { + "location": "RELATIVE", + "path": null, + "type": "payload" + } + ], + "type": "generic" + }, + "keyValueSeparator": ": ", + "templateInput": { + "location": "BODY", + "path": "metadata" + }, + "type": "dict" + } + } + ], + "inputDelimiter": ",\n\t", + "type": "generic" + }, + "type": "v1" + }, + "additionalTemplates": { + "async": { + "clientInstantiation": { + "imports": [ + "from seed import AsyncSeedApi" + ], + "isOptional": true, + "templateString": "client = AsyncSeedApi(\n base_url=\"https://yourhost.com/path/to/api\",\n)", + "templateInputs": [], + "inputDelimiter": ",", + "type": "generic" + }, + "functionInvocation": { + "imports": [], + "isOptional": true, + "templateString": "await client.user.create(\n\t$FERN_INPUT\n)", + "templateInputs": [ + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "username=$FERN_INPUT", + "templateInputs": [ + { + "location": "BODY", + "path": "username", + "type": "payload" + } + ], + "type": "generic" + } + }, + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "email=$FERN_INPUT", + "templateInputs": [ + { + "location": "BODY", + "path": "email", + "type": "payload" + } + ], + "type": "generic" + } + }, + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "age=$FERN_INPUT", + "templateInputs": [ + { + "location": "BODY", + "path": "age", + "type": "payload" + } + ], + "type": "generic" + } + }, + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "weight=$FERN_INPUT", + "templateInputs": [ + { + "location": "BODY", + "path": "weight", + "type": "payload" + } + ], + "type": "generic" + } + }, + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "containerTemplateString": "metadata={\n\t\t$FERN_INPUT\n\t}", + "delimiter": ",\n\t\t", + "keyTemplate": { + "imports": [], + "isOptional": true, + "templateString": "$FERN_INPUT", + "templateInputs": [ + { + "location": "RELATIVE", + "path": null, + "type": "payload" + } + ], + "type": "generic" + }, + "valueTemplate": { + "imports": [], + "isOptional": true, + "templateString": "$FERN_INPUT", + "templateInputs": [ + { + "location": "RELATIVE", + "path": null, + "type": "payload" + } + ], + "type": "generic" + }, + "keyValueSeparator": ": ", + "templateInput": { + "location": "BODY", + "path": "metadata" + }, + "type": "dict" + } + } + ], + "inputDelimiter": ",\n\t", + "type": "generic" + }, + "type": "v1" + } + } + } +] \ No newline at end of file diff --git a/seed/python-sdk/grpc-proto/snippet.json b/seed/python-sdk/grpc-proto/snippet.json new file mode 100644 index 00000000000..5ae8b71b92f --- /dev/null +++ b/seed/python-sdk/grpc-proto/snippet.json @@ -0,0 +1,18 @@ +{ + "types": {}, + "endpoints": [ + { + "example_identifier": "default", + "id": { + "path": "/users", + "method": "POST", + "identifier_override": "endpoint_user.create" + }, + "snippet": { + "sync_client": "from seed import SeedApi\n\nclient = SeedApi(\n base_url=\"https://yourhost.com/path/to/api\",\n)\nclient.user.create()\n", + "async_client": "import asyncio\n\nfrom seed import AsyncSeedApi\n\nclient = AsyncSeedApi(\n base_url=\"https://yourhost.com/path/to/api\",\n)\n\n\nasync def main() -> None:\n await client.user.create()\n\n\nasyncio.run(main())\n", + "type": "python" + } + } + ] +} \ No newline at end of file diff --git a/seed/python-sdk/grpc-proto/src/seed/__init__.py b/seed/python-sdk/grpc-proto/src/seed/__init__.py new file mode 100644 index 00000000000..510feb0905d --- /dev/null +++ b/seed/python-sdk/grpc-proto/src/seed/__init__.py @@ -0,0 +1,8 @@ +# This file was auto-generated by Fern from our API Definition. + +from .types import CreateResponse, UserModel +from . import user +from .client import AsyncSeedApi, SeedApi +from .version import __version__ + +__all__ = ["AsyncSeedApi", "CreateResponse", "SeedApi", "UserModel", "__version__", "user"] diff --git a/seed/python-sdk/grpc-proto/src/seed/client.py b/seed/python-sdk/grpc-proto/src/seed/client.py new file mode 100644 index 00000000000..e5b9d9d875e --- /dev/null +++ b/seed/python-sdk/grpc-proto/src/seed/client.py @@ -0,0 +1,104 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import httpx + +from .core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from .user.client import AsyncUserClient, UserClient + + +class SeedApi: + """ + Use this class to access the different functions within the SDK. You can instantiate any number of clients with different configuration that will propagate to these functions. + + Parameters + ---------- + base_url : str + The base url to use for requests from the client. + + timeout : typing.Optional[float] + The timeout to be used, in seconds, for requests. By default the timeout is 60 seconds, unless a custom httpx client is used, in which case this default is not enforced. + + follow_redirects : typing.Optional[bool] + Whether the default httpx client follows redirects or not, this is irrelevant if a custom httpx client is passed in. + + httpx_client : typing.Optional[httpx.Client] + The httpx client to use for making requests, a preconfigured client is used by default, however this is useful should you want to pass in any custom httpx configuration. + + Examples + -------- + from seed import SeedApi + + client = SeedApi( + base_url="https://yourhost.com/path/to/api", + ) + """ + + def __init__( + self, + *, + base_url: str, + timeout: typing.Optional[float] = None, + follow_redirects: typing.Optional[bool] = True, + httpx_client: typing.Optional[httpx.Client] = None + ): + _defaulted_timeout = timeout if timeout is not None else 60 if httpx_client is None else None + self._client_wrapper = SyncClientWrapper( + base_url=base_url, + httpx_client=httpx_client + if httpx_client is not None + else httpx.Client(timeout=_defaulted_timeout, follow_redirects=follow_redirects) + if follow_redirects is not None + else httpx.Client(timeout=_defaulted_timeout), + timeout=_defaulted_timeout, + ) + self.user = UserClient(client_wrapper=self._client_wrapper) + + +class AsyncSeedApi: + """ + Use this class to access the different functions within the SDK. You can instantiate any number of clients with different configuration that will propagate to these functions. + + Parameters + ---------- + base_url : str + The base url to use for requests from the client. + + timeout : typing.Optional[float] + The timeout to be used, in seconds, for requests. By default the timeout is 60 seconds, unless a custom httpx client is used, in which case this default is not enforced. + + follow_redirects : typing.Optional[bool] + Whether the default httpx client follows redirects or not, this is irrelevant if a custom httpx client is passed in. + + httpx_client : typing.Optional[httpx.AsyncClient] + The httpx client to use for making requests, a preconfigured client is used by default, however this is useful should you want to pass in any custom httpx configuration. + + Examples + -------- + from seed import AsyncSeedApi + + client = AsyncSeedApi( + base_url="https://yourhost.com/path/to/api", + ) + """ + + def __init__( + self, + *, + base_url: str, + timeout: typing.Optional[float] = None, + follow_redirects: typing.Optional[bool] = True, + httpx_client: typing.Optional[httpx.AsyncClient] = None + ): + _defaulted_timeout = timeout if timeout is not None else 60 if httpx_client is None else None + self._client_wrapper = AsyncClientWrapper( + base_url=base_url, + httpx_client=httpx_client + if httpx_client is not None + else httpx.AsyncClient(timeout=_defaulted_timeout, follow_redirects=follow_redirects) + if follow_redirects is not None + else httpx.AsyncClient(timeout=_defaulted_timeout), + timeout=_defaulted_timeout, + ) + self.user = AsyncUserClient(client_wrapper=self._client_wrapper) diff --git a/seed/python-sdk/grpc-proto/src/seed/core/__init__.py b/seed/python-sdk/grpc-proto/src/seed/core/__init__.py new file mode 100644 index 00000000000..5a0bee343d0 --- /dev/null +++ b/seed/python-sdk/grpc-proto/src/seed/core/__init__.py @@ -0,0 +1,48 @@ +# This file was auto-generated by Fern from our API Definition. + +from .api_error import ApiError +from .client_wrapper import AsyncClientWrapper, BaseClientWrapper, SyncClientWrapper +from .datetime_utils import serialize_datetime +from .file import File, convert_file_dict_to_httpx_tuples +from .http_client import AsyncHttpClient, HttpClient +from .jsonable_encoder import jsonable_encoder +from .pydantic_utilities import ( + IS_PYDANTIC_V2, + UniversalBaseModel, + UniversalRootModel, + deep_union_pydantic_dicts, + parse_obj_as, + universal_field_validator, + universal_root_validator, + update_forward_refs, +) +from .query_encoder import encode_query +from .remove_none_from_dict import remove_none_from_dict +from .request_options import RequestOptions +from .serialization import FieldMetadata, convert_and_respect_annotation_metadata + +__all__ = [ + "ApiError", + "AsyncClientWrapper", + "AsyncHttpClient", + "BaseClientWrapper", + "FieldMetadata", + "File", + "HttpClient", + "IS_PYDANTIC_V2", + "RequestOptions", + "SyncClientWrapper", + "UniversalBaseModel", + "UniversalRootModel", + "convert_and_respect_annotation_metadata", + "convert_file_dict_to_httpx_tuples", + "deep_union_pydantic_dicts", + "encode_query", + "jsonable_encoder", + "parse_obj_as", + "remove_none_from_dict", + "serialize_datetime", + "universal_field_validator", + "universal_root_validator", + "update_forward_refs", +] diff --git a/seed/python-sdk/grpc-proto/src/seed/core/api_error.py b/seed/python-sdk/grpc-proto/src/seed/core/api_error.py new file mode 100644 index 00000000000..2e9fc5431cd --- /dev/null +++ b/seed/python-sdk/grpc-proto/src/seed/core/api_error.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + + +class ApiError(Exception): + status_code: typing.Optional[int] + body: typing.Any + + def __init__(self, *, status_code: typing.Optional[int] = None, body: typing.Any = None): + self.status_code = status_code + self.body = body + + def __str__(self) -> str: + return f"status_code: {self.status_code}, body: {self.body}" diff --git a/seed/python-sdk/grpc-proto/src/seed/core/client_wrapper.py b/seed/python-sdk/grpc-proto/src/seed/core/client_wrapper.py new file mode 100644 index 00000000000..709044aa330 --- /dev/null +++ b/seed/python-sdk/grpc-proto/src/seed/core/client_wrapper.py @@ -0,0 +1,49 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import httpx + +from .http_client import AsyncHttpClient, HttpClient + + +class BaseClientWrapper: + def __init__(self, *, base_url: str, timeout: typing.Optional[float] = None): + self._base_url = base_url + self._timeout = timeout + + def get_headers(self) -> typing.Dict[str, str]: + headers: typing.Dict[str, str] = { + "X-Fern-Language": "Python", + "X-Fern-SDK-Name": "fern_grpc-proto", + "X-Fern-SDK-Version": "0.0.1", + } + return headers + + def get_base_url(self) -> str: + return self._base_url + + def get_timeout(self) -> typing.Optional[float]: + return self._timeout + + +class SyncClientWrapper(BaseClientWrapper): + def __init__(self, *, base_url: str, timeout: typing.Optional[float] = None, httpx_client: httpx.Client): + super().__init__(base_url=base_url, timeout=timeout) + self.httpx_client = HttpClient( + httpx_client=httpx_client, + base_headers=self.get_headers(), + base_timeout=self.get_timeout(), + base_url=self.get_base_url(), + ) + + +class AsyncClientWrapper(BaseClientWrapper): + def __init__(self, *, base_url: str, timeout: typing.Optional[float] = None, httpx_client: httpx.AsyncClient): + super().__init__(base_url=base_url, timeout=timeout) + self.httpx_client = AsyncHttpClient( + httpx_client=httpx_client, + base_headers=self.get_headers(), + base_timeout=self.get_timeout(), + base_url=self.get_base_url(), + ) diff --git a/seed/python-sdk/grpc-proto/src/seed/core/datetime_utils.py b/seed/python-sdk/grpc-proto/src/seed/core/datetime_utils.py new file mode 100644 index 00000000000..7c9864a944c --- /dev/null +++ b/seed/python-sdk/grpc-proto/src/seed/core/datetime_utils.py @@ -0,0 +1,28 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt + + +def serialize_datetime(v: dt.datetime) -> str: + """ + Serialize a datetime including timezone info. + + Uses the timezone info provided if present, otherwise uses the current runtime's timezone info. + + UTC datetimes end in "Z" while all other timezones are represented as offset from UTC, e.g. +05:00. + """ + + def _serialize_zoned_datetime(v: dt.datetime) -> str: + if v.tzinfo is not None and v.tzinfo.tzname(None) == dt.timezone.utc.tzname(None): + # UTC is a special case where we use "Z" at the end instead of "+00:00" + return v.isoformat().replace("+00:00", "Z") + else: + # Delegate to the typical +/- offset format + return v.isoformat() + + if v.tzinfo is not None: + return _serialize_zoned_datetime(v) + else: + local_tz = dt.datetime.now().astimezone().tzinfo + localized_dt = v.replace(tzinfo=local_tz) + return _serialize_zoned_datetime(localized_dt) diff --git a/seed/python-sdk/grpc-proto/src/seed/core/file.py b/seed/python-sdk/grpc-proto/src/seed/core/file.py new file mode 100644 index 00000000000..cb0d40bbbf3 --- /dev/null +++ b/seed/python-sdk/grpc-proto/src/seed/core/file.py @@ -0,0 +1,38 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +# File typing inspired by the flexibility of types within the httpx library +# https://github.com/encode/httpx/blob/master/httpx/_types.py +FileContent = typing.Union[typing.IO[bytes], bytes, str] +File = typing.Union[ + # file (or bytes) + FileContent, + # (filename, file (or bytes)) + typing.Tuple[typing.Optional[str], FileContent], + # (filename, file (or bytes), content_type) + typing.Tuple[typing.Optional[str], FileContent, typing.Optional[str]], + # (filename, file (or bytes), content_type, headers) + typing.Tuple[typing.Optional[str], FileContent, typing.Optional[str], typing.Mapping[str, str]], +] + + +def convert_file_dict_to_httpx_tuples( + d: typing.Dict[str, typing.Union[File, typing.List[File]]] +) -> typing.List[typing.Tuple[str, File]]: + """ + The format we use is a list of tuples, where the first element is the + name of the file and the second is the file object. Typically HTTPX wants + a dict, but to be able to send lists of files, you have to use the list + approach (which also works for non-lists) + https://github.com/encode/httpx/pull/1032 + """ + + httpx_tuples = [] + for key, file_like in d.items(): + if isinstance(file_like, list): + for file_like_item in file_like: + httpx_tuples.append((key, file_like_item)) + else: + httpx_tuples.append((key, file_like)) + return httpx_tuples diff --git a/seed/python-sdk/grpc-proto/src/seed/core/http_client.py b/seed/python-sdk/grpc-proto/src/seed/core/http_client.py new file mode 100644 index 00000000000..356880bbc3e --- /dev/null +++ b/seed/python-sdk/grpc-proto/src/seed/core/http_client.py @@ -0,0 +1,476 @@ +# This file was auto-generated by Fern from our API Definition. + +import asyncio +import email.utils +import json +import re +import time +import typing +import urllib.parse +from contextlib import asynccontextmanager, contextmanager +from random import random + +import httpx + +from .file import File, convert_file_dict_to_httpx_tuples +from .jsonable_encoder import jsonable_encoder +from .query_encoder import encode_query +from .remove_none_from_dict import remove_none_from_dict +from .request_options import RequestOptions + +INITIAL_RETRY_DELAY_SECONDS = 0.5 +MAX_RETRY_DELAY_SECONDS = 10 +MAX_RETRY_DELAY_SECONDS_FROM_HEADER = 30 + + +def _parse_retry_after(response_headers: httpx.Headers) -> typing.Optional[float]: + """ + This function parses the `Retry-After` header in a HTTP response and returns the number of seconds to wait. + + Inspired by the urllib3 retry implementation. + """ + retry_after_ms = response_headers.get("retry-after-ms") + if retry_after_ms is not None: + try: + return int(retry_after_ms) / 1000 if retry_after_ms > 0 else 0 + except Exception: + pass + + retry_after = response_headers.get("retry-after") + if retry_after is None: + return None + + # Attempt to parse the header as an int. + if re.match(r"^\s*[0-9]+\s*$", retry_after): + seconds = float(retry_after) + # Fallback to parsing it as a date. + else: + retry_date_tuple = email.utils.parsedate_tz(retry_after) + if retry_date_tuple is None: + return None + if retry_date_tuple[9] is None: # Python 2 + # Assume UTC if no timezone was specified + # On Python2.7, parsedate_tz returns None for a timezone offset + # instead of 0 if no timezone is given, where mktime_tz treats + # a None timezone offset as local time. + retry_date_tuple = retry_date_tuple[:9] + (0,) + retry_date_tuple[10:] + + retry_date = email.utils.mktime_tz(retry_date_tuple) + seconds = retry_date - time.time() + + if seconds < 0: + seconds = 0 + + return seconds + + +def _retry_timeout(response: httpx.Response, retries: int) -> float: + """ + Determine the amount of time to wait before retrying a request. + This function begins by trying to parse a retry-after header from the response, and then proceeds to use exponential backoff + with a jitter to determine the number of seconds to wait. + """ + + # If the API asks us to wait a certain amount of time (and it's a reasonable amount), just do what it says. + retry_after = _parse_retry_after(response.headers) + if retry_after is not None and retry_after <= MAX_RETRY_DELAY_SECONDS_FROM_HEADER: + return retry_after + + # Apply exponential backoff, capped at MAX_RETRY_DELAY_SECONDS. + retry_delay = min(INITIAL_RETRY_DELAY_SECONDS * pow(2.0, retries), MAX_RETRY_DELAY_SECONDS) + + # Add a randomness / jitter to the retry delay to avoid overwhelming the server with retries. + timeout = retry_delay * (1 - 0.25 * random()) + return timeout if timeout >= 0 else 0 + + +def _should_retry(response: httpx.Response) -> bool: + retriable_400s = [429, 408, 409] + return response.status_code >= 500 or response.status_code in retriable_400s + + +def remove_omit_from_dict( + original: typing.Dict[str, typing.Optional[typing.Any]], omit: typing.Optional[typing.Any] +) -> typing.Dict[str, typing.Any]: + if omit is None: + return original + new: typing.Dict[str, typing.Any] = {} + for key, value in original.items(): + if value is not omit: + new[key] = value + return new + + +def maybe_filter_request_body( + data: typing.Optional[typing.Any], + request_options: typing.Optional[RequestOptions], + omit: typing.Optional[typing.Any], +) -> typing.Optional[typing.Any]: + if data is None: + return ( + jsonable_encoder(request_options.get("additional_body_parameters", {})) or {} + if request_options is not None + else None + ) + elif not isinstance(data, typing.Mapping): + data_content = jsonable_encoder(data) + else: + data_content = { + **(jsonable_encoder(remove_omit_from_dict(data, omit))), # type: ignore + **( + jsonable_encoder(request_options.get("additional_body_parameters", {})) or {} + if request_options is not None + else {} + ), + } + return data_content + + +# Abstracted out for testing purposes +def get_request_body( + *, + json: typing.Optional[typing.Any], + data: typing.Optional[typing.Any], + request_options: typing.Optional[RequestOptions], + omit: typing.Optional[typing.Any], +) -> typing.Tuple[typing.Optional[typing.Any], typing.Optional[typing.Any]]: + json_body = None + data_body = None + if data is not None: + data_body = maybe_filter_request_body(data, request_options, omit) + else: + # If both data and json are None, we send json data in the event extra properties are specified + json_body = maybe_filter_request_body(json, request_options, omit) + + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None + + +class HttpClient: + def __init__( + self, + *, + httpx_client: httpx.Client, + base_timeout: typing.Optional[float], + base_headers: typing.Dict[str, str], + base_url: typing.Optional[str] = None, + ): + self.base_url = base_url + self.base_timeout = base_timeout + self.base_headers = base_headers + self.httpx_client = httpx_client + + def get_base_url(self, maybe_base_url: typing.Optional[str]) -> str: + base_url = self.base_url if maybe_base_url is None else maybe_base_url + if base_url is None: + raise ValueError("A base_url is required to make this request, please provide one and try again.") + return base_url + + def request( + self, + path: typing.Optional[str] = None, + *, + method: str, + base_url: typing.Optional[str] = None, + params: typing.Optional[typing.Dict[str, typing.Any]] = None, + json: typing.Optional[typing.Any] = None, + data: typing.Optional[typing.Any] = None, + content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None, + files: typing.Optional[typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]]] = None, + headers: typing.Optional[typing.Dict[str, typing.Any]] = None, + request_options: typing.Optional[RequestOptions] = None, + retries: int = 0, + omit: typing.Optional[typing.Any] = None, + ) -> httpx.Response: + base_url = self.get_base_url(base_url) + timeout = ( + request_options.get("timeout_in_seconds") + if request_options is not None and request_options.get("timeout_in_seconds") is not None + else self.base_timeout + ) + + json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit) + + response = self.httpx_client.request( + method=method, + url=urllib.parse.urljoin(f"{base_url}/", path), + headers=jsonable_encoder( + remove_none_from_dict( + { + **self.base_headers, + **(headers if headers is not None else {}), + **(request_options.get("additional_headers", {}) or {} if request_options is not None else {}), + } + ) + ), + params=encode_query( + jsonable_encoder( + remove_none_from_dict( + remove_omit_from_dict( + { + **(params if params is not None else {}), + **( + request_options.get("additional_query_parameters", {}) or {} + if request_options is not None + else {} + ), + }, + omit, + ) + ) + ) + ), + json=json_body, + data=data_body, + content=content, + files=convert_file_dict_to_httpx_tuples(remove_none_from_dict(files)) if files is not None else None, + timeout=timeout, + ) + + max_retries: int = request_options.get("max_retries", 0) if request_options is not None else 0 + if _should_retry(response=response): + if max_retries > retries: + time.sleep(_retry_timeout(response=response, retries=retries)) + return self.request( + path=path, + method=method, + base_url=base_url, + params=params, + json=json, + content=content, + files=files, + headers=headers, + request_options=request_options, + retries=retries + 1, + omit=omit, + ) + + return response + + @contextmanager + def stream( + self, + path: typing.Optional[str] = None, + *, + method: str, + base_url: typing.Optional[str] = None, + params: typing.Optional[typing.Dict[str, typing.Any]] = None, + json: typing.Optional[typing.Any] = None, + data: typing.Optional[typing.Any] = None, + content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None, + files: typing.Optional[typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]]] = None, + headers: typing.Optional[typing.Dict[str, typing.Any]] = None, + request_options: typing.Optional[RequestOptions] = None, + retries: int = 0, + omit: typing.Optional[typing.Any] = None, + ) -> typing.Iterator[httpx.Response]: + base_url = self.get_base_url(base_url) + timeout = ( + request_options.get("timeout_in_seconds") + if request_options is not None and request_options.get("timeout_in_seconds") is not None + else self.base_timeout + ) + + json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit) + + with self.httpx_client.stream( + method=method, + url=urllib.parse.urljoin(f"{base_url}/", path), + headers=jsonable_encoder( + remove_none_from_dict( + { + **self.base_headers, + **(headers if headers is not None else {}), + **(request_options.get("additional_headers", {}) if request_options is not None else {}), + } + ) + ), + params=encode_query( + jsonable_encoder( + remove_none_from_dict( + remove_omit_from_dict( + { + **(params if params is not None else {}), + **( + request_options.get("additional_query_parameters", {}) + if request_options is not None + else {} + ), + }, + omit, + ) + ) + ) + ), + json=json_body, + data=data_body, + content=content, + files=convert_file_dict_to_httpx_tuples(remove_none_from_dict(files)) if files is not None else None, + timeout=timeout, + ) as stream: + yield stream + + +class AsyncHttpClient: + def __init__( + self, + *, + httpx_client: httpx.AsyncClient, + base_timeout: typing.Optional[float], + base_headers: typing.Dict[str, str], + base_url: typing.Optional[str] = None, + ): + self.base_url = base_url + self.base_timeout = base_timeout + self.base_headers = base_headers + self.httpx_client = httpx_client + + def get_base_url(self, maybe_base_url: typing.Optional[str]) -> str: + base_url = self.base_url if maybe_base_url is None else maybe_base_url + if base_url is None: + raise ValueError("A base_url is required to make this request, please provide one and try again.") + return base_url + + async def request( + self, + path: typing.Optional[str] = None, + *, + method: str, + base_url: typing.Optional[str] = None, + params: typing.Optional[typing.Dict[str, typing.Any]] = None, + json: typing.Optional[typing.Any] = None, + data: typing.Optional[typing.Any] = None, + content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None, + files: typing.Optional[typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]]] = None, + headers: typing.Optional[typing.Dict[str, typing.Any]] = None, + request_options: typing.Optional[RequestOptions] = None, + retries: int = 0, + omit: typing.Optional[typing.Any] = None, + ) -> httpx.Response: + base_url = self.get_base_url(base_url) + timeout = ( + request_options.get("timeout_in_seconds") + if request_options is not None and request_options.get("timeout_in_seconds") is not None + else self.base_timeout + ) + + json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit) + + # Add the input to each of these and do None-safety checks + response = await self.httpx_client.request( + method=method, + url=urllib.parse.urljoin(f"{base_url}/", path), + headers=jsonable_encoder( + remove_none_from_dict( + { + **self.base_headers, + **(headers if headers is not None else {}), + **(request_options.get("additional_headers", {}) or {} if request_options is not None else {}), + } + ) + ), + params=encode_query( + jsonable_encoder( + remove_none_from_dict( + remove_omit_from_dict( + { + **(params if params is not None else {}), + **( + request_options.get("additional_query_parameters", {}) or {} + if request_options is not None + else {} + ), + }, + omit, + ) + ) + ) + ), + json=json_body, + data=data_body, + content=content, + files=convert_file_dict_to_httpx_tuples(remove_none_from_dict(files)) if files is not None else None, + timeout=timeout, + ) + + max_retries: int = request_options.get("max_retries", 0) if request_options is not None else 0 + if _should_retry(response=response): + if max_retries > retries: + await asyncio.sleep(_retry_timeout(response=response, retries=retries)) + return await self.request( + path=path, + method=method, + base_url=base_url, + params=params, + json=json, + content=content, + files=files, + headers=headers, + request_options=request_options, + retries=retries + 1, + omit=omit, + ) + return response + + @asynccontextmanager + async def stream( + self, + path: typing.Optional[str] = None, + *, + method: str, + base_url: typing.Optional[str] = None, + params: typing.Optional[typing.Dict[str, typing.Any]] = None, + json: typing.Optional[typing.Any] = None, + data: typing.Optional[typing.Any] = None, + content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None, + files: typing.Optional[typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]]] = None, + headers: typing.Optional[typing.Dict[str, typing.Any]] = None, + request_options: typing.Optional[RequestOptions] = None, + retries: int = 0, + omit: typing.Optional[typing.Any] = None, + ) -> typing.AsyncIterator[httpx.Response]: + base_url = self.get_base_url(base_url) + timeout = ( + request_options.get("timeout_in_seconds") + if request_options is not None and request_options.get("timeout_in_seconds") is not None + else self.base_timeout + ) + + json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit) + + async with self.httpx_client.stream( + method=method, + url=urllib.parse.urljoin(f"{base_url}/", path), + headers=jsonable_encoder( + remove_none_from_dict( + { + **self.base_headers, + **(headers if headers is not None else {}), + **(request_options.get("additional_headers", {}) if request_options is not None else {}), + } + ) + ), + params=encode_query( + jsonable_encoder( + remove_none_from_dict( + remove_omit_from_dict( + { + **(params if params is not None else {}), + **( + request_options.get("additional_query_parameters", {}) + if request_options is not None + else {} + ), + }, + omit=omit, + ) + ) + ) + ), + json=json_body, + data=data_body, + content=content, + files=convert_file_dict_to_httpx_tuples(remove_none_from_dict(files)) if files is not None else None, + timeout=timeout, + ) as stream: + yield stream diff --git a/seed/python-sdk/grpc-proto/src/seed/core/jsonable_encoder.py b/seed/python-sdk/grpc-proto/src/seed/core/jsonable_encoder.py new file mode 100644 index 00000000000..d3fd328fd41 --- /dev/null +++ b/seed/python-sdk/grpc-proto/src/seed/core/jsonable_encoder.py @@ -0,0 +1,97 @@ +# This file was auto-generated by Fern from our API Definition. + +""" +jsonable_encoder converts a Python object to a JSON-friendly dict +(e.g. datetimes to strings, Pydantic models to dicts). + +Taken from FastAPI, and made a bit simpler +https://github.com/tiangolo/fastapi/blob/master/fastapi/encoders.py +""" + +import base64 +import dataclasses +import datetime as dt +from enum import Enum +from pathlib import PurePath +from types import GeneratorType +from typing import Any, Callable, Dict, List, Optional, Set, Union + +import pydantic + +from .datetime_utils import serialize_datetime +from .pydantic_utilities import IS_PYDANTIC_V2, encode_by_type, to_jsonable_with_fallback + +SetIntStr = Set[Union[int, str]] +DictIntStrAny = Dict[Union[int, str], Any] + + +def jsonable_encoder(obj: Any, custom_encoder: Optional[Dict[Any, Callable[[Any], Any]]] = None) -> Any: + custom_encoder = custom_encoder or {} + if custom_encoder: + if type(obj) in custom_encoder: + return custom_encoder[type(obj)](obj) + else: + for encoder_type, encoder_instance in custom_encoder.items(): + if isinstance(obj, encoder_type): + return encoder_instance(obj) + if isinstance(obj, pydantic.BaseModel): + if IS_PYDANTIC_V2: + encoder = getattr(obj.model_config, "json_encoders", {}) # type: ignore # Pydantic v2 + else: + encoder = getattr(obj.__config__, "json_encoders", {}) # type: ignore # Pydantic v1 + if custom_encoder: + encoder.update(custom_encoder) + obj_dict = obj.dict(by_alias=True) + if "__root__" in obj_dict: + obj_dict = obj_dict["__root__"] + if "root" in obj_dict: + obj_dict = obj_dict["root"] + return jsonable_encoder(obj_dict, custom_encoder=encoder) + if dataclasses.is_dataclass(obj): + obj_dict = dataclasses.asdict(obj) # type: ignore + return jsonable_encoder(obj_dict, custom_encoder=custom_encoder) + if isinstance(obj, bytes): + return base64.b64encode(obj).decode("utf-8") + if isinstance(obj, Enum): + return obj.value + if isinstance(obj, PurePath): + return str(obj) + if isinstance(obj, (str, int, float, type(None))): + return obj + if isinstance(obj, dt.datetime): + return serialize_datetime(obj) + if isinstance(obj, dt.date): + return str(obj) + if isinstance(obj, dict): + encoded_dict = {} + allowed_keys = set(obj.keys()) + for key, value in obj.items(): + if key in allowed_keys: + encoded_key = jsonable_encoder(key, custom_encoder=custom_encoder) + encoded_value = jsonable_encoder(value, custom_encoder=custom_encoder) + encoded_dict[encoded_key] = encoded_value + return encoded_dict + if isinstance(obj, (list, set, frozenset, GeneratorType, tuple)): + encoded_list = [] + for item in obj: + encoded_list.append(jsonable_encoder(item, custom_encoder=custom_encoder)) + return encoded_list + + def fallback_serializer(o: Any) -> Any: + attempt_encode = encode_by_type(o) + if attempt_encode is not None: + return attempt_encode + + try: + data = dict(o) + except Exception as e: + errors: List[Exception] = [] + errors.append(e) + try: + data = vars(o) + except Exception as e: + errors.append(e) + raise ValueError(errors) from e + return jsonable_encoder(data, custom_encoder=custom_encoder) + + return to_jsonable_with_fallback(obj, fallback_serializer) diff --git a/seed/python-sdk/grpc-proto/src/seed/core/pydantic_utilities.py b/seed/python-sdk/grpc-proto/src/seed/core/pydantic_utilities.py new file mode 100644 index 00000000000..d7fb87bf581 --- /dev/null +++ b/seed/python-sdk/grpc-proto/src/seed/core/pydantic_utilities.py @@ -0,0 +1,179 @@ +# This file was auto-generated by Fern from our API Definition. + +# nopycln: file +import datetime as dt +import typing +from collections import defaultdict +from functools import wraps + +import pydantic + +from .datetime_utils import serialize_datetime + +IS_PYDANTIC_V2 = pydantic.VERSION.startswith("2.") + +if IS_PYDANTIC_V2: + # isort will try to reformat the comments on these imports, which breaks mypy + # isort: off + from pydantic.v1.datetime_parse import ( # type: ignore # pyright: ignore[reportMissingImports] # Pydantic v2 + parse_date as parse_date, + ) + from pydantic.v1.datetime_parse import ( # pyright: ignore[reportMissingImports] # Pydantic v2 + parse_datetime as parse_datetime, + ) + from pydantic.v1.json import ( # type: ignore # pyright: ignore[reportMissingImports] # Pydantic v2 + ENCODERS_BY_TYPE as encoders_by_type, + ) + from pydantic.v1.typing import ( # type: ignore # pyright: ignore[reportMissingImports] # Pydantic v2 + get_args as get_args, + ) + from pydantic.v1.typing import get_origin as get_origin # pyright: ignore[reportMissingImports] # Pydantic v2 + from pydantic.v1.typing import ( # pyright: ignore[reportMissingImports] # Pydantic v2 + is_literal_type as is_literal_type, + ) + from pydantic.v1.typing import is_union as is_union # pyright: ignore[reportMissingImports] # Pydantic v2 + from pydantic.v1.fields import ModelField as ModelField # type: ignore # pyright: ignore[reportMissingImports] # Pydantic v2 +else: + from pydantic.datetime_parse import parse_date as parse_date # type: ignore # Pydantic v1 + from pydantic.datetime_parse import parse_datetime as parse_datetime # type: ignore # Pydantic v1 + from pydantic.fields import ModelField as ModelField # type: ignore # Pydantic v1 + from pydantic.json import ENCODERS_BY_TYPE as encoders_by_type # type: ignore # Pydantic v1 + from pydantic.typing import get_args as get_args # type: ignore # Pydantic v1 + from pydantic.typing import get_origin as get_origin # type: ignore # Pydantic v1 + from pydantic.typing import is_literal_type as is_literal_type # type: ignore # Pydantic v1 + from pydantic.typing import is_union as is_union # type: ignore # Pydantic v1 + + # isort: on + + +T = typing.TypeVar("T") +Model = typing.TypeVar("Model", bound=pydantic.BaseModel) + + +def deep_union_pydantic_dicts( + source: typing.Dict[str, typing.Any], destination: typing.Dict[str, typing.Any] +) -> typing.Dict[str, typing.Any]: + for key, value in source.items(): + if isinstance(value, dict): + node = destination.setdefault(key, {}) + deep_union_pydantic_dicts(value, node) + else: + destination[key] = value + + return destination + + +def parse_obj_as(type_: typing.Type[T], object_: typing.Any) -> T: + if IS_PYDANTIC_V2: + adapter = pydantic.TypeAdapter(type_) # type: ignore # Pydantic v2 + return adapter.validate_python(object_) + else: + return pydantic.parse_obj_as(type_, object_) + + +def to_jsonable_with_fallback( + obj: typing.Any, fallback_serializer: typing.Callable[[typing.Any], typing.Any] +) -> typing.Any: + if IS_PYDANTIC_V2: + from pydantic_core import to_jsonable_python + + return to_jsonable_python(obj, fallback=fallback_serializer) + else: + return fallback_serializer(obj) + + +class UniversalBaseModel(pydantic.BaseModel): + class Config: + populate_by_name = True + smart_union = True + allow_population_by_field_name = True + json_encoders = {dt.datetime: serialize_datetime} + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = {"by_alias": True, "exclude_unset": True, **kwargs} + if IS_PYDANTIC_V2: + return super().model_dump_json(**kwargs_with_defaults) # type: ignore # Pydantic v2 + else: + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + kwargs_with_defaults_exclude_unset: typing.Any = {"by_alias": True, "exclude_unset": True, **kwargs} + kwargs_with_defaults_exclude_none: typing.Any = {"by_alias": True, "exclude_none": True, **kwargs} + + if IS_PYDANTIC_V2: + return deep_union_pydantic_dicts( + super().model_dump(**kwargs_with_defaults_exclude_unset), # type: ignore # Pydantic v2 + super().model_dump(**kwargs_with_defaults_exclude_none), # type: ignore # Pydantic v2 + ) + else: + return deep_union_pydantic_dicts( + super().dict(**kwargs_with_defaults_exclude_unset), super().dict(**kwargs_with_defaults_exclude_none) + ) + + +UniversalRootModel: typing.Type[pydantic.BaseModel] +if IS_PYDANTIC_V2: + + class V2RootModel(UniversalBaseModel, pydantic.RootModel): # type: ignore # Pydantic v2 + pass + + UniversalRootModel = V2RootModel +else: + UniversalRootModel = UniversalBaseModel + + +def encode_by_type(o: typing.Any) -> typing.Any: + encoders_by_class_tuples: typing.Dict[ + typing.Callable[[typing.Any], typing.Any], typing.Tuple[typing.Any, ...] + ] = defaultdict(tuple) + for type_, encoder in encoders_by_type.items(): + encoders_by_class_tuples[encoder] += (type_,) + + if type(o) in encoders_by_type: + return encoders_by_type[type(o)](o) + for encoder, classes_tuple in encoders_by_class_tuples.items(): + if isinstance(o, classes_tuple): + return encoder(o) + + +def update_forward_refs(model: typing.Type["Model"], **localns: typing.Any) -> None: + if IS_PYDANTIC_V2: + model.model_rebuild(force=True, raise_errors=False) # type: ignore # Pydantic v2 + else: + model.update_forward_refs(**localns) + + +# Mirrors Pydantic's internal typing +AnyCallable = typing.Callable[..., typing.Any] + + +def universal_root_validator(pre: bool = False) -> typing.Callable[[AnyCallable], AnyCallable]: + def decorator(func: AnyCallable) -> AnyCallable: + @wraps(func) + def validate(*args: typing.Any, **kwargs: typing.Any) -> AnyCallable: + if IS_PYDANTIC_V2: + wrapped_func = pydantic.model_validator("before" if pre else "after")(func) # type: ignore # Pydantic v2 + else: + wrapped_func = pydantic.root_validator(pre=pre)(func) # type: ignore # Pydantic v1 + + return wrapped_func(*args, **kwargs) # type: ignore # Pydantic v2 + + return validate + + return decorator + + +def universal_field_validator(field_name: str, pre: bool = False) -> typing.Callable[[AnyCallable], AnyCallable]: + def decorator(func: AnyCallable) -> AnyCallable: + @wraps(func) + def validate(*args: typing.Any, **kwargs: typing.Any) -> AnyCallable: + if IS_PYDANTIC_V2: + wrapped_func = pydantic.field_validator(field_name, mode="before" if pre else "after")(func) # type: ignore # Pydantic v2 + else: + wrapped_func = pydantic.validator(field_name, pre=pre)(func) # type: ignore # Pydantic v1 + + return wrapped_func(*args, **kwargs) + + return validate + + return decorator diff --git a/seed/python-sdk/grpc-proto/src/seed/core/query_encoder.py b/seed/python-sdk/grpc-proto/src/seed/core/query_encoder.py new file mode 100644 index 00000000000..24076d72ee9 --- /dev/null +++ b/seed/python-sdk/grpc-proto/src/seed/core/query_encoder.py @@ -0,0 +1,33 @@ +# This file was auto-generated by Fern from our API Definition. + +from collections import ChainMap +from typing import Any, Dict, Optional + +import pydantic + + +# Flattens dicts to be of the form {"key[subkey][subkey2]": value} where value is not a dict +def traverse_query_dict(dict_flat: Dict[str, Any], key_prefix: Optional[str] = None) -> Dict[str, Any]: + result = {} + for k, v in dict_flat.items(): + key = f"{key_prefix}[{k}]" if key_prefix is not None else k + if isinstance(v, dict): + result.update(traverse_query_dict(v, key)) + else: + result[key] = v + return result + + +def single_query_encoder(query_key: str, query_value: Any) -> Dict[str, Any]: + if isinstance(query_value, pydantic.BaseModel) or isinstance(query_value, dict): + if isinstance(query_value, pydantic.BaseModel): + obj_dict = query_value.dict(by_alias=True) + else: + obj_dict = query_value + return traverse_query_dict(obj_dict, query_key) + + return {query_key: query_value} + + +def encode_query(query: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]: + return dict(ChainMap(*[single_query_encoder(k, v) for k, v in query.items()])) if query is not None else None diff --git a/seed/python-sdk/grpc-proto/src/seed/core/remove_none_from_dict.py b/seed/python-sdk/grpc-proto/src/seed/core/remove_none_from_dict.py new file mode 100644 index 00000000000..c2298143f14 --- /dev/null +++ b/seed/python-sdk/grpc-proto/src/seed/core/remove_none_from_dict.py @@ -0,0 +1,11 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import Any, Dict, Mapping, Optional + + +def remove_none_from_dict(original: Mapping[str, Optional[Any]]) -> Dict[str, Any]: + new: Dict[str, Any] = {} + for key, value in original.items(): + if value is not None: + new[key] = value + return new diff --git a/seed/python-sdk/grpc-proto/src/seed/core/request_options.py b/seed/python-sdk/grpc-proto/src/seed/core/request_options.py new file mode 100644 index 00000000000..d0bf0dbcecd --- /dev/null +++ b/seed/python-sdk/grpc-proto/src/seed/core/request_options.py @@ -0,0 +1,32 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +try: + from typing import NotRequired # type: ignore +except ImportError: + from typing_extensions import NotRequired + + +class RequestOptions(typing.TypedDict, total=False): + """ + Additional options for request-specific configuration when calling APIs via the SDK. + This is used primarily as an optional final parameter for service functions. + + Attributes: + - timeout_in_seconds: int. The number of seconds to await an API call before timing out. + + - max_retries: int. The max number of retries to attempt if the API call fails. + + - additional_headers: typing.Dict[str, typing.Any]. A dictionary containing additional parameters to spread into the request's header dict + + - additional_query_parameters: typing.Dict[str, typing.Any]. A dictionary containing additional parameters to spread into the request's query parameters dict + + - additional_body_parameters: typing.Dict[str, typing.Any]. A dictionary containing additional parameters to spread into the request's body parameters dict + """ + + timeout_in_seconds: NotRequired[int] + max_retries: NotRequired[int] + additional_headers: NotRequired[typing.Dict[str, typing.Any]] + additional_query_parameters: NotRequired[typing.Dict[str, typing.Any]] + additional_body_parameters: NotRequired[typing.Dict[str, typing.Any]] diff --git a/seed/python-sdk/grpc-proto/src/seed/core/serialization.py b/seed/python-sdk/grpc-proto/src/seed/core/serialization.py new file mode 100644 index 00000000000..8ad5cf8125f --- /dev/null +++ b/seed/python-sdk/grpc-proto/src/seed/core/serialization.py @@ -0,0 +1,167 @@ +# This file was auto-generated by Fern from our API Definition. + +import collections +import typing + +import typing_extensions + + +class FieldMetadata: + """ + Metadata class used to annotate fields to provide additional information. + + Example: + class MyDict(TypedDict): + field: typing.Annotated[str, FieldMetadata(alias="field_name")] + + Will serialize: `{"field": "value"}` + To: `{"field_name": "value"}` + """ + + alias: str + + def __init__(self, *, alias: str) -> None: + self.alias = alias + + +def convert_and_respect_annotation_metadata( + *, object_: typing.Any, annotation: typing.Any, inner_type: typing.Optional[typing.Any] = None +) -> typing.Any: + """ + Respect the metadata annotations on a field, such as aliasing. This function effectively + manipulates the dict-form of an object to respect the metadata annotations. This is primarily used for + TypedDicts, which cannot support aliasing out of the box, and can be extended for additional + utilities, such as defaults. + + Parameters + ---------- + object_ : typing.Any + + annotation : type + The type we're looking to apply typing annotations from + + inner_type : typing.Optional[type] + + Returns + ------- + typing.Any + """ + + if object_ is None: + return None + if inner_type is None: + inner_type = annotation + + clean_type = _remove_annotations(inner_type) + if typing_extensions.is_typeddict(clean_type) and isinstance(object_, typing.Mapping): + return _convert_typeddict(object_, clean_type) + + if ( + # If you're iterating on a string, do not bother to coerce it to a sequence. + (not isinstance(object_, str)) + and ( + ( + ( + typing_extensions.get_origin(clean_type) == typing.List + or typing_extensions.get_origin(clean_type) == list + or clean_type == typing.List + ) + and isinstance(object_, typing.List) + ) + or ( + ( + typing_extensions.get_origin(clean_type) == typing.Set + or typing_extensions.get_origin(clean_type) == set + or clean_type == typing.Set + ) + and isinstance(object_, typing.Set) + ) + or ( + ( + typing_extensions.get_origin(clean_type) == typing.Sequence + or typing_extensions.get_origin(clean_type) == collections.abc.Sequence + or clean_type == typing.Sequence + ) + and isinstance(object_, typing.Sequence) + ) + ) + ): + inner_type = typing_extensions.get_args(clean_type)[0] + return [ + convert_and_respect_annotation_metadata(object_=item, annotation=annotation, inner_type=inner_type) + for item in object_ + ] + + if typing_extensions.get_origin(clean_type) == typing.Union: + # We should be able to ~relatively~ safely try to convert keys against all + # member types in the union, the edge case here is if one member aliases a field + # of the same name to a different name from another member + # Or if another member aliases a field of the same name that another member does not. + for member in typing_extensions.get_args(clean_type): + object_ = convert_and_respect_annotation_metadata(object_=object_, annotation=annotation, inner_type=member) + return object_ + + annotated_type = _get_annotation(annotation) + if annotated_type is None: + return object_ + + # If the object is not a TypedDict, a Union, or other container (list, set, sequence, etc.) + # Then we can safely call it on the recursive conversion. + return object_ + + +def _convert_typeddict(object_: typing.Mapping[str, object], expected_type: typing.Any) -> typing.Mapping[str, object]: + converted_object: typing.Dict[str, object] = {} + annotations = typing_extensions.get_type_hints(expected_type, include_extras=True) + for key, value in object_.items(): + type_ = annotations.get(key) + if type_ is None: + converted_object[key] = value + else: + converted_object[_alias_key(key, type_)] = convert_and_respect_annotation_metadata( + object_=value, annotation=type_ + ) + return converted_object + + +def _get_annotation(type_: typing.Any) -> typing.Optional[typing.Any]: + maybe_annotated_type = typing_extensions.get_origin(type_) + if maybe_annotated_type is None: + return None + + if maybe_annotated_type == typing_extensions.NotRequired: + type_ = typing_extensions.get_args(type_)[0] + maybe_annotated_type = typing_extensions.get_origin(type_) + + if maybe_annotated_type == typing_extensions.Annotated: + return type_ + + return None + + +def _remove_annotations(type_: typing.Any) -> typing.Any: + maybe_annotated_type = typing_extensions.get_origin(type_) + if maybe_annotated_type is None: + return type_ + + if maybe_annotated_type == typing_extensions.NotRequired: + return _remove_annotations(typing_extensions.get_args(type_)[0]) + + if maybe_annotated_type == typing_extensions.Annotated: + return _remove_annotations(typing_extensions.get_args(type_)[0]) + + return type_ + + +def _alias_key(key: str, type_: typing.Any) -> str: + maybe_annotated_type = _get_annotation(type_) + + if maybe_annotated_type is not None: + # The actual annotations are 1 onward, the first is the annotated type + annotations = typing_extensions.get_args(maybe_annotated_type)[1:] + + for annotation in annotations: + if isinstance(annotation, FieldMetadata) and annotation.alias is not None: + return annotation.alias + + return key diff --git a/seed/python-sdk/grpc-proto/src/seed/py.typed b/seed/python-sdk/grpc-proto/src/seed/py.typed new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/python-sdk/grpc-proto/src/seed/types/__init__.py b/seed/python-sdk/grpc-proto/src/seed/types/__init__.py new file mode 100644 index 00000000000..243a9961e67 --- /dev/null +++ b/seed/python-sdk/grpc-proto/src/seed/types/__init__.py @@ -0,0 +1,6 @@ +# This file was auto-generated by Fern from our API Definition. + +from .create_response import CreateResponse +from .user_model import UserModel + +__all__ = ["CreateResponse", "UserModel"] diff --git a/seed/python-sdk/grpc-proto/src/seed/types/create_response.py b/seed/python-sdk/grpc-proto/src/seed/types/create_response.py new file mode 100644 index 00000000000..7322352a43a --- /dev/null +++ b/seed/python-sdk/grpc-proto/src/seed/types/create_response.py @@ -0,0 +1,21 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic + +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .user_model import UserModel + + +class CreateResponse(UniversalBaseModel): + user: typing.Optional[UserModel] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/seed/python-sdk/grpc-proto/src/seed/types/user_model.py b/seed/python-sdk/grpc-proto/src/seed/types/user_model.py new file mode 100644 index 00000000000..44801986641 --- /dev/null +++ b/seed/python-sdk/grpc-proto/src/seed/types/user_model.py @@ -0,0 +1,24 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic + +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class UserModel(UniversalBaseModel): + username: typing.Optional[str] = None + email: typing.Optional[str] = None + age: typing.Optional[int] = None + weight: typing.Optional[float] = None + metadata: typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/seed/python-sdk/grpc-proto/src/seed/user/__init__.py b/seed/python-sdk/grpc-proto/src/seed/user/__init__.py new file mode 100644 index 00000000000..f3ea2659bb1 --- /dev/null +++ b/seed/python-sdk/grpc-proto/src/seed/user/__init__.py @@ -0,0 +1,2 @@ +# This file was auto-generated by Fern from our API Definition. + diff --git a/seed/python-sdk/grpc-proto/src/seed/user/client.py b/seed/python-sdk/grpc-proto/src/seed/user/client.py new file mode 100644 index 00000000000..5d81af9d075 --- /dev/null +++ b/seed/python-sdk/grpc-proto/src/seed/user/client.py @@ -0,0 +1,141 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from ..types.create_response import CreateResponse + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class UserClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def create( + self, + *, + username: typing.Optional[str] = OMIT, + email: typing.Optional[str] = OMIT, + age: typing.Optional[int] = OMIT, + weight: typing.Optional[float] = OMIT, + metadata: typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]] = OMIT, + request_options: typing.Optional[RequestOptions] = None + ) -> CreateResponse: + """ + Parameters + ---------- + username : typing.Optional[str] + + email : typing.Optional[str] + + age : typing.Optional[int] + + weight : typing.Optional[float] + + metadata : typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CreateResponse + OK + + Examples + -------- + from seed import SeedApi + + client = SeedApi( + base_url="https://yourhost.com/path/to/api", + ) + client.user.create() + """ + _response = self._client_wrapper.httpx_client.request( + "users", + method="POST", + json={"username": username, "email": email, "age": age, "weight": weight, "metadata": metadata}, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + return typing.cast(CreateResponse, parse_obj_as(type_=CreateResponse, object_=_response.json())) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + + +class AsyncUserClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def create( + self, + *, + username: typing.Optional[str] = OMIT, + email: typing.Optional[str] = OMIT, + age: typing.Optional[int] = OMIT, + weight: typing.Optional[float] = OMIT, + metadata: typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]] = OMIT, + request_options: typing.Optional[RequestOptions] = None + ) -> CreateResponse: + """ + Parameters + ---------- + username : typing.Optional[str] + + email : typing.Optional[str] + + age : typing.Optional[int] + + weight : typing.Optional[float] + + metadata : typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CreateResponse + OK + + Examples + -------- + import asyncio + + from seed import AsyncSeedApi + + client = AsyncSeedApi( + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.user.create() + + + asyncio.run(main()) + """ + _response = await self._client_wrapper.httpx_client.request( + "users", + method="POST", + json={"username": username, "email": email, "age": age, "weight": weight, "metadata": metadata}, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + return typing.cast(CreateResponse, parse_obj_as(type_=CreateResponse, object_=_response.json())) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) diff --git a/seed/python-sdk/grpc-proto/src/seed/version.py b/seed/python-sdk/grpc-proto/src/seed/version.py new file mode 100644 index 00000000000..2389c9c87c2 --- /dev/null +++ b/seed/python-sdk/grpc-proto/src/seed/version.py @@ -0,0 +1,4 @@ + +from importlib import metadata + +__version__ = metadata.version("fern_grpc-proto") diff --git a/seed/python-sdk/grpc-proto/tests/__init__.py b/seed/python-sdk/grpc-proto/tests/__init__.py new file mode 100644 index 00000000000..f3ea2659bb1 --- /dev/null +++ b/seed/python-sdk/grpc-proto/tests/__init__.py @@ -0,0 +1,2 @@ +# This file was auto-generated by Fern from our API Definition. + diff --git a/seed/python-sdk/grpc-proto/tests/conftest.py b/seed/python-sdk/grpc-proto/tests/conftest.py new file mode 100644 index 00000000000..c39a48a47ef --- /dev/null +++ b/seed/python-sdk/grpc-proto/tests/conftest.py @@ -0,0 +1,16 @@ +# This file was auto-generated by Fern from our API Definition. + +import os + +import pytest +from seed import AsyncSeedApi, SeedApi + + +@pytest.fixture +def client() -> SeedApi: + return SeedApi(base_url=os.getenv("TESTS_BASE_URL", "base_url")) + + +@pytest.fixture +def async_client() -> AsyncSeedApi: + return AsyncSeedApi(base_url=os.getenv("TESTS_BASE_URL", "base_url")) diff --git a/seed/python-sdk/grpc-proto/tests/custom/test_client.py b/seed/python-sdk/grpc-proto/tests/custom/test_client.py new file mode 100644 index 00000000000..60a58e64c27 --- /dev/null +++ b/seed/python-sdk/grpc-proto/tests/custom/test_client.py @@ -0,0 +1,6 @@ +import pytest + +# Get started with writing tests with pytest at https://docs.pytest.org +@pytest.mark.skip(reason="Unimplemented") +def test_client() -> None: + assert True == True diff --git a/seed/python-sdk/grpc-proto/tests/test_user.py b/seed/python-sdk/grpc-proto/tests/test_user.py new file mode 100644 index 00000000000..26e2d34282b --- /dev/null +++ b/seed/python-sdk/grpc-proto/tests/test_user.py @@ -0,0 +1,21 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from seed import AsyncSeedApi, SeedApi + +from .utilities import validate_response + + +async def test_create(client: SeedApi, async_client: AsyncSeedApi) -> None: + expected_response: typing.Any = { + "user": {"username": "username", "email": "email", "age": 1, "weight": 1.1, "metadata": {"key": "value"}} + } + expected_types: typing.Any = { + "user": {"username": None, "email": None, "age": None, "weight": None, "metadata": ("dict", {0: (None, None)})} + } + response = client.user.create() + validate_response(response, expected_response, expected_types) + + async_response = await async_client.user.create() + validate_response(async_response, expected_response, expected_types) diff --git a/seed/python-sdk/grpc-proto/tests/utilities.py b/seed/python-sdk/grpc-proto/tests/utilities.py new file mode 100644 index 00000000000..13da208eb3a --- /dev/null +++ b/seed/python-sdk/grpc-proto/tests/utilities.py @@ -0,0 +1,138 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +import uuid + +import pydantic +from dateutil import parser + + +def cast_field(json_expectation: typing.Any, type_expectation: typing.Any) -> typing.Any: + # Cast these specific types which come through as string and expect our + # models to cast to the correct type. + if type_expectation == "uuid": + return uuid.UUID(json_expectation) + elif type_expectation == "date": + return parser.parse(json_expectation).date() + elif type_expectation == "datetime": + return parser.parse(json_expectation) + elif type_expectation == "set": + return set(json_expectation) + elif type_expectation == "integer": + # Necessary as we allow numeric keys, but JSON makes them strings + return int(json_expectation) + + return json_expectation + + +def validate_field(response: typing.Any, json_expectation: typing.Any, type_expectation: typing.Any) -> None: + # Allow for an escape hatch if the object cannot be validated + if type_expectation == "no_validate": + return + + is_container_of_complex_type = False + # Parse types in containers, note that dicts are handled within `validate_response` + if isinstance(json_expectation, list): + if isinstance(type_expectation, tuple): + container_expectation = type_expectation[0] + contents_expectation = type_expectation[1] + + cast_json_expectation = [] + for idx, ex in enumerate(json_expectation): + if isinstance(contents_expectation, dict): + entry_expectation = contents_expectation.get(idx) + if isinstance(entry_expectation, dict): + is_container_of_complex_type = True + validate_response( + response=response[idx], json_expectation=ex, type_expectations=entry_expectation + ) + else: + cast_json_expectation.append(cast_field(ex, entry_expectation)) + else: + cast_json_expectation.append(ex) + json_expectation = cast_json_expectation + + # Note that we explicitly do not allow for sets of pydantic models as they are not hashable, so + # if any of the values of the set have a type_expectation of a dict, we're assuming it's a pydantic + # model and keeping it a list. + if container_expectation != "set" or not any( + map(lambda value: isinstance(value, dict), list(contents_expectation.values())) + ): + json_expectation = cast_field(json_expectation, container_expectation) + elif isinstance(type_expectation, tuple): + container_expectation = type_expectation[0] + contents_expectation = type_expectation[1] + if isinstance(contents_expectation, dict): + json_expectation = { + cast_field( + key, contents_expectation.get(idx)[0] if contents_expectation.get(idx) is not None else None # type: ignore + ): cast_field( + value, contents_expectation.get(idx)[1] if contents_expectation.get(idx) is not None else None # type: ignore + ) + for idx, (key, value) in enumerate(json_expectation.items()) + } + else: + json_expectation = cast_field(json_expectation, container_expectation) + elif type_expectation is not None: + json_expectation = cast_field(json_expectation, type_expectation) + + # When dealing with containers of models, etc. we're validating them implicitly, so no need to check the resultant list + if not is_container_of_complex_type: + assert ( + json_expectation == response + ), "Primitives found, expected: {0} (type: {1}), Actual: {2} (type: {3})".format( + json_expectation, type(json_expectation), response, type(response) + ) + + +# Arg type_expectations is a deeply nested structure that matches the response, but with the values replaced with the expected types +def validate_response(response: typing.Any, json_expectation: typing.Any, type_expectations: typing.Any) -> None: + # Allow for an escape hatch if the object cannot be validated + if type_expectations == "no_validate": + return + + if ( + not isinstance(response, list) + and not isinstance(response, dict) + and not issubclass(type(response), pydantic.BaseModel) + ): + validate_field(response=response, json_expectation=json_expectation, type_expectation=type_expectations) + return + + if isinstance(response, list): + assert len(response) == len(json_expectation), "Length mismatch, expected: {0}, Actual: {1}".format( + len(response), len(json_expectation) + ) + content_expectation = type_expectations + if isinstance(type_expectations, tuple): + content_expectation = type_expectations[1] + for idx, item in enumerate(response): + validate_response( + response=item, json_expectation=json_expectation[idx], type_expectations=content_expectation[idx] + ) + else: + response_json = response + if issubclass(type(response), pydantic.BaseModel): + response_json = response.dict(by_alias=True) + + for key, value in json_expectation.items(): + assert key in response_json, "Field {0} not found within the response object: {1}".format( + key, response_json + ) + + type_expectation = None + if type_expectations is not None and isinstance(type_expectations, dict): + type_expectation = type_expectations.get(key) + + # If your type_expectation is a tuple then you have a container field, process it as such + # Otherwise, we're just validating a single field that's a pydantic model. + if isinstance(value, dict) and not isinstance(type_expectation, tuple): + validate_response( + response=response_json[key], json_expectation=value, type_expectations=type_expectation + ) + else: + validate_field(response=response_json[key], json_expectation=value, type_expectation=type_expectation) + + # Ensure there are no additional fields here either + del response_json[key] + assert len(response_json) == 0, "Additional fields found, expected None: {0}".format(response_json) diff --git a/seed/python-sdk/grpc-proto/tests/utils/__init__.py b/seed/python-sdk/grpc-proto/tests/utils/__init__.py new file mode 100644 index 00000000000..f3ea2659bb1 --- /dev/null +++ b/seed/python-sdk/grpc-proto/tests/utils/__init__.py @@ -0,0 +1,2 @@ +# This file was auto-generated by Fern from our API Definition. + diff --git a/seed/python-sdk/grpc-proto/tests/utils/assets/models/__init__.py b/seed/python-sdk/grpc-proto/tests/utils/assets/models/__init__.py new file mode 100644 index 00000000000..2cf01263529 --- /dev/null +++ b/seed/python-sdk/grpc-proto/tests/utils/assets/models/__init__.py @@ -0,0 +1,21 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +from .circle import CircleParams +from .object_with_defaults import ObjectWithDefaultsParams +from .object_with_optional_field import ObjectWithOptionalFieldParams +from .shape import Shape_CircleParams, Shape_SquareParams, ShapeParams +from .square import SquareParams +from .undiscriminated_shape import UndiscriminatedShapeParams + +__all__ = [ + "CircleParams", + "ObjectWithDefaultsParams", + "ObjectWithOptionalFieldParams", + "ShapeParams", + "Shape_CircleParams", + "Shape_SquareParams", + "SquareParams", + "UndiscriminatedShapeParams", +] diff --git a/seed/python-sdk/grpc-proto/tests/utils/assets/models/circle.py b/seed/python-sdk/grpc-proto/tests/utils/assets/models/circle.py new file mode 100644 index 00000000000..af7a1bf8a8e --- /dev/null +++ b/seed/python-sdk/grpc-proto/tests/utils/assets/models/circle.py @@ -0,0 +1,10 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +import typing_extensions +from seed.core.serialization import FieldMetadata + + +class CircleParams(typing_extensions.TypedDict): + radius_measurement: typing_extensions.Annotated[float, FieldMetadata(alias="radiusMeasurement")] diff --git a/seed/python-sdk/grpc-proto/tests/utils/assets/models/color.py b/seed/python-sdk/grpc-proto/tests/utils/assets/models/color.py new file mode 100644 index 00000000000..2aa2c4c52f0 --- /dev/null +++ b/seed/python-sdk/grpc-proto/tests/utils/assets/models/color.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +import typing + +Color = typing.Union[typing.Literal["red", "blue"], typing.Any] diff --git a/seed/python-sdk/grpc-proto/tests/utils/assets/models/object_with_defaults.py b/seed/python-sdk/grpc-proto/tests/utils/assets/models/object_with_defaults.py new file mode 100644 index 00000000000..a977b1d2aa1 --- /dev/null +++ b/seed/python-sdk/grpc-proto/tests/utils/assets/models/object_with_defaults.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +import typing_extensions + + +class ObjectWithDefaultsParams(typing_extensions.TypedDict): + """ + Defines properties with default values and validation rules. + """ + + decimal: typing_extensions.NotRequired[float] + string: typing_extensions.NotRequired[str] + required_string: str diff --git a/seed/python-sdk/grpc-proto/tests/utils/assets/models/object_with_optional_field.py b/seed/python-sdk/grpc-proto/tests/utils/assets/models/object_with_optional_field.py new file mode 100644 index 00000000000..3ad93d5f305 --- /dev/null +++ b/seed/python-sdk/grpc-proto/tests/utils/assets/models/object_with_optional_field.py @@ -0,0 +1,35 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing +import uuid + +import typing_extensions +from seed.core.serialization import FieldMetadata + +from .color import Color +from .shape import ShapeParams +from .undiscriminated_shape import UndiscriminatedShapeParams + + +class ObjectWithOptionalFieldParams(typing_extensions.TypedDict): + literal: typing.Literal["lit_one"] + string: typing_extensions.NotRequired[str] + integer: typing_extensions.NotRequired[int] + long_: typing_extensions.NotRequired[typing_extensions.Annotated[int, FieldMetadata(alias="long")]] + double: typing_extensions.NotRequired[float] + bool_: typing_extensions.NotRequired[typing_extensions.Annotated[bool, FieldMetadata(alias="bool")]] + datetime: typing_extensions.NotRequired[dt.datetime] + date: typing_extensions.NotRequired[dt.date] + uuid_: typing_extensions.NotRequired[typing_extensions.Annotated[uuid.UUID, FieldMetadata(alias="uuid")]] + base_64: typing_extensions.NotRequired[typing_extensions.Annotated[str, FieldMetadata(alias="base64")]] + list_: typing_extensions.NotRequired[typing_extensions.Annotated[typing.Sequence[str], FieldMetadata(alias="list")]] + set_: typing_extensions.NotRequired[typing_extensions.Annotated[typing.Set[str], FieldMetadata(alias="set")]] + map_: typing_extensions.NotRequired[typing_extensions.Annotated[typing.Dict[int, str], FieldMetadata(alias="map")]] + enum: typing_extensions.NotRequired[Color] + union: typing_extensions.NotRequired[ShapeParams] + second_union: typing_extensions.NotRequired[ShapeParams] + undiscriminated_union: typing_extensions.NotRequired[UndiscriminatedShapeParams] + any: typing.Any diff --git a/seed/python-sdk/grpc-proto/tests/utils/assets/models/shape.py b/seed/python-sdk/grpc-proto/tests/utils/assets/models/shape.py new file mode 100644 index 00000000000..2c33c877951 --- /dev/null +++ b/seed/python-sdk/grpc-proto/tests/utils/assets/models/shape.py @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +import typing_extensions +from seed.core.serialization import FieldMetadata + + +class Base(typing_extensions.TypedDict): + id: str + + +class Shape_CircleParams(Base): + shape_type: typing_extensions.Annotated[typing.Literal["circle"], FieldMetadata(alias="shapeType")] + radius_measurement: typing_extensions.Annotated[float, FieldMetadata(alias="radiusMeasurement")] + + +class Shape_SquareParams(Base): + shape_type: typing_extensions.Annotated[typing.Literal["square"], FieldMetadata(alias="shapeType")] + length_measurement: typing_extensions.Annotated[float, FieldMetadata(alias="lengthMeasurement")] + + +ShapeParams = typing.Union[Shape_CircleParams, Shape_SquareParams] diff --git a/seed/python-sdk/grpc-proto/tests/utils/assets/models/square.py b/seed/python-sdk/grpc-proto/tests/utils/assets/models/square.py new file mode 100644 index 00000000000..b9b7dd319bc --- /dev/null +++ b/seed/python-sdk/grpc-proto/tests/utils/assets/models/square.py @@ -0,0 +1,10 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +import typing_extensions +from seed.core.serialization import FieldMetadata + + +class SquareParams(typing_extensions.TypedDict): + length_measurement: typing_extensions.Annotated[float, FieldMetadata(alias="lengthMeasurement")] diff --git a/seed/python-sdk/grpc-proto/tests/utils/assets/models/undiscriminated_shape.py b/seed/python-sdk/grpc-proto/tests/utils/assets/models/undiscriminated_shape.py new file mode 100644 index 00000000000..99f12b300d1 --- /dev/null +++ b/seed/python-sdk/grpc-proto/tests/utils/assets/models/undiscriminated_shape.py @@ -0,0 +1,10 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +import typing + +from .circle import CircleParams +from .square import SquareParams + +UndiscriminatedShapeParams = typing.Union[CircleParams, SquareParams] diff --git a/seed/python-sdk/grpc-proto/tests/utils/test_http_client.py b/seed/python-sdk/grpc-proto/tests/utils/test_http_client.py new file mode 100644 index 00000000000..a541bae6531 --- /dev/null +++ b/seed/python-sdk/grpc-proto/tests/utils/test_http_client.py @@ -0,0 +1,61 @@ +# This file was auto-generated by Fern from our API Definition. + +from seed.core.http_client import get_request_body +from seed.core.request_options import RequestOptions + + +def get_request_options() -> RequestOptions: + return {"additional_body_parameters": {"see you": "later"}} + + +def test_get_json_request_body() -> None: + json_body, data_body = get_request_body(json={"hello": "world"}, data=None, request_options=None, omit=None) + assert json_body == {"hello": "world"} + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={"goodbye": "world"}, data=None, request_options=get_request_options(), omit=None + ) + + assert json_body_extras == {"goodbye": "world", "see you": "later"} + assert data_body_extras is None + + +def test_get_files_request_body() -> None: + json_body, data_body = get_request_body(json=None, data={"hello": "world"}, request_options=None, omit=None) + assert data_body == {"hello": "world"} + assert json_body is None + + json_body_extras, data_body_extras = get_request_body( + json=None, data={"goodbye": "world"}, request_options=get_request_options(), omit=None + ) + + assert data_body_extras == {"goodbye": "world", "see you": "later"} + assert json_body_extras is None + + +def test_get_none_request_body() -> None: + json_body, data_body = get_request_body(json=None, data=None, request_options=None, omit=None) + assert data_body is None + assert json_body is None + + json_body_extras, data_body_extras = get_request_body( + json=None, data=None, request_options=get_request_options(), omit=None + ) + + assert json_body_extras == {"see you": "later"} + assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/grpc-proto/tests/utils/test_query_encoding.py b/seed/python-sdk/grpc-proto/tests/utils/test_query_encoding.py new file mode 100644 index 00000000000..247e1551b46 --- /dev/null +++ b/seed/python-sdk/grpc-proto/tests/utils/test_query_encoding.py @@ -0,0 +1,18 @@ +# This file was auto-generated by Fern from our API Definition. + +from seed.core.query_encoder import encode_query + + +def test_query_encoding() -> None: + assert encode_query({"hello world": "hello world"}) == {"hello world": "hello world"} + assert encode_query({"hello_world": {"hello": "world"}}) == {"hello_world[hello]": "world"} + assert encode_query({"hello_world": {"hello": {"world": "today"}, "test": "this"}, "hi": "there"}) == { + "hello_world[hello][world]": "today", + "hello_world[test]": "this", + "hi": "there", + } + + +def test_encode_query_with_none() -> None: + encoded = encode_query(None) + assert encoded == None diff --git a/seed/python-sdk/grpc-proto/tests/utils/test_serialization.py b/seed/python-sdk/grpc-proto/tests/utils/test_serialization.py new file mode 100644 index 00000000000..58b1ed66e6d --- /dev/null +++ b/seed/python-sdk/grpc-proto/tests/utils/test_serialization.py @@ -0,0 +1,66 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import Any, List + +from seed.core.serialization import convert_and_respect_annotation_metadata + +from .assets.models import ObjectWithOptionalFieldParams, ShapeParams + +UNION_TEST: ShapeParams = {"radius_measurement": 1.0, "shape_type": "circle", "id": "1"} +UNION_TEST_CONVERTED = {"shapeType": "circle", "radiusMeasurement": 1.0, "id": "1"} + + +def test_convert_and_respect_annotation_metadata() -> None: + data: ObjectWithOptionalFieldParams = { + "string": "string", + "long_": 12345, + "bool_": True, + "literal": "lit_one", + "any": "any", + } + converted = convert_and_respect_annotation_metadata(object_=data, annotation=ObjectWithOptionalFieldParams) + assert converted == {"string": "string", "long": 12345, "bool": True, "literal": "lit_one", "any": "any"} + + +def test_convert_and_respect_annotation_metadata_in_list() -> None: + data: List[ObjectWithOptionalFieldParams] = [ + {"string": "string", "long_": 12345, "bool_": True, "literal": "lit_one", "any": "any"}, + {"string": "another string", "long_": 67890, "list_": [], "literal": "lit_one", "any": "any"}, + ] + converted = convert_and_respect_annotation_metadata(object_=data, annotation=List[ObjectWithOptionalFieldParams]) + + assert converted == [ + {"string": "string", "long": 12345, "bool": True, "literal": "lit_one", "any": "any"}, + {"string": "another string", "long": 67890, "list": [], "literal": "lit_one", "any": "any"}, + ] + + +def test_convert_and_respect_annotation_metadata_in_nested_object() -> None: + data: ObjectWithOptionalFieldParams = { + "string": "string", + "long_": 12345, + "union": UNION_TEST, + "literal": "lit_one", + "any": "any", + } + converted = convert_and_respect_annotation_metadata(object_=data, annotation=ObjectWithOptionalFieldParams) + + assert converted == { + "string": "string", + "long": 12345, + "union": UNION_TEST_CONVERTED, + "literal": "lit_one", + "any": "any", + } + + +def test_convert_and_respect_annotation_metadata_in_union() -> None: + converted = convert_and_respect_annotation_metadata(object_=UNION_TEST, annotation=ShapeParams) + + assert converted == UNION_TEST_CONVERTED + + +def test_convert_and_respect_annotation_metadata_with_empty_object() -> None: + data: Any = {} + converted = convert_and_respect_annotation_metadata(object_=data, annotation=ShapeParams) + assert converted == data diff --git a/seed/python-sdk/grpc/.github/workflows/ci.yml b/seed/python-sdk/grpc/.github/workflows/ci.yml new file mode 100644 index 00000000000..b7316b8cab7 --- /dev/null +++ b/seed/python-sdk/grpc/.github/workflows/ci.yml @@ -0,0 +1,63 @@ +name: ci + +on: [push] +jobs: + compile: + runs-on: ubuntu-20.04 + steps: + - name: Checkout repo + uses: actions/checkout@v3 + - name: Set up python + uses: actions/setup-python@v4 + with: + python-version: 3.8 + - name: Bootstrap poetry + run: | + curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 + - name: Install dependencies + run: poetry install + - name: Compile + run: poetry run mypy . + test: + runs-on: ubuntu-20.04 + steps: + - name: Checkout repo + uses: actions/checkout@v3 + - name: Set up python + uses: actions/setup-python@v4 + with: + python-version: 3.8 + - name: Bootstrap poetry + run: | + curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 + - name: Install dependencies + run: poetry install + + - name: Install Fern + run: npm install -g fern-api + - name: Test + run: fern test --command "poetry run pytest -rP ." + + publish: + needs: [compile, test] + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + runs-on: ubuntu-20.04 + steps: + - name: Checkout repo + uses: actions/checkout@v3 + - name: Set up python + uses: actions/setup-python@v4 + with: + python-version: 3.8 + - name: Bootstrap poetry + run: | + curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 + - name: Install dependencies + run: poetry install + - name: Publish to pypi + run: | + poetry config repositories.remote + poetry --no-interaction -v publish --build --repository remote --username "$" --password "$" + env: + : ${{ secrets. }} + : ${{ secrets. }} diff --git a/seed/python-sdk/grpc/.gitignore b/seed/python-sdk/grpc/.gitignore new file mode 100644 index 00000000000..42cb863501e --- /dev/null +++ b/seed/python-sdk/grpc/.gitignore @@ -0,0 +1,4 @@ +dist/ +.mypy_cache/ +__pycache__/ +poetry.toml diff --git a/seed/python-sdk/grpc/.mock/definition/api.yml b/seed/python-sdk/grpc/.mock/definition/api.yml new file mode 100644 index 00000000000..15a9f823724 --- /dev/null +++ b/seed/python-sdk/grpc/.mock/definition/api.yml @@ -0,0 +1,4 @@ +name: api + +error-discrimination: + strategy: status-code \ No newline at end of file diff --git a/seed/python-sdk/grpc/.mock/definition/user.yml b/seed/python-sdk/grpc/.mock/definition/user.yml new file mode 100644 index 00000000000..80223300afc --- /dev/null +++ b/seed/python-sdk/grpc/.mock/definition/user.yml @@ -0,0 +1,61 @@ +types: + Metadata: + type: map> + encoding: + proto: + type: google.protobuf.Struct + + MetadataValue: + discriminated: false + union: + - double + - string + - boolean + - list + encoding: + proto: + type: google.protobuf.Value + + User: + properties: + id: string + username: string + email: optional + age: optional + weight: optional + metadata: optional + + CreateUserResponse: + properties: + user: User + +service: + auth: false + base-path: / + transport: + grpc: + service-name: UserService + endpoints: + createUser: + method: POST + path: /users + request: + name: CreateUserRequest + body: + properties: + username: string + email: optional + age: optional + weight: optional + response: CreateUserResponse + + getUser: + method: GET + path: /users + request: + name: GetUserRequest + query-parameters: + username: optional + age: optional + weight: optional + response: User diff --git a/seed/python-sdk/grpc/.mock/fern.config.json b/seed/python-sdk/grpc/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/python-sdk/grpc/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/python-sdk/grpc/.mock/generators.yml b/seed/python-sdk/grpc/.mock/generators.yml new file mode 100644 index 00000000000..f62dfba5843 --- /dev/null +++ b/seed/python-sdk/grpc/.mock/generators.yml @@ -0,0 +1,4 @@ +api: + - proto: + root: proto + target: proto/user/v1/user.proto \ No newline at end of file diff --git a/seed/python-sdk/grpc/README.md b/seed/python-sdk/grpc/README.md new file mode 100644 index 00000000000..8c4d67cd304 --- /dev/null +++ b/seed/python-sdk/grpc/README.md @@ -0,0 +1,140 @@ +# Seed Python Library + +[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-SDK%20generated%20by%20Fern-brightgreen)](https://github.com/fern-api/fern) +[![pypi](https://img.shields.io/pypi/v/fern_grpc)](https://pypi.python.org/pypi/fern_grpc) + +The Seed Python library provides convenient access to the Seed API from Python. + +## Installation + +```sh +pip install fern_grpc +``` + +## Usage + +Instantiate and use the client with the following: + +```python +from seed import SeedApi + +client = SeedApi( + base_url="https://yourhost.com/path/to/api", +) +client.user.create_user( + username="string", + email="string", + age=1, + weight=1.1, +) +``` + +## Async Client + +The SDK also exports an `async` client so that you can make non-blocking calls to our API. + +```python +import asyncio + +from seed import AsyncSeedApi + +client = AsyncSeedApi( + base_url="https://yourhost.com/path/to/api", +) + + +async def main() -> None: + await client.user.create_user( + username="string", + email="string", + age=1, + weight=1.1, + ) + + +asyncio.run(main()) +``` + +## Exception Handling + +When the API returns a non-success status code (4xx or 5xx response), a subclass of the following error +will be thrown. + +```python +from seed.core.api_error import ApiError + +try: + client.user.create_user(...) +except ApiError as e: + print(e.status_code) + print(e.body) +``` + +## Advanced + +### Retries + +The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long +as the request is deemed retriable and the number of retry attempts has not grown larger than the configured +retry limit (default: 2). + +A request is deemed retriable when any of the following HTTP status codes is returned: + +- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) +- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) +- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) + +Use the `max_retries` request option to configure this behavior. + +```python +client.user.create_user(..., { + "max_retries": 1 +}) +``` + +### Timeouts + +The SDK defaults to a 60 second timeout. You can configure this with a timeout option at the client or request level. + +```python + +from seed import SeedApi + +client = SeedApi( + ..., + timeout=20.0, +) + + +# Override timeout for a specific method +client.user.create_user(..., { + "timeout_in_seconds": 1 +}) +``` + +### Custom Client + +You can override the `httpx` client to customize it for your use-case. Some common use-cases include support for proxies +and transports. +```python +import httpx +from seed import SeedApi + +client = SeedApi( + ..., + httpx_client=httpx.Client( + proxies="http://my.test.proxy.example.com", + transport=httpx.HTTPTransport(local_address="0.0.0.0"), + ), +) +``` + +## Contributing + +While we value open-source contributions to this SDK, this library is generated programmatically. +Additions made directly to this library would have to be moved over to our generation code, +otherwise they would be overwritten upon the next generated release. Feel free to open a PR as +a proof of concept, but know that we will not be able to merge it as-is. We suggest opening +an issue first to discuss with us! + +On the other hand, contributions to the README are always very welcome! diff --git a/seed/python-sdk/grpc/pyproject.toml b/seed/python-sdk/grpc/pyproject.toml new file mode 100644 index 00000000000..9d0e56d9387 --- /dev/null +++ b/seed/python-sdk/grpc/pyproject.toml @@ -0,0 +1,57 @@ +[tool.poetry] +name = "fern_grpc" +version = "0.0.1" +description = "" +readme = "README.md" +authors = [] +keywords = [] + +classifiers = [ + "Intended Audience :: Developers", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Operating System :: OS Independent", + "Operating System :: POSIX", + "Operating System :: MacOS", + "Operating System :: POSIX :: Linux", + "Operating System :: Microsoft :: Windows", + "Topic :: Software Development :: Libraries :: Python Modules", + "Typing :: Typed" +] +packages = [ + { include = "seed", from = "src"} +] + +[project.urls] +Repository = 'https://github.com/grpc/fern' + +[tool.poetry.dependencies] +python = "^3.8" +httpx = ">=0.21.2" +pydantic = ">= 1.9.2" +pydantic-core = "^2.18.2" +typing_extensions = ">= 4.0.0" + +[tool.poetry.dev-dependencies] +mypy = "1.0.1" +pytest = "^7.4.0" +pytest-asyncio = "^0.23.5" +python-dateutil = "^2.9.0" +types-python-dateutil = "^2.9.0.20240316" + +[tool.pytest.ini_options] +testpaths = [ "tests" ] +asyncio_mode = "auto" + +[tool.mypy] +plugins = ["pydantic.mypy"] + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/seed/python-sdk/grpc/reference.md b/seed/python-sdk/grpc/reference.md new file mode 100644 index 00000000000..c3fbc3a56b2 --- /dev/null +++ b/seed/python-sdk/grpc/reference.md @@ -0,0 +1,159 @@ +# Reference +## User +
client.user.create_user(...) +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from seed import SeedApi + +client = SeedApi( + base_url="https://yourhost.com/path/to/api", +) +client.user.create_user( + username="string", + email="string", + age=1, + weight=1.1, +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**username:** `str` + +
+
+ +
+
+ +**email:** `typing.Optional[str]` + +
+
+ +
+
+ +**age:** `typing.Optional[int]` + +
+
+ +
+
+ +**weight:** `typing.Optional[float]` + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.user.get_user(...) +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from seed import SeedApi + +client = SeedApi( + base_url="https://yourhost.com/path/to/api", +) +client.user.get_user( + username="string", + age=1, + weight=1.1, +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**username:** `typing.Optional[str]` + +
+
+ +
+
+ +**age:** `typing.Optional[int]` + +
+
+ +
+
+ +**weight:** `typing.Optional[float]` + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ diff --git a/seed/python-sdk/grpc/snippet-templates.json b/seed/python-sdk/grpc/snippet-templates.json new file mode 100644 index 00000000000..f29c28825c7 --- /dev/null +++ b/seed/python-sdk/grpc/snippet-templates.json @@ -0,0 +1,342 @@ +[ + { + "sdk": { + "package": "fern_grpc", + "version": "0.0.1", + "type": "python" + }, + "endpointId": { + "path": "/users", + "method": "POST", + "identifierOverride": "endpoint_user.createUser" + }, + "snippetTemplate": { + "clientInstantiation": { + "imports": [ + "from seed import SeedApi" + ], + "isOptional": true, + "templateString": "client = SeedApi(\n base_url=\"https://yourhost.com/path/to/api\",\n)", + "templateInputs": [], + "inputDelimiter": ",", + "type": "generic" + }, + "functionInvocation": { + "imports": [], + "isOptional": true, + "templateString": "client.user.create_user(\n\t$FERN_INPUT\n)", + "templateInputs": [ + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "username=$FERN_INPUT", + "templateInputs": [ + { + "location": "BODY", + "path": "username", + "type": "payload" + } + ], + "type": "generic" + } + }, + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "email=$FERN_INPUT", + "templateInputs": [ + { + "location": "BODY", + "path": "email", + "type": "payload" + } + ], + "type": "generic" + } + }, + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "age=$FERN_INPUT", + "templateInputs": [ + { + "location": "BODY", + "path": "age", + "type": "payload" + } + ], + "type": "generic" + } + }, + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "weight=$FERN_INPUT", + "templateInputs": [ + { + "location": "BODY", + "path": "weight", + "type": "payload" + } + ], + "type": "generic" + } + } + ], + "inputDelimiter": ",\n\t", + "type": "generic" + }, + "type": "v1" + }, + "additionalTemplates": { + "async": { + "clientInstantiation": { + "imports": [ + "from seed import AsyncSeedApi" + ], + "isOptional": true, + "templateString": "client = AsyncSeedApi(\n base_url=\"https://yourhost.com/path/to/api\",\n)", + "templateInputs": [], + "inputDelimiter": ",", + "type": "generic" + }, + "functionInvocation": { + "imports": [], + "isOptional": true, + "templateString": "await client.user.create_user(\n\t$FERN_INPUT\n)", + "templateInputs": [ + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "username=$FERN_INPUT", + "templateInputs": [ + { + "location": "BODY", + "path": "username", + "type": "payload" + } + ], + "type": "generic" + } + }, + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "email=$FERN_INPUT", + "templateInputs": [ + { + "location": "BODY", + "path": "email", + "type": "payload" + } + ], + "type": "generic" + } + }, + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "age=$FERN_INPUT", + "templateInputs": [ + { + "location": "BODY", + "path": "age", + "type": "payload" + } + ], + "type": "generic" + } + }, + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "weight=$FERN_INPUT", + "templateInputs": [ + { + "location": "BODY", + "path": "weight", + "type": "payload" + } + ], + "type": "generic" + } + } + ], + "inputDelimiter": ",\n\t", + "type": "generic" + }, + "type": "v1" + } + } + }, + { + "sdk": { + "package": "fern_grpc", + "version": "0.0.1", + "type": "python" + }, + "endpointId": { + "path": "/users", + "method": "GET", + "identifierOverride": "endpoint_user.getUser" + }, + "snippetTemplate": { + "clientInstantiation": { + "imports": [ + "from seed import SeedApi" + ], + "isOptional": true, + "templateString": "client = SeedApi(\n base_url=\"https://yourhost.com/path/to/api\",\n)", + "templateInputs": [], + "inputDelimiter": ",", + "type": "generic" + }, + "functionInvocation": { + "imports": [], + "isOptional": true, + "templateString": "client.user.get_user(\n\t$FERN_INPUT\n)", + "templateInputs": [ + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "username=$FERN_INPUT", + "templateInputs": [ + { + "location": "QUERY", + "path": "username", + "type": "payload" + } + ], + "type": "generic" + } + }, + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "age=$FERN_INPUT", + "templateInputs": [ + { + "location": "QUERY", + "path": "age", + "type": "payload" + } + ], + "type": "generic" + } + }, + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "weight=$FERN_INPUT", + "templateInputs": [ + { + "location": "QUERY", + "path": "weight", + "type": "payload" + } + ], + "type": "generic" + } + } + ], + "inputDelimiter": ",\n\t", + "type": "generic" + }, + "type": "v1" + }, + "additionalTemplates": { + "async": { + "clientInstantiation": { + "imports": [ + "from seed import AsyncSeedApi" + ], + "isOptional": true, + "templateString": "client = AsyncSeedApi(\n base_url=\"https://yourhost.com/path/to/api\",\n)", + "templateInputs": [], + "inputDelimiter": ",", + "type": "generic" + }, + "functionInvocation": { + "imports": [], + "isOptional": true, + "templateString": "await client.user.get_user(\n\t$FERN_INPUT\n)", + "templateInputs": [ + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "username=$FERN_INPUT", + "templateInputs": [ + { + "location": "QUERY", + "path": "username", + "type": "payload" + } + ], + "type": "generic" + } + }, + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "age=$FERN_INPUT", + "templateInputs": [ + { + "location": "QUERY", + "path": "age", + "type": "payload" + } + ], + "type": "generic" + } + }, + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "weight=$FERN_INPUT", + "templateInputs": [ + { + "location": "QUERY", + "path": "weight", + "type": "payload" + } + ], + "type": "generic" + } + } + ], + "inputDelimiter": ",\n\t", + "type": "generic" + }, + "type": "v1" + } + } + } +] \ No newline at end of file diff --git a/seed/python-sdk/grpc/snippet.json b/seed/python-sdk/grpc/snippet.json new file mode 100644 index 00000000000..1437bc0d662 --- /dev/null +++ b/seed/python-sdk/grpc/snippet.json @@ -0,0 +1,31 @@ +{ + "types": {}, + "endpoints": [ + { + "example_identifier": "default", + "id": { + "path": "/users", + "method": "POST", + "identifier_override": "endpoint_user.createUser" + }, + "snippet": { + "sync_client": "from seed import SeedApi\n\nclient = SeedApi(\n base_url=\"https://yourhost.com/path/to/api\",\n)\nclient.user.create_user(\n username=\"string\",\n email=\"string\",\n age=1,\n weight=1.1,\n)\n", + "async_client": "import asyncio\n\nfrom seed import AsyncSeedApi\n\nclient = AsyncSeedApi(\n base_url=\"https://yourhost.com/path/to/api\",\n)\n\n\nasync def main() -> None:\n await client.user.create_user(\n username=\"string\",\n email=\"string\",\n age=1,\n weight=1.1,\n )\n\n\nasyncio.run(main())\n", + "type": "python" + } + }, + { + "example_identifier": "default", + "id": { + "path": "/users", + "method": "GET", + "identifier_override": "endpoint_user.getUser" + }, + "snippet": { + "sync_client": "from seed import SeedApi\n\nclient = SeedApi(\n base_url=\"https://yourhost.com/path/to/api\",\n)\nclient.user.get_user(\n username=\"string\",\n age=1,\n weight=1.1,\n)\n", + "async_client": "import asyncio\n\nfrom seed import AsyncSeedApi\n\nclient = AsyncSeedApi(\n base_url=\"https://yourhost.com/path/to/api\",\n)\n\n\nasync def main() -> None:\n await client.user.get_user(\n username=\"string\",\n age=1,\n weight=1.1,\n )\n\n\nasyncio.run(main())\n", + "type": "python" + } + } + ] +} \ No newline at end of file diff --git a/seed/python-sdk/grpc/src/seed/__init__.py b/seed/python-sdk/grpc/src/seed/__init__.py new file mode 100644 index 00000000000..ef6c8a0f7ef --- /dev/null +++ b/seed/python-sdk/grpc/src/seed/__init__.py @@ -0,0 +1,8 @@ +# This file was auto-generated by Fern from our API Definition. + +from . import user +from .client import AsyncSeedApi, SeedApi +from .user import CreateUserResponse, Metadata, MetadataValue, User +from .version import __version__ + +__all__ = ["AsyncSeedApi", "CreateUserResponse", "Metadata", "MetadataValue", "SeedApi", "User", "__version__", "user"] diff --git a/seed/python-sdk/grpc/src/seed/client.py b/seed/python-sdk/grpc/src/seed/client.py new file mode 100644 index 00000000000..e5b9d9d875e --- /dev/null +++ b/seed/python-sdk/grpc/src/seed/client.py @@ -0,0 +1,104 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import httpx + +from .core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from .user.client import AsyncUserClient, UserClient + + +class SeedApi: + """ + Use this class to access the different functions within the SDK. You can instantiate any number of clients with different configuration that will propagate to these functions. + + Parameters + ---------- + base_url : str + The base url to use for requests from the client. + + timeout : typing.Optional[float] + The timeout to be used, in seconds, for requests. By default the timeout is 60 seconds, unless a custom httpx client is used, in which case this default is not enforced. + + follow_redirects : typing.Optional[bool] + Whether the default httpx client follows redirects or not, this is irrelevant if a custom httpx client is passed in. + + httpx_client : typing.Optional[httpx.Client] + The httpx client to use for making requests, a preconfigured client is used by default, however this is useful should you want to pass in any custom httpx configuration. + + Examples + -------- + from seed import SeedApi + + client = SeedApi( + base_url="https://yourhost.com/path/to/api", + ) + """ + + def __init__( + self, + *, + base_url: str, + timeout: typing.Optional[float] = None, + follow_redirects: typing.Optional[bool] = True, + httpx_client: typing.Optional[httpx.Client] = None + ): + _defaulted_timeout = timeout if timeout is not None else 60 if httpx_client is None else None + self._client_wrapper = SyncClientWrapper( + base_url=base_url, + httpx_client=httpx_client + if httpx_client is not None + else httpx.Client(timeout=_defaulted_timeout, follow_redirects=follow_redirects) + if follow_redirects is not None + else httpx.Client(timeout=_defaulted_timeout), + timeout=_defaulted_timeout, + ) + self.user = UserClient(client_wrapper=self._client_wrapper) + + +class AsyncSeedApi: + """ + Use this class to access the different functions within the SDK. You can instantiate any number of clients with different configuration that will propagate to these functions. + + Parameters + ---------- + base_url : str + The base url to use for requests from the client. + + timeout : typing.Optional[float] + The timeout to be used, in seconds, for requests. By default the timeout is 60 seconds, unless a custom httpx client is used, in which case this default is not enforced. + + follow_redirects : typing.Optional[bool] + Whether the default httpx client follows redirects or not, this is irrelevant if a custom httpx client is passed in. + + httpx_client : typing.Optional[httpx.AsyncClient] + The httpx client to use for making requests, a preconfigured client is used by default, however this is useful should you want to pass in any custom httpx configuration. + + Examples + -------- + from seed import AsyncSeedApi + + client = AsyncSeedApi( + base_url="https://yourhost.com/path/to/api", + ) + """ + + def __init__( + self, + *, + base_url: str, + timeout: typing.Optional[float] = None, + follow_redirects: typing.Optional[bool] = True, + httpx_client: typing.Optional[httpx.AsyncClient] = None + ): + _defaulted_timeout = timeout if timeout is not None else 60 if httpx_client is None else None + self._client_wrapper = AsyncClientWrapper( + base_url=base_url, + httpx_client=httpx_client + if httpx_client is not None + else httpx.AsyncClient(timeout=_defaulted_timeout, follow_redirects=follow_redirects) + if follow_redirects is not None + else httpx.AsyncClient(timeout=_defaulted_timeout), + timeout=_defaulted_timeout, + ) + self.user = AsyncUserClient(client_wrapper=self._client_wrapper) diff --git a/seed/python-sdk/grpc/src/seed/core/__init__.py b/seed/python-sdk/grpc/src/seed/core/__init__.py new file mode 100644 index 00000000000..5a0bee343d0 --- /dev/null +++ b/seed/python-sdk/grpc/src/seed/core/__init__.py @@ -0,0 +1,48 @@ +# This file was auto-generated by Fern from our API Definition. + +from .api_error import ApiError +from .client_wrapper import AsyncClientWrapper, BaseClientWrapper, SyncClientWrapper +from .datetime_utils import serialize_datetime +from .file import File, convert_file_dict_to_httpx_tuples +from .http_client import AsyncHttpClient, HttpClient +from .jsonable_encoder import jsonable_encoder +from .pydantic_utilities import ( + IS_PYDANTIC_V2, + UniversalBaseModel, + UniversalRootModel, + deep_union_pydantic_dicts, + parse_obj_as, + universal_field_validator, + universal_root_validator, + update_forward_refs, +) +from .query_encoder import encode_query +from .remove_none_from_dict import remove_none_from_dict +from .request_options import RequestOptions +from .serialization import FieldMetadata, convert_and_respect_annotation_metadata + +__all__ = [ + "ApiError", + "AsyncClientWrapper", + "AsyncHttpClient", + "BaseClientWrapper", + "FieldMetadata", + "File", + "HttpClient", + "IS_PYDANTIC_V2", + "RequestOptions", + "SyncClientWrapper", + "UniversalBaseModel", + "UniversalRootModel", + "convert_and_respect_annotation_metadata", + "convert_file_dict_to_httpx_tuples", + "deep_union_pydantic_dicts", + "encode_query", + "jsonable_encoder", + "parse_obj_as", + "remove_none_from_dict", + "serialize_datetime", + "universal_field_validator", + "universal_root_validator", + "update_forward_refs", +] diff --git a/seed/python-sdk/grpc/src/seed/core/api_error.py b/seed/python-sdk/grpc/src/seed/core/api_error.py new file mode 100644 index 00000000000..2e9fc5431cd --- /dev/null +++ b/seed/python-sdk/grpc/src/seed/core/api_error.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + + +class ApiError(Exception): + status_code: typing.Optional[int] + body: typing.Any + + def __init__(self, *, status_code: typing.Optional[int] = None, body: typing.Any = None): + self.status_code = status_code + self.body = body + + def __str__(self) -> str: + return f"status_code: {self.status_code}, body: {self.body}" diff --git a/seed/python-sdk/grpc/src/seed/core/client_wrapper.py b/seed/python-sdk/grpc/src/seed/core/client_wrapper.py new file mode 100644 index 00000000000..35fa7809f83 --- /dev/null +++ b/seed/python-sdk/grpc/src/seed/core/client_wrapper.py @@ -0,0 +1,49 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import httpx + +from .http_client import AsyncHttpClient, HttpClient + + +class BaseClientWrapper: + def __init__(self, *, base_url: str, timeout: typing.Optional[float] = None): + self._base_url = base_url + self._timeout = timeout + + def get_headers(self) -> typing.Dict[str, str]: + headers: typing.Dict[str, str] = { + "X-Fern-Language": "Python", + "X-Fern-SDK-Name": "fern_grpc", + "X-Fern-SDK-Version": "0.0.1", + } + return headers + + def get_base_url(self) -> str: + return self._base_url + + def get_timeout(self) -> typing.Optional[float]: + return self._timeout + + +class SyncClientWrapper(BaseClientWrapper): + def __init__(self, *, base_url: str, timeout: typing.Optional[float] = None, httpx_client: httpx.Client): + super().__init__(base_url=base_url, timeout=timeout) + self.httpx_client = HttpClient( + httpx_client=httpx_client, + base_headers=self.get_headers(), + base_timeout=self.get_timeout(), + base_url=self.get_base_url(), + ) + + +class AsyncClientWrapper(BaseClientWrapper): + def __init__(self, *, base_url: str, timeout: typing.Optional[float] = None, httpx_client: httpx.AsyncClient): + super().__init__(base_url=base_url, timeout=timeout) + self.httpx_client = AsyncHttpClient( + httpx_client=httpx_client, + base_headers=self.get_headers(), + base_timeout=self.get_timeout(), + base_url=self.get_base_url(), + ) diff --git a/seed/python-sdk/grpc/src/seed/core/datetime_utils.py b/seed/python-sdk/grpc/src/seed/core/datetime_utils.py new file mode 100644 index 00000000000..7c9864a944c --- /dev/null +++ b/seed/python-sdk/grpc/src/seed/core/datetime_utils.py @@ -0,0 +1,28 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt + + +def serialize_datetime(v: dt.datetime) -> str: + """ + Serialize a datetime including timezone info. + + Uses the timezone info provided if present, otherwise uses the current runtime's timezone info. + + UTC datetimes end in "Z" while all other timezones are represented as offset from UTC, e.g. +05:00. + """ + + def _serialize_zoned_datetime(v: dt.datetime) -> str: + if v.tzinfo is not None and v.tzinfo.tzname(None) == dt.timezone.utc.tzname(None): + # UTC is a special case where we use "Z" at the end instead of "+00:00" + return v.isoformat().replace("+00:00", "Z") + else: + # Delegate to the typical +/- offset format + return v.isoformat() + + if v.tzinfo is not None: + return _serialize_zoned_datetime(v) + else: + local_tz = dt.datetime.now().astimezone().tzinfo + localized_dt = v.replace(tzinfo=local_tz) + return _serialize_zoned_datetime(localized_dt) diff --git a/seed/python-sdk/grpc/src/seed/core/file.py b/seed/python-sdk/grpc/src/seed/core/file.py new file mode 100644 index 00000000000..cb0d40bbbf3 --- /dev/null +++ b/seed/python-sdk/grpc/src/seed/core/file.py @@ -0,0 +1,38 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +# File typing inspired by the flexibility of types within the httpx library +# https://github.com/encode/httpx/blob/master/httpx/_types.py +FileContent = typing.Union[typing.IO[bytes], bytes, str] +File = typing.Union[ + # file (or bytes) + FileContent, + # (filename, file (or bytes)) + typing.Tuple[typing.Optional[str], FileContent], + # (filename, file (or bytes), content_type) + typing.Tuple[typing.Optional[str], FileContent, typing.Optional[str]], + # (filename, file (or bytes), content_type, headers) + typing.Tuple[typing.Optional[str], FileContent, typing.Optional[str], typing.Mapping[str, str]], +] + + +def convert_file_dict_to_httpx_tuples( + d: typing.Dict[str, typing.Union[File, typing.List[File]]] +) -> typing.List[typing.Tuple[str, File]]: + """ + The format we use is a list of tuples, where the first element is the + name of the file and the second is the file object. Typically HTTPX wants + a dict, but to be able to send lists of files, you have to use the list + approach (which also works for non-lists) + https://github.com/encode/httpx/pull/1032 + """ + + httpx_tuples = [] + for key, file_like in d.items(): + if isinstance(file_like, list): + for file_like_item in file_like: + httpx_tuples.append((key, file_like_item)) + else: + httpx_tuples.append((key, file_like)) + return httpx_tuples diff --git a/seed/python-sdk/grpc/src/seed/core/http_client.py b/seed/python-sdk/grpc/src/seed/core/http_client.py new file mode 100644 index 00000000000..356880bbc3e --- /dev/null +++ b/seed/python-sdk/grpc/src/seed/core/http_client.py @@ -0,0 +1,476 @@ +# This file was auto-generated by Fern from our API Definition. + +import asyncio +import email.utils +import json +import re +import time +import typing +import urllib.parse +from contextlib import asynccontextmanager, contextmanager +from random import random + +import httpx + +from .file import File, convert_file_dict_to_httpx_tuples +from .jsonable_encoder import jsonable_encoder +from .query_encoder import encode_query +from .remove_none_from_dict import remove_none_from_dict +from .request_options import RequestOptions + +INITIAL_RETRY_DELAY_SECONDS = 0.5 +MAX_RETRY_DELAY_SECONDS = 10 +MAX_RETRY_DELAY_SECONDS_FROM_HEADER = 30 + + +def _parse_retry_after(response_headers: httpx.Headers) -> typing.Optional[float]: + """ + This function parses the `Retry-After` header in a HTTP response and returns the number of seconds to wait. + + Inspired by the urllib3 retry implementation. + """ + retry_after_ms = response_headers.get("retry-after-ms") + if retry_after_ms is not None: + try: + return int(retry_after_ms) / 1000 if retry_after_ms > 0 else 0 + except Exception: + pass + + retry_after = response_headers.get("retry-after") + if retry_after is None: + return None + + # Attempt to parse the header as an int. + if re.match(r"^\s*[0-9]+\s*$", retry_after): + seconds = float(retry_after) + # Fallback to parsing it as a date. + else: + retry_date_tuple = email.utils.parsedate_tz(retry_after) + if retry_date_tuple is None: + return None + if retry_date_tuple[9] is None: # Python 2 + # Assume UTC if no timezone was specified + # On Python2.7, parsedate_tz returns None for a timezone offset + # instead of 0 if no timezone is given, where mktime_tz treats + # a None timezone offset as local time. + retry_date_tuple = retry_date_tuple[:9] + (0,) + retry_date_tuple[10:] + + retry_date = email.utils.mktime_tz(retry_date_tuple) + seconds = retry_date - time.time() + + if seconds < 0: + seconds = 0 + + return seconds + + +def _retry_timeout(response: httpx.Response, retries: int) -> float: + """ + Determine the amount of time to wait before retrying a request. + This function begins by trying to parse a retry-after header from the response, and then proceeds to use exponential backoff + with a jitter to determine the number of seconds to wait. + """ + + # If the API asks us to wait a certain amount of time (and it's a reasonable amount), just do what it says. + retry_after = _parse_retry_after(response.headers) + if retry_after is not None and retry_after <= MAX_RETRY_DELAY_SECONDS_FROM_HEADER: + return retry_after + + # Apply exponential backoff, capped at MAX_RETRY_DELAY_SECONDS. + retry_delay = min(INITIAL_RETRY_DELAY_SECONDS * pow(2.0, retries), MAX_RETRY_DELAY_SECONDS) + + # Add a randomness / jitter to the retry delay to avoid overwhelming the server with retries. + timeout = retry_delay * (1 - 0.25 * random()) + return timeout if timeout >= 0 else 0 + + +def _should_retry(response: httpx.Response) -> bool: + retriable_400s = [429, 408, 409] + return response.status_code >= 500 or response.status_code in retriable_400s + + +def remove_omit_from_dict( + original: typing.Dict[str, typing.Optional[typing.Any]], omit: typing.Optional[typing.Any] +) -> typing.Dict[str, typing.Any]: + if omit is None: + return original + new: typing.Dict[str, typing.Any] = {} + for key, value in original.items(): + if value is not omit: + new[key] = value + return new + + +def maybe_filter_request_body( + data: typing.Optional[typing.Any], + request_options: typing.Optional[RequestOptions], + omit: typing.Optional[typing.Any], +) -> typing.Optional[typing.Any]: + if data is None: + return ( + jsonable_encoder(request_options.get("additional_body_parameters", {})) or {} + if request_options is not None + else None + ) + elif not isinstance(data, typing.Mapping): + data_content = jsonable_encoder(data) + else: + data_content = { + **(jsonable_encoder(remove_omit_from_dict(data, omit))), # type: ignore + **( + jsonable_encoder(request_options.get("additional_body_parameters", {})) or {} + if request_options is not None + else {} + ), + } + return data_content + + +# Abstracted out for testing purposes +def get_request_body( + *, + json: typing.Optional[typing.Any], + data: typing.Optional[typing.Any], + request_options: typing.Optional[RequestOptions], + omit: typing.Optional[typing.Any], +) -> typing.Tuple[typing.Optional[typing.Any], typing.Optional[typing.Any]]: + json_body = None + data_body = None + if data is not None: + data_body = maybe_filter_request_body(data, request_options, omit) + else: + # If both data and json are None, we send json data in the event extra properties are specified + json_body = maybe_filter_request_body(json, request_options, omit) + + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None + + +class HttpClient: + def __init__( + self, + *, + httpx_client: httpx.Client, + base_timeout: typing.Optional[float], + base_headers: typing.Dict[str, str], + base_url: typing.Optional[str] = None, + ): + self.base_url = base_url + self.base_timeout = base_timeout + self.base_headers = base_headers + self.httpx_client = httpx_client + + def get_base_url(self, maybe_base_url: typing.Optional[str]) -> str: + base_url = self.base_url if maybe_base_url is None else maybe_base_url + if base_url is None: + raise ValueError("A base_url is required to make this request, please provide one and try again.") + return base_url + + def request( + self, + path: typing.Optional[str] = None, + *, + method: str, + base_url: typing.Optional[str] = None, + params: typing.Optional[typing.Dict[str, typing.Any]] = None, + json: typing.Optional[typing.Any] = None, + data: typing.Optional[typing.Any] = None, + content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None, + files: typing.Optional[typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]]] = None, + headers: typing.Optional[typing.Dict[str, typing.Any]] = None, + request_options: typing.Optional[RequestOptions] = None, + retries: int = 0, + omit: typing.Optional[typing.Any] = None, + ) -> httpx.Response: + base_url = self.get_base_url(base_url) + timeout = ( + request_options.get("timeout_in_seconds") + if request_options is not None and request_options.get("timeout_in_seconds") is not None + else self.base_timeout + ) + + json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit) + + response = self.httpx_client.request( + method=method, + url=urllib.parse.urljoin(f"{base_url}/", path), + headers=jsonable_encoder( + remove_none_from_dict( + { + **self.base_headers, + **(headers if headers is not None else {}), + **(request_options.get("additional_headers", {}) or {} if request_options is not None else {}), + } + ) + ), + params=encode_query( + jsonable_encoder( + remove_none_from_dict( + remove_omit_from_dict( + { + **(params if params is not None else {}), + **( + request_options.get("additional_query_parameters", {}) or {} + if request_options is not None + else {} + ), + }, + omit, + ) + ) + ) + ), + json=json_body, + data=data_body, + content=content, + files=convert_file_dict_to_httpx_tuples(remove_none_from_dict(files)) if files is not None else None, + timeout=timeout, + ) + + max_retries: int = request_options.get("max_retries", 0) if request_options is not None else 0 + if _should_retry(response=response): + if max_retries > retries: + time.sleep(_retry_timeout(response=response, retries=retries)) + return self.request( + path=path, + method=method, + base_url=base_url, + params=params, + json=json, + content=content, + files=files, + headers=headers, + request_options=request_options, + retries=retries + 1, + omit=omit, + ) + + return response + + @contextmanager + def stream( + self, + path: typing.Optional[str] = None, + *, + method: str, + base_url: typing.Optional[str] = None, + params: typing.Optional[typing.Dict[str, typing.Any]] = None, + json: typing.Optional[typing.Any] = None, + data: typing.Optional[typing.Any] = None, + content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None, + files: typing.Optional[typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]]] = None, + headers: typing.Optional[typing.Dict[str, typing.Any]] = None, + request_options: typing.Optional[RequestOptions] = None, + retries: int = 0, + omit: typing.Optional[typing.Any] = None, + ) -> typing.Iterator[httpx.Response]: + base_url = self.get_base_url(base_url) + timeout = ( + request_options.get("timeout_in_seconds") + if request_options is not None and request_options.get("timeout_in_seconds") is not None + else self.base_timeout + ) + + json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit) + + with self.httpx_client.stream( + method=method, + url=urllib.parse.urljoin(f"{base_url}/", path), + headers=jsonable_encoder( + remove_none_from_dict( + { + **self.base_headers, + **(headers if headers is not None else {}), + **(request_options.get("additional_headers", {}) if request_options is not None else {}), + } + ) + ), + params=encode_query( + jsonable_encoder( + remove_none_from_dict( + remove_omit_from_dict( + { + **(params if params is not None else {}), + **( + request_options.get("additional_query_parameters", {}) + if request_options is not None + else {} + ), + }, + omit, + ) + ) + ) + ), + json=json_body, + data=data_body, + content=content, + files=convert_file_dict_to_httpx_tuples(remove_none_from_dict(files)) if files is not None else None, + timeout=timeout, + ) as stream: + yield stream + + +class AsyncHttpClient: + def __init__( + self, + *, + httpx_client: httpx.AsyncClient, + base_timeout: typing.Optional[float], + base_headers: typing.Dict[str, str], + base_url: typing.Optional[str] = None, + ): + self.base_url = base_url + self.base_timeout = base_timeout + self.base_headers = base_headers + self.httpx_client = httpx_client + + def get_base_url(self, maybe_base_url: typing.Optional[str]) -> str: + base_url = self.base_url if maybe_base_url is None else maybe_base_url + if base_url is None: + raise ValueError("A base_url is required to make this request, please provide one and try again.") + return base_url + + async def request( + self, + path: typing.Optional[str] = None, + *, + method: str, + base_url: typing.Optional[str] = None, + params: typing.Optional[typing.Dict[str, typing.Any]] = None, + json: typing.Optional[typing.Any] = None, + data: typing.Optional[typing.Any] = None, + content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None, + files: typing.Optional[typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]]] = None, + headers: typing.Optional[typing.Dict[str, typing.Any]] = None, + request_options: typing.Optional[RequestOptions] = None, + retries: int = 0, + omit: typing.Optional[typing.Any] = None, + ) -> httpx.Response: + base_url = self.get_base_url(base_url) + timeout = ( + request_options.get("timeout_in_seconds") + if request_options is not None and request_options.get("timeout_in_seconds") is not None + else self.base_timeout + ) + + json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit) + + # Add the input to each of these and do None-safety checks + response = await self.httpx_client.request( + method=method, + url=urllib.parse.urljoin(f"{base_url}/", path), + headers=jsonable_encoder( + remove_none_from_dict( + { + **self.base_headers, + **(headers if headers is not None else {}), + **(request_options.get("additional_headers", {}) or {} if request_options is not None else {}), + } + ) + ), + params=encode_query( + jsonable_encoder( + remove_none_from_dict( + remove_omit_from_dict( + { + **(params if params is not None else {}), + **( + request_options.get("additional_query_parameters", {}) or {} + if request_options is not None + else {} + ), + }, + omit, + ) + ) + ) + ), + json=json_body, + data=data_body, + content=content, + files=convert_file_dict_to_httpx_tuples(remove_none_from_dict(files)) if files is not None else None, + timeout=timeout, + ) + + max_retries: int = request_options.get("max_retries", 0) if request_options is not None else 0 + if _should_retry(response=response): + if max_retries > retries: + await asyncio.sleep(_retry_timeout(response=response, retries=retries)) + return await self.request( + path=path, + method=method, + base_url=base_url, + params=params, + json=json, + content=content, + files=files, + headers=headers, + request_options=request_options, + retries=retries + 1, + omit=omit, + ) + return response + + @asynccontextmanager + async def stream( + self, + path: typing.Optional[str] = None, + *, + method: str, + base_url: typing.Optional[str] = None, + params: typing.Optional[typing.Dict[str, typing.Any]] = None, + json: typing.Optional[typing.Any] = None, + data: typing.Optional[typing.Any] = None, + content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None, + files: typing.Optional[typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]]] = None, + headers: typing.Optional[typing.Dict[str, typing.Any]] = None, + request_options: typing.Optional[RequestOptions] = None, + retries: int = 0, + omit: typing.Optional[typing.Any] = None, + ) -> typing.AsyncIterator[httpx.Response]: + base_url = self.get_base_url(base_url) + timeout = ( + request_options.get("timeout_in_seconds") + if request_options is not None and request_options.get("timeout_in_seconds") is not None + else self.base_timeout + ) + + json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit) + + async with self.httpx_client.stream( + method=method, + url=urllib.parse.urljoin(f"{base_url}/", path), + headers=jsonable_encoder( + remove_none_from_dict( + { + **self.base_headers, + **(headers if headers is not None else {}), + **(request_options.get("additional_headers", {}) if request_options is not None else {}), + } + ) + ), + params=encode_query( + jsonable_encoder( + remove_none_from_dict( + remove_omit_from_dict( + { + **(params if params is not None else {}), + **( + request_options.get("additional_query_parameters", {}) + if request_options is not None + else {} + ), + }, + omit=omit, + ) + ) + ) + ), + json=json_body, + data=data_body, + content=content, + files=convert_file_dict_to_httpx_tuples(remove_none_from_dict(files)) if files is not None else None, + timeout=timeout, + ) as stream: + yield stream diff --git a/seed/python-sdk/grpc/src/seed/core/jsonable_encoder.py b/seed/python-sdk/grpc/src/seed/core/jsonable_encoder.py new file mode 100644 index 00000000000..d3fd328fd41 --- /dev/null +++ b/seed/python-sdk/grpc/src/seed/core/jsonable_encoder.py @@ -0,0 +1,97 @@ +# This file was auto-generated by Fern from our API Definition. + +""" +jsonable_encoder converts a Python object to a JSON-friendly dict +(e.g. datetimes to strings, Pydantic models to dicts). + +Taken from FastAPI, and made a bit simpler +https://github.com/tiangolo/fastapi/blob/master/fastapi/encoders.py +""" + +import base64 +import dataclasses +import datetime as dt +from enum import Enum +from pathlib import PurePath +from types import GeneratorType +from typing import Any, Callable, Dict, List, Optional, Set, Union + +import pydantic + +from .datetime_utils import serialize_datetime +from .pydantic_utilities import IS_PYDANTIC_V2, encode_by_type, to_jsonable_with_fallback + +SetIntStr = Set[Union[int, str]] +DictIntStrAny = Dict[Union[int, str], Any] + + +def jsonable_encoder(obj: Any, custom_encoder: Optional[Dict[Any, Callable[[Any], Any]]] = None) -> Any: + custom_encoder = custom_encoder or {} + if custom_encoder: + if type(obj) in custom_encoder: + return custom_encoder[type(obj)](obj) + else: + for encoder_type, encoder_instance in custom_encoder.items(): + if isinstance(obj, encoder_type): + return encoder_instance(obj) + if isinstance(obj, pydantic.BaseModel): + if IS_PYDANTIC_V2: + encoder = getattr(obj.model_config, "json_encoders", {}) # type: ignore # Pydantic v2 + else: + encoder = getattr(obj.__config__, "json_encoders", {}) # type: ignore # Pydantic v1 + if custom_encoder: + encoder.update(custom_encoder) + obj_dict = obj.dict(by_alias=True) + if "__root__" in obj_dict: + obj_dict = obj_dict["__root__"] + if "root" in obj_dict: + obj_dict = obj_dict["root"] + return jsonable_encoder(obj_dict, custom_encoder=encoder) + if dataclasses.is_dataclass(obj): + obj_dict = dataclasses.asdict(obj) # type: ignore + return jsonable_encoder(obj_dict, custom_encoder=custom_encoder) + if isinstance(obj, bytes): + return base64.b64encode(obj).decode("utf-8") + if isinstance(obj, Enum): + return obj.value + if isinstance(obj, PurePath): + return str(obj) + if isinstance(obj, (str, int, float, type(None))): + return obj + if isinstance(obj, dt.datetime): + return serialize_datetime(obj) + if isinstance(obj, dt.date): + return str(obj) + if isinstance(obj, dict): + encoded_dict = {} + allowed_keys = set(obj.keys()) + for key, value in obj.items(): + if key in allowed_keys: + encoded_key = jsonable_encoder(key, custom_encoder=custom_encoder) + encoded_value = jsonable_encoder(value, custom_encoder=custom_encoder) + encoded_dict[encoded_key] = encoded_value + return encoded_dict + if isinstance(obj, (list, set, frozenset, GeneratorType, tuple)): + encoded_list = [] + for item in obj: + encoded_list.append(jsonable_encoder(item, custom_encoder=custom_encoder)) + return encoded_list + + def fallback_serializer(o: Any) -> Any: + attempt_encode = encode_by_type(o) + if attempt_encode is not None: + return attempt_encode + + try: + data = dict(o) + except Exception as e: + errors: List[Exception] = [] + errors.append(e) + try: + data = vars(o) + except Exception as e: + errors.append(e) + raise ValueError(errors) from e + return jsonable_encoder(data, custom_encoder=custom_encoder) + + return to_jsonable_with_fallback(obj, fallback_serializer) diff --git a/seed/python-sdk/grpc/src/seed/core/pydantic_utilities.py b/seed/python-sdk/grpc/src/seed/core/pydantic_utilities.py new file mode 100644 index 00000000000..d7fb87bf581 --- /dev/null +++ b/seed/python-sdk/grpc/src/seed/core/pydantic_utilities.py @@ -0,0 +1,179 @@ +# This file was auto-generated by Fern from our API Definition. + +# nopycln: file +import datetime as dt +import typing +from collections import defaultdict +from functools import wraps + +import pydantic + +from .datetime_utils import serialize_datetime + +IS_PYDANTIC_V2 = pydantic.VERSION.startswith("2.") + +if IS_PYDANTIC_V2: + # isort will try to reformat the comments on these imports, which breaks mypy + # isort: off + from pydantic.v1.datetime_parse import ( # type: ignore # pyright: ignore[reportMissingImports] # Pydantic v2 + parse_date as parse_date, + ) + from pydantic.v1.datetime_parse import ( # pyright: ignore[reportMissingImports] # Pydantic v2 + parse_datetime as parse_datetime, + ) + from pydantic.v1.json import ( # type: ignore # pyright: ignore[reportMissingImports] # Pydantic v2 + ENCODERS_BY_TYPE as encoders_by_type, + ) + from pydantic.v1.typing import ( # type: ignore # pyright: ignore[reportMissingImports] # Pydantic v2 + get_args as get_args, + ) + from pydantic.v1.typing import get_origin as get_origin # pyright: ignore[reportMissingImports] # Pydantic v2 + from pydantic.v1.typing import ( # pyright: ignore[reportMissingImports] # Pydantic v2 + is_literal_type as is_literal_type, + ) + from pydantic.v1.typing import is_union as is_union # pyright: ignore[reportMissingImports] # Pydantic v2 + from pydantic.v1.fields import ModelField as ModelField # type: ignore # pyright: ignore[reportMissingImports] # Pydantic v2 +else: + from pydantic.datetime_parse import parse_date as parse_date # type: ignore # Pydantic v1 + from pydantic.datetime_parse import parse_datetime as parse_datetime # type: ignore # Pydantic v1 + from pydantic.fields import ModelField as ModelField # type: ignore # Pydantic v1 + from pydantic.json import ENCODERS_BY_TYPE as encoders_by_type # type: ignore # Pydantic v1 + from pydantic.typing import get_args as get_args # type: ignore # Pydantic v1 + from pydantic.typing import get_origin as get_origin # type: ignore # Pydantic v1 + from pydantic.typing import is_literal_type as is_literal_type # type: ignore # Pydantic v1 + from pydantic.typing import is_union as is_union # type: ignore # Pydantic v1 + + # isort: on + + +T = typing.TypeVar("T") +Model = typing.TypeVar("Model", bound=pydantic.BaseModel) + + +def deep_union_pydantic_dicts( + source: typing.Dict[str, typing.Any], destination: typing.Dict[str, typing.Any] +) -> typing.Dict[str, typing.Any]: + for key, value in source.items(): + if isinstance(value, dict): + node = destination.setdefault(key, {}) + deep_union_pydantic_dicts(value, node) + else: + destination[key] = value + + return destination + + +def parse_obj_as(type_: typing.Type[T], object_: typing.Any) -> T: + if IS_PYDANTIC_V2: + adapter = pydantic.TypeAdapter(type_) # type: ignore # Pydantic v2 + return adapter.validate_python(object_) + else: + return pydantic.parse_obj_as(type_, object_) + + +def to_jsonable_with_fallback( + obj: typing.Any, fallback_serializer: typing.Callable[[typing.Any], typing.Any] +) -> typing.Any: + if IS_PYDANTIC_V2: + from pydantic_core import to_jsonable_python + + return to_jsonable_python(obj, fallback=fallback_serializer) + else: + return fallback_serializer(obj) + + +class UniversalBaseModel(pydantic.BaseModel): + class Config: + populate_by_name = True + smart_union = True + allow_population_by_field_name = True + json_encoders = {dt.datetime: serialize_datetime} + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = {"by_alias": True, "exclude_unset": True, **kwargs} + if IS_PYDANTIC_V2: + return super().model_dump_json(**kwargs_with_defaults) # type: ignore # Pydantic v2 + else: + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + kwargs_with_defaults_exclude_unset: typing.Any = {"by_alias": True, "exclude_unset": True, **kwargs} + kwargs_with_defaults_exclude_none: typing.Any = {"by_alias": True, "exclude_none": True, **kwargs} + + if IS_PYDANTIC_V2: + return deep_union_pydantic_dicts( + super().model_dump(**kwargs_with_defaults_exclude_unset), # type: ignore # Pydantic v2 + super().model_dump(**kwargs_with_defaults_exclude_none), # type: ignore # Pydantic v2 + ) + else: + return deep_union_pydantic_dicts( + super().dict(**kwargs_with_defaults_exclude_unset), super().dict(**kwargs_with_defaults_exclude_none) + ) + + +UniversalRootModel: typing.Type[pydantic.BaseModel] +if IS_PYDANTIC_V2: + + class V2RootModel(UniversalBaseModel, pydantic.RootModel): # type: ignore # Pydantic v2 + pass + + UniversalRootModel = V2RootModel +else: + UniversalRootModel = UniversalBaseModel + + +def encode_by_type(o: typing.Any) -> typing.Any: + encoders_by_class_tuples: typing.Dict[ + typing.Callable[[typing.Any], typing.Any], typing.Tuple[typing.Any, ...] + ] = defaultdict(tuple) + for type_, encoder in encoders_by_type.items(): + encoders_by_class_tuples[encoder] += (type_,) + + if type(o) in encoders_by_type: + return encoders_by_type[type(o)](o) + for encoder, classes_tuple in encoders_by_class_tuples.items(): + if isinstance(o, classes_tuple): + return encoder(o) + + +def update_forward_refs(model: typing.Type["Model"], **localns: typing.Any) -> None: + if IS_PYDANTIC_V2: + model.model_rebuild(force=True, raise_errors=False) # type: ignore # Pydantic v2 + else: + model.update_forward_refs(**localns) + + +# Mirrors Pydantic's internal typing +AnyCallable = typing.Callable[..., typing.Any] + + +def universal_root_validator(pre: bool = False) -> typing.Callable[[AnyCallable], AnyCallable]: + def decorator(func: AnyCallable) -> AnyCallable: + @wraps(func) + def validate(*args: typing.Any, **kwargs: typing.Any) -> AnyCallable: + if IS_PYDANTIC_V2: + wrapped_func = pydantic.model_validator("before" if pre else "after")(func) # type: ignore # Pydantic v2 + else: + wrapped_func = pydantic.root_validator(pre=pre)(func) # type: ignore # Pydantic v1 + + return wrapped_func(*args, **kwargs) # type: ignore # Pydantic v2 + + return validate + + return decorator + + +def universal_field_validator(field_name: str, pre: bool = False) -> typing.Callable[[AnyCallable], AnyCallable]: + def decorator(func: AnyCallable) -> AnyCallable: + @wraps(func) + def validate(*args: typing.Any, **kwargs: typing.Any) -> AnyCallable: + if IS_PYDANTIC_V2: + wrapped_func = pydantic.field_validator(field_name, mode="before" if pre else "after")(func) # type: ignore # Pydantic v2 + else: + wrapped_func = pydantic.validator(field_name, pre=pre)(func) # type: ignore # Pydantic v1 + + return wrapped_func(*args, **kwargs) + + return validate + + return decorator diff --git a/seed/python-sdk/grpc/src/seed/core/query_encoder.py b/seed/python-sdk/grpc/src/seed/core/query_encoder.py new file mode 100644 index 00000000000..24076d72ee9 --- /dev/null +++ b/seed/python-sdk/grpc/src/seed/core/query_encoder.py @@ -0,0 +1,33 @@ +# This file was auto-generated by Fern from our API Definition. + +from collections import ChainMap +from typing import Any, Dict, Optional + +import pydantic + + +# Flattens dicts to be of the form {"key[subkey][subkey2]": value} where value is not a dict +def traverse_query_dict(dict_flat: Dict[str, Any], key_prefix: Optional[str] = None) -> Dict[str, Any]: + result = {} + for k, v in dict_flat.items(): + key = f"{key_prefix}[{k}]" if key_prefix is not None else k + if isinstance(v, dict): + result.update(traverse_query_dict(v, key)) + else: + result[key] = v + return result + + +def single_query_encoder(query_key: str, query_value: Any) -> Dict[str, Any]: + if isinstance(query_value, pydantic.BaseModel) or isinstance(query_value, dict): + if isinstance(query_value, pydantic.BaseModel): + obj_dict = query_value.dict(by_alias=True) + else: + obj_dict = query_value + return traverse_query_dict(obj_dict, query_key) + + return {query_key: query_value} + + +def encode_query(query: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]: + return dict(ChainMap(*[single_query_encoder(k, v) for k, v in query.items()])) if query is not None else None diff --git a/seed/python-sdk/grpc/src/seed/core/remove_none_from_dict.py b/seed/python-sdk/grpc/src/seed/core/remove_none_from_dict.py new file mode 100644 index 00000000000..c2298143f14 --- /dev/null +++ b/seed/python-sdk/grpc/src/seed/core/remove_none_from_dict.py @@ -0,0 +1,11 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import Any, Dict, Mapping, Optional + + +def remove_none_from_dict(original: Mapping[str, Optional[Any]]) -> Dict[str, Any]: + new: Dict[str, Any] = {} + for key, value in original.items(): + if value is not None: + new[key] = value + return new diff --git a/seed/python-sdk/grpc/src/seed/core/request_options.py b/seed/python-sdk/grpc/src/seed/core/request_options.py new file mode 100644 index 00000000000..d0bf0dbcecd --- /dev/null +++ b/seed/python-sdk/grpc/src/seed/core/request_options.py @@ -0,0 +1,32 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +try: + from typing import NotRequired # type: ignore +except ImportError: + from typing_extensions import NotRequired + + +class RequestOptions(typing.TypedDict, total=False): + """ + Additional options for request-specific configuration when calling APIs via the SDK. + This is used primarily as an optional final parameter for service functions. + + Attributes: + - timeout_in_seconds: int. The number of seconds to await an API call before timing out. + + - max_retries: int. The max number of retries to attempt if the API call fails. + + - additional_headers: typing.Dict[str, typing.Any]. A dictionary containing additional parameters to spread into the request's header dict + + - additional_query_parameters: typing.Dict[str, typing.Any]. A dictionary containing additional parameters to spread into the request's query parameters dict + + - additional_body_parameters: typing.Dict[str, typing.Any]. A dictionary containing additional parameters to spread into the request's body parameters dict + """ + + timeout_in_seconds: NotRequired[int] + max_retries: NotRequired[int] + additional_headers: NotRequired[typing.Dict[str, typing.Any]] + additional_query_parameters: NotRequired[typing.Dict[str, typing.Any]] + additional_body_parameters: NotRequired[typing.Dict[str, typing.Any]] diff --git a/seed/python-sdk/grpc/src/seed/core/serialization.py b/seed/python-sdk/grpc/src/seed/core/serialization.py new file mode 100644 index 00000000000..8ad5cf8125f --- /dev/null +++ b/seed/python-sdk/grpc/src/seed/core/serialization.py @@ -0,0 +1,167 @@ +# This file was auto-generated by Fern from our API Definition. + +import collections +import typing + +import typing_extensions + + +class FieldMetadata: + """ + Metadata class used to annotate fields to provide additional information. + + Example: + class MyDict(TypedDict): + field: typing.Annotated[str, FieldMetadata(alias="field_name")] + + Will serialize: `{"field": "value"}` + To: `{"field_name": "value"}` + """ + + alias: str + + def __init__(self, *, alias: str) -> None: + self.alias = alias + + +def convert_and_respect_annotation_metadata( + *, object_: typing.Any, annotation: typing.Any, inner_type: typing.Optional[typing.Any] = None +) -> typing.Any: + """ + Respect the metadata annotations on a field, such as aliasing. This function effectively + manipulates the dict-form of an object to respect the metadata annotations. This is primarily used for + TypedDicts, which cannot support aliasing out of the box, and can be extended for additional + utilities, such as defaults. + + Parameters + ---------- + object_ : typing.Any + + annotation : type + The type we're looking to apply typing annotations from + + inner_type : typing.Optional[type] + + Returns + ------- + typing.Any + """ + + if object_ is None: + return None + if inner_type is None: + inner_type = annotation + + clean_type = _remove_annotations(inner_type) + if typing_extensions.is_typeddict(clean_type) and isinstance(object_, typing.Mapping): + return _convert_typeddict(object_, clean_type) + + if ( + # If you're iterating on a string, do not bother to coerce it to a sequence. + (not isinstance(object_, str)) + and ( + ( + ( + typing_extensions.get_origin(clean_type) == typing.List + or typing_extensions.get_origin(clean_type) == list + or clean_type == typing.List + ) + and isinstance(object_, typing.List) + ) + or ( + ( + typing_extensions.get_origin(clean_type) == typing.Set + or typing_extensions.get_origin(clean_type) == set + or clean_type == typing.Set + ) + and isinstance(object_, typing.Set) + ) + or ( + ( + typing_extensions.get_origin(clean_type) == typing.Sequence + or typing_extensions.get_origin(clean_type) == collections.abc.Sequence + or clean_type == typing.Sequence + ) + and isinstance(object_, typing.Sequence) + ) + ) + ): + inner_type = typing_extensions.get_args(clean_type)[0] + return [ + convert_and_respect_annotation_metadata(object_=item, annotation=annotation, inner_type=inner_type) + for item in object_ + ] + + if typing_extensions.get_origin(clean_type) == typing.Union: + # We should be able to ~relatively~ safely try to convert keys against all + # member types in the union, the edge case here is if one member aliases a field + # of the same name to a different name from another member + # Or if another member aliases a field of the same name that another member does not. + for member in typing_extensions.get_args(clean_type): + object_ = convert_and_respect_annotation_metadata(object_=object_, annotation=annotation, inner_type=member) + return object_ + + annotated_type = _get_annotation(annotation) + if annotated_type is None: + return object_ + + # If the object is not a TypedDict, a Union, or other container (list, set, sequence, etc.) + # Then we can safely call it on the recursive conversion. + return object_ + + +def _convert_typeddict(object_: typing.Mapping[str, object], expected_type: typing.Any) -> typing.Mapping[str, object]: + converted_object: typing.Dict[str, object] = {} + annotations = typing_extensions.get_type_hints(expected_type, include_extras=True) + for key, value in object_.items(): + type_ = annotations.get(key) + if type_ is None: + converted_object[key] = value + else: + converted_object[_alias_key(key, type_)] = convert_and_respect_annotation_metadata( + object_=value, annotation=type_ + ) + return converted_object + + +def _get_annotation(type_: typing.Any) -> typing.Optional[typing.Any]: + maybe_annotated_type = typing_extensions.get_origin(type_) + if maybe_annotated_type is None: + return None + + if maybe_annotated_type == typing_extensions.NotRequired: + type_ = typing_extensions.get_args(type_)[0] + maybe_annotated_type = typing_extensions.get_origin(type_) + + if maybe_annotated_type == typing_extensions.Annotated: + return type_ + + return None + + +def _remove_annotations(type_: typing.Any) -> typing.Any: + maybe_annotated_type = typing_extensions.get_origin(type_) + if maybe_annotated_type is None: + return type_ + + if maybe_annotated_type == typing_extensions.NotRequired: + return _remove_annotations(typing_extensions.get_args(type_)[0]) + + if maybe_annotated_type == typing_extensions.Annotated: + return _remove_annotations(typing_extensions.get_args(type_)[0]) + + return type_ + + +def _alias_key(key: str, type_: typing.Any) -> str: + maybe_annotated_type = _get_annotation(type_) + + if maybe_annotated_type is not None: + # The actual annotations are 1 onward, the first is the annotated type + annotations = typing_extensions.get_args(maybe_annotated_type)[1:] + + for annotation in annotations: + if isinstance(annotation, FieldMetadata) and annotation.alias is not None: + return annotation.alias + + return key diff --git a/seed/python-sdk/grpc/src/seed/py.typed b/seed/python-sdk/grpc/src/seed/py.typed new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/python-sdk/grpc/src/seed/user/__init__.py b/seed/python-sdk/grpc/src/seed/user/__init__.py new file mode 100644 index 00000000000..ebe658e18c3 --- /dev/null +++ b/seed/python-sdk/grpc/src/seed/user/__init__.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +from .types import CreateUserResponse, Metadata, MetadataValue, User + +__all__ = ["CreateUserResponse", "Metadata", "MetadataValue", "User"] diff --git a/seed/python-sdk/grpc/src/seed/user/client.py b/seed/python-sdk/grpc/src/seed/user/client.py new file mode 100644 index 00000000000..c953b88ba3c --- /dev/null +++ b/seed/python-sdk/grpc/src/seed/user/client.py @@ -0,0 +1,255 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.jsonable_encoder import jsonable_encoder +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from .types.create_user_response import CreateUserResponse +from .types.user import User + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class UserClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def create_user( + self, + *, + username: str, + email: typing.Optional[str] = OMIT, + age: typing.Optional[int] = OMIT, + weight: typing.Optional[float] = OMIT, + request_options: typing.Optional[RequestOptions] = None + ) -> CreateUserResponse: + """ + Parameters + ---------- + username : str + + email : typing.Optional[str] + + age : typing.Optional[int] + + weight : typing.Optional[float] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CreateUserResponse + + Examples + -------- + from seed import SeedApi + + client = SeedApi( + base_url="https://yourhost.com/path/to/api", + ) + client.user.create_user( + username="string", + email="string", + age=1, + weight=1.1, + ) + """ + _response = self._client_wrapper.httpx_client.request( + "users", + method="POST", + json={"username": username, "email": email, "age": age, "weight": weight}, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + return typing.cast(CreateUserResponse, parse_obj_as(type_=CreateUserResponse, object_=_response.json())) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + + def get_user( + self, + *, + username: typing.Optional[str] = None, + age: typing.Optional[int] = None, + weight: typing.Optional[float] = None, + request_options: typing.Optional[RequestOptions] = None + ) -> User: + """ + Parameters + ---------- + username : typing.Optional[str] + + age : typing.Optional[int] + + weight : typing.Optional[float] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + User + + Examples + -------- + from seed import SeedApi + + client = SeedApi( + base_url="https://yourhost.com/path/to/api", + ) + client.user.get_user( + username="string", + age=1, + weight=1.1, + ) + """ + _response = self._client_wrapper.httpx_client.request( + "users", + method="GET", + params={"username": username, "age": jsonable_encoder(age), "weight": jsonable_encoder(weight)}, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return typing.cast(User, parse_obj_as(type_=User, object_=_response.json())) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + + +class AsyncUserClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def create_user( + self, + *, + username: str, + email: typing.Optional[str] = OMIT, + age: typing.Optional[int] = OMIT, + weight: typing.Optional[float] = OMIT, + request_options: typing.Optional[RequestOptions] = None + ) -> CreateUserResponse: + """ + Parameters + ---------- + username : str + + email : typing.Optional[str] + + age : typing.Optional[int] + + weight : typing.Optional[float] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + CreateUserResponse + + Examples + -------- + import asyncio + + from seed import AsyncSeedApi + + client = AsyncSeedApi( + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.user.create_user( + username="string", + email="string", + age=1, + weight=1.1, + ) + + + asyncio.run(main()) + """ + _response = await self._client_wrapper.httpx_client.request( + "users", + method="POST", + json={"username": username, "email": email, "age": age, "weight": weight}, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + return typing.cast(CreateUserResponse, parse_obj_as(type_=CreateUserResponse, object_=_response.json())) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + + async def get_user( + self, + *, + username: typing.Optional[str] = None, + age: typing.Optional[int] = None, + weight: typing.Optional[float] = None, + request_options: typing.Optional[RequestOptions] = None + ) -> User: + """ + Parameters + ---------- + username : typing.Optional[str] + + age : typing.Optional[int] + + weight : typing.Optional[float] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + User + + Examples + -------- + import asyncio + + from seed import AsyncSeedApi + + client = AsyncSeedApi( + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.user.get_user( + username="string", + age=1, + weight=1.1, + ) + + + asyncio.run(main()) + """ + _response = await self._client_wrapper.httpx_client.request( + "users", + method="GET", + params={"username": username, "age": jsonable_encoder(age), "weight": jsonable_encoder(weight)}, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + return typing.cast(User, parse_obj_as(type_=User, object_=_response.json())) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) diff --git a/seed/python-sdk/grpc/src/seed/user/types/__init__.py b/seed/python-sdk/grpc/src/seed/user/types/__init__.py new file mode 100644 index 00000000000..25fbdf08a23 --- /dev/null +++ b/seed/python-sdk/grpc/src/seed/user/types/__init__.py @@ -0,0 +1,8 @@ +# This file was auto-generated by Fern from our API Definition. + +from .create_user_response import CreateUserResponse +from .metadata import Metadata +from .metadata_value import MetadataValue +from .user import User + +__all__ = ["CreateUserResponse", "Metadata", "MetadataValue", "User"] diff --git a/seed/python-sdk/grpc/src/seed/user/types/create_user_response.py b/seed/python-sdk/grpc/src/seed/user/types/create_user_response.py new file mode 100644 index 00000000000..61d7c763e75 --- /dev/null +++ b/seed/python-sdk/grpc/src/seed/user/types/create_user_response.py @@ -0,0 +1,21 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic + +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .user import User + + +class CreateUserResponse(UniversalBaseModel): + user: User + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/seed/python-sdk/grpc/src/seed/user/types/metadata.py b/seed/python-sdk/grpc/src/seed/user/types/metadata.py new file mode 100644 index 00000000000..511c4984799 --- /dev/null +++ b/seed/python-sdk/grpc/src/seed/user/types/metadata.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from .metadata_value import MetadataValue + +Metadata = typing.Dict[str, typing.Optional[MetadataValue]] diff --git a/seed/python-sdk/grpc/src/seed/user/types/metadata_value.py b/seed/python-sdk/grpc/src/seed/user/types/metadata_value.py new file mode 100644 index 00000000000..28b9ba3e1b6 --- /dev/null +++ b/seed/python-sdk/grpc/src/seed/user/types/metadata_value.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +MetadataValue = typing.Union[float, str, bool, typing.List[MetadataValue]] diff --git a/seed/python-sdk/grpc/src/seed/user/types/user.py b/seed/python-sdk/grpc/src/seed/user/types/user.py new file mode 100644 index 00000000000..43a93702e0c --- /dev/null +++ b/seed/python-sdk/grpc/src/seed/user/types/user.py @@ -0,0 +1,26 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic + +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .metadata import Metadata + + +class User(UniversalBaseModel): + id: str + username: str + email: typing.Optional[str] = None + age: typing.Optional[int] = None + weight: typing.Optional[float] = None + metadata: typing.Optional[Metadata] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/seed/python-sdk/grpc/src/seed/version.py b/seed/python-sdk/grpc/src/seed/version.py new file mode 100644 index 00000000000..8b182af9687 --- /dev/null +++ b/seed/python-sdk/grpc/src/seed/version.py @@ -0,0 +1,4 @@ + +from importlib import metadata + +__version__ = metadata.version("fern_grpc") diff --git a/seed/python-sdk/grpc/tests/__init__.py b/seed/python-sdk/grpc/tests/__init__.py new file mode 100644 index 00000000000..f3ea2659bb1 --- /dev/null +++ b/seed/python-sdk/grpc/tests/__init__.py @@ -0,0 +1,2 @@ +# This file was auto-generated by Fern from our API Definition. + diff --git a/seed/python-sdk/grpc/tests/conftest.py b/seed/python-sdk/grpc/tests/conftest.py new file mode 100644 index 00000000000..c39a48a47ef --- /dev/null +++ b/seed/python-sdk/grpc/tests/conftest.py @@ -0,0 +1,16 @@ +# This file was auto-generated by Fern from our API Definition. + +import os + +import pytest +from seed import AsyncSeedApi, SeedApi + + +@pytest.fixture +def client() -> SeedApi: + return SeedApi(base_url=os.getenv("TESTS_BASE_URL", "base_url")) + + +@pytest.fixture +def async_client() -> AsyncSeedApi: + return AsyncSeedApi(base_url=os.getenv("TESTS_BASE_URL", "base_url")) diff --git a/seed/python-sdk/grpc/tests/custom/test_client.py b/seed/python-sdk/grpc/tests/custom/test_client.py new file mode 100644 index 00000000000..60a58e64c27 --- /dev/null +++ b/seed/python-sdk/grpc/tests/custom/test_client.py @@ -0,0 +1,6 @@ +import pytest + +# Get started with writing tests with pytest at https://docs.pytest.org +@pytest.mark.skip(reason="Unimplemented") +def test_client() -> None: + assert True == True diff --git a/seed/python-sdk/grpc/tests/test_user.py b/seed/python-sdk/grpc/tests/test_user.py new file mode 100644 index 00000000000..7347989535a --- /dev/null +++ b/seed/python-sdk/grpc/tests/test_user.py @@ -0,0 +1,59 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from seed import AsyncSeedApi, SeedApi + +from .utilities import validate_response + + +async def test_create_user(client: SeedApi, async_client: AsyncSeedApi) -> None: + expected_response: typing.Any = { + "user": { + "id": "string", + "username": "string", + "email": "string", + "age": 1, + "weight": 1.1, + "metadata": {"string": {"key": "value"}}, + } + } + expected_types: typing.Any = { + "user": { + "id": None, + "username": None, + "email": None, + "age": None, + "weight": None, + "metadata": ("dict", {0: (None, None)}), + } + } + response = client.user.create_user(username="string", email="string", age=1, weight=1.1) + validate_response(response, expected_response, expected_types) + + async_response = await async_client.user.create_user(username="string", email="string", age=1, weight=1.1) + validate_response(async_response, expected_response, expected_types) + + +async def test_get_user(client: SeedApi, async_client: AsyncSeedApi) -> None: + expected_response: typing.Any = { + "id": "string", + "username": "string", + "email": "string", + "age": 1, + "weight": 1.1, + "metadata": {"string": {"key": "value"}}, + } + expected_types: typing.Any = { + "id": None, + "username": None, + "email": None, + "age": None, + "weight": None, + "metadata": ("dict", {0: (None, None)}), + } + response = client.user.get_user(username="string", age=1, weight=1.1) + validate_response(response, expected_response, expected_types) + + async_response = await async_client.user.get_user(username="string", age=1, weight=1.1) + validate_response(async_response, expected_response, expected_types) diff --git a/seed/python-sdk/grpc/tests/utilities.py b/seed/python-sdk/grpc/tests/utilities.py new file mode 100644 index 00000000000..13da208eb3a --- /dev/null +++ b/seed/python-sdk/grpc/tests/utilities.py @@ -0,0 +1,138 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +import uuid + +import pydantic +from dateutil import parser + + +def cast_field(json_expectation: typing.Any, type_expectation: typing.Any) -> typing.Any: + # Cast these specific types which come through as string and expect our + # models to cast to the correct type. + if type_expectation == "uuid": + return uuid.UUID(json_expectation) + elif type_expectation == "date": + return parser.parse(json_expectation).date() + elif type_expectation == "datetime": + return parser.parse(json_expectation) + elif type_expectation == "set": + return set(json_expectation) + elif type_expectation == "integer": + # Necessary as we allow numeric keys, but JSON makes them strings + return int(json_expectation) + + return json_expectation + + +def validate_field(response: typing.Any, json_expectation: typing.Any, type_expectation: typing.Any) -> None: + # Allow for an escape hatch if the object cannot be validated + if type_expectation == "no_validate": + return + + is_container_of_complex_type = False + # Parse types in containers, note that dicts are handled within `validate_response` + if isinstance(json_expectation, list): + if isinstance(type_expectation, tuple): + container_expectation = type_expectation[0] + contents_expectation = type_expectation[1] + + cast_json_expectation = [] + for idx, ex in enumerate(json_expectation): + if isinstance(contents_expectation, dict): + entry_expectation = contents_expectation.get(idx) + if isinstance(entry_expectation, dict): + is_container_of_complex_type = True + validate_response( + response=response[idx], json_expectation=ex, type_expectations=entry_expectation + ) + else: + cast_json_expectation.append(cast_field(ex, entry_expectation)) + else: + cast_json_expectation.append(ex) + json_expectation = cast_json_expectation + + # Note that we explicitly do not allow for sets of pydantic models as they are not hashable, so + # if any of the values of the set have a type_expectation of a dict, we're assuming it's a pydantic + # model and keeping it a list. + if container_expectation != "set" or not any( + map(lambda value: isinstance(value, dict), list(contents_expectation.values())) + ): + json_expectation = cast_field(json_expectation, container_expectation) + elif isinstance(type_expectation, tuple): + container_expectation = type_expectation[0] + contents_expectation = type_expectation[1] + if isinstance(contents_expectation, dict): + json_expectation = { + cast_field( + key, contents_expectation.get(idx)[0] if contents_expectation.get(idx) is not None else None # type: ignore + ): cast_field( + value, contents_expectation.get(idx)[1] if contents_expectation.get(idx) is not None else None # type: ignore + ) + for idx, (key, value) in enumerate(json_expectation.items()) + } + else: + json_expectation = cast_field(json_expectation, container_expectation) + elif type_expectation is not None: + json_expectation = cast_field(json_expectation, type_expectation) + + # When dealing with containers of models, etc. we're validating them implicitly, so no need to check the resultant list + if not is_container_of_complex_type: + assert ( + json_expectation == response + ), "Primitives found, expected: {0} (type: {1}), Actual: {2} (type: {3})".format( + json_expectation, type(json_expectation), response, type(response) + ) + + +# Arg type_expectations is a deeply nested structure that matches the response, but with the values replaced with the expected types +def validate_response(response: typing.Any, json_expectation: typing.Any, type_expectations: typing.Any) -> None: + # Allow for an escape hatch if the object cannot be validated + if type_expectations == "no_validate": + return + + if ( + not isinstance(response, list) + and not isinstance(response, dict) + and not issubclass(type(response), pydantic.BaseModel) + ): + validate_field(response=response, json_expectation=json_expectation, type_expectation=type_expectations) + return + + if isinstance(response, list): + assert len(response) == len(json_expectation), "Length mismatch, expected: {0}, Actual: {1}".format( + len(response), len(json_expectation) + ) + content_expectation = type_expectations + if isinstance(type_expectations, tuple): + content_expectation = type_expectations[1] + for idx, item in enumerate(response): + validate_response( + response=item, json_expectation=json_expectation[idx], type_expectations=content_expectation[idx] + ) + else: + response_json = response + if issubclass(type(response), pydantic.BaseModel): + response_json = response.dict(by_alias=True) + + for key, value in json_expectation.items(): + assert key in response_json, "Field {0} not found within the response object: {1}".format( + key, response_json + ) + + type_expectation = None + if type_expectations is not None and isinstance(type_expectations, dict): + type_expectation = type_expectations.get(key) + + # If your type_expectation is a tuple then you have a container field, process it as such + # Otherwise, we're just validating a single field that's a pydantic model. + if isinstance(value, dict) and not isinstance(type_expectation, tuple): + validate_response( + response=response_json[key], json_expectation=value, type_expectations=type_expectation + ) + else: + validate_field(response=response_json[key], json_expectation=value, type_expectation=type_expectation) + + # Ensure there are no additional fields here either + del response_json[key] + assert len(response_json) == 0, "Additional fields found, expected None: {0}".format(response_json) diff --git a/seed/python-sdk/grpc/tests/utils/__init__.py b/seed/python-sdk/grpc/tests/utils/__init__.py new file mode 100644 index 00000000000..f3ea2659bb1 --- /dev/null +++ b/seed/python-sdk/grpc/tests/utils/__init__.py @@ -0,0 +1,2 @@ +# This file was auto-generated by Fern from our API Definition. + diff --git a/seed/python-sdk/grpc/tests/utils/assets/models/__init__.py b/seed/python-sdk/grpc/tests/utils/assets/models/__init__.py new file mode 100644 index 00000000000..2cf01263529 --- /dev/null +++ b/seed/python-sdk/grpc/tests/utils/assets/models/__init__.py @@ -0,0 +1,21 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +from .circle import CircleParams +from .object_with_defaults import ObjectWithDefaultsParams +from .object_with_optional_field import ObjectWithOptionalFieldParams +from .shape import Shape_CircleParams, Shape_SquareParams, ShapeParams +from .square import SquareParams +from .undiscriminated_shape import UndiscriminatedShapeParams + +__all__ = [ + "CircleParams", + "ObjectWithDefaultsParams", + "ObjectWithOptionalFieldParams", + "ShapeParams", + "Shape_CircleParams", + "Shape_SquareParams", + "SquareParams", + "UndiscriminatedShapeParams", +] diff --git a/seed/python-sdk/grpc/tests/utils/assets/models/circle.py b/seed/python-sdk/grpc/tests/utils/assets/models/circle.py new file mode 100644 index 00000000000..af7a1bf8a8e --- /dev/null +++ b/seed/python-sdk/grpc/tests/utils/assets/models/circle.py @@ -0,0 +1,10 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +import typing_extensions +from seed.core.serialization import FieldMetadata + + +class CircleParams(typing_extensions.TypedDict): + radius_measurement: typing_extensions.Annotated[float, FieldMetadata(alias="radiusMeasurement")] diff --git a/seed/python-sdk/grpc/tests/utils/assets/models/color.py b/seed/python-sdk/grpc/tests/utils/assets/models/color.py new file mode 100644 index 00000000000..2aa2c4c52f0 --- /dev/null +++ b/seed/python-sdk/grpc/tests/utils/assets/models/color.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +import typing + +Color = typing.Union[typing.Literal["red", "blue"], typing.Any] diff --git a/seed/python-sdk/grpc/tests/utils/assets/models/object_with_defaults.py b/seed/python-sdk/grpc/tests/utils/assets/models/object_with_defaults.py new file mode 100644 index 00000000000..a977b1d2aa1 --- /dev/null +++ b/seed/python-sdk/grpc/tests/utils/assets/models/object_with_defaults.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +import typing_extensions + + +class ObjectWithDefaultsParams(typing_extensions.TypedDict): + """ + Defines properties with default values and validation rules. + """ + + decimal: typing_extensions.NotRequired[float] + string: typing_extensions.NotRequired[str] + required_string: str diff --git a/seed/python-sdk/grpc/tests/utils/assets/models/object_with_optional_field.py b/seed/python-sdk/grpc/tests/utils/assets/models/object_with_optional_field.py new file mode 100644 index 00000000000..3ad93d5f305 --- /dev/null +++ b/seed/python-sdk/grpc/tests/utils/assets/models/object_with_optional_field.py @@ -0,0 +1,35 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing +import uuid + +import typing_extensions +from seed.core.serialization import FieldMetadata + +from .color import Color +from .shape import ShapeParams +from .undiscriminated_shape import UndiscriminatedShapeParams + + +class ObjectWithOptionalFieldParams(typing_extensions.TypedDict): + literal: typing.Literal["lit_one"] + string: typing_extensions.NotRequired[str] + integer: typing_extensions.NotRequired[int] + long_: typing_extensions.NotRequired[typing_extensions.Annotated[int, FieldMetadata(alias="long")]] + double: typing_extensions.NotRequired[float] + bool_: typing_extensions.NotRequired[typing_extensions.Annotated[bool, FieldMetadata(alias="bool")]] + datetime: typing_extensions.NotRequired[dt.datetime] + date: typing_extensions.NotRequired[dt.date] + uuid_: typing_extensions.NotRequired[typing_extensions.Annotated[uuid.UUID, FieldMetadata(alias="uuid")]] + base_64: typing_extensions.NotRequired[typing_extensions.Annotated[str, FieldMetadata(alias="base64")]] + list_: typing_extensions.NotRequired[typing_extensions.Annotated[typing.Sequence[str], FieldMetadata(alias="list")]] + set_: typing_extensions.NotRequired[typing_extensions.Annotated[typing.Set[str], FieldMetadata(alias="set")]] + map_: typing_extensions.NotRequired[typing_extensions.Annotated[typing.Dict[int, str], FieldMetadata(alias="map")]] + enum: typing_extensions.NotRequired[Color] + union: typing_extensions.NotRequired[ShapeParams] + second_union: typing_extensions.NotRequired[ShapeParams] + undiscriminated_union: typing_extensions.NotRequired[UndiscriminatedShapeParams] + any: typing.Any diff --git a/seed/python-sdk/grpc/tests/utils/assets/models/shape.py b/seed/python-sdk/grpc/tests/utils/assets/models/shape.py new file mode 100644 index 00000000000..2c33c877951 --- /dev/null +++ b/seed/python-sdk/grpc/tests/utils/assets/models/shape.py @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +import typing_extensions +from seed.core.serialization import FieldMetadata + + +class Base(typing_extensions.TypedDict): + id: str + + +class Shape_CircleParams(Base): + shape_type: typing_extensions.Annotated[typing.Literal["circle"], FieldMetadata(alias="shapeType")] + radius_measurement: typing_extensions.Annotated[float, FieldMetadata(alias="radiusMeasurement")] + + +class Shape_SquareParams(Base): + shape_type: typing_extensions.Annotated[typing.Literal["square"], FieldMetadata(alias="shapeType")] + length_measurement: typing_extensions.Annotated[float, FieldMetadata(alias="lengthMeasurement")] + + +ShapeParams = typing.Union[Shape_CircleParams, Shape_SquareParams] diff --git a/seed/python-sdk/grpc/tests/utils/assets/models/square.py b/seed/python-sdk/grpc/tests/utils/assets/models/square.py new file mode 100644 index 00000000000..b9b7dd319bc --- /dev/null +++ b/seed/python-sdk/grpc/tests/utils/assets/models/square.py @@ -0,0 +1,10 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +import typing_extensions +from seed.core.serialization import FieldMetadata + + +class SquareParams(typing_extensions.TypedDict): + length_measurement: typing_extensions.Annotated[float, FieldMetadata(alias="lengthMeasurement")] diff --git a/seed/python-sdk/grpc/tests/utils/assets/models/undiscriminated_shape.py b/seed/python-sdk/grpc/tests/utils/assets/models/undiscriminated_shape.py new file mode 100644 index 00000000000..99f12b300d1 --- /dev/null +++ b/seed/python-sdk/grpc/tests/utils/assets/models/undiscriminated_shape.py @@ -0,0 +1,10 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +import typing + +from .circle import CircleParams +from .square import SquareParams + +UndiscriminatedShapeParams = typing.Union[CircleParams, SquareParams] diff --git a/seed/python-sdk/grpc/tests/utils/test_http_client.py b/seed/python-sdk/grpc/tests/utils/test_http_client.py new file mode 100644 index 00000000000..a541bae6531 --- /dev/null +++ b/seed/python-sdk/grpc/tests/utils/test_http_client.py @@ -0,0 +1,61 @@ +# This file was auto-generated by Fern from our API Definition. + +from seed.core.http_client import get_request_body +from seed.core.request_options import RequestOptions + + +def get_request_options() -> RequestOptions: + return {"additional_body_parameters": {"see you": "later"}} + + +def test_get_json_request_body() -> None: + json_body, data_body = get_request_body(json={"hello": "world"}, data=None, request_options=None, omit=None) + assert json_body == {"hello": "world"} + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={"goodbye": "world"}, data=None, request_options=get_request_options(), omit=None + ) + + assert json_body_extras == {"goodbye": "world", "see you": "later"} + assert data_body_extras is None + + +def test_get_files_request_body() -> None: + json_body, data_body = get_request_body(json=None, data={"hello": "world"}, request_options=None, omit=None) + assert data_body == {"hello": "world"} + assert json_body is None + + json_body_extras, data_body_extras = get_request_body( + json=None, data={"goodbye": "world"}, request_options=get_request_options(), omit=None + ) + + assert data_body_extras == {"goodbye": "world", "see you": "later"} + assert json_body_extras is None + + +def test_get_none_request_body() -> None: + json_body, data_body = get_request_body(json=None, data=None, request_options=None, omit=None) + assert data_body is None + assert json_body is None + + json_body_extras, data_body_extras = get_request_body( + json=None, data=None, request_options=get_request_options(), omit=None + ) + + assert json_body_extras == {"see you": "later"} + assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/grpc/tests/utils/test_query_encoding.py b/seed/python-sdk/grpc/tests/utils/test_query_encoding.py new file mode 100644 index 00000000000..247e1551b46 --- /dev/null +++ b/seed/python-sdk/grpc/tests/utils/test_query_encoding.py @@ -0,0 +1,18 @@ +# This file was auto-generated by Fern from our API Definition. + +from seed.core.query_encoder import encode_query + + +def test_query_encoding() -> None: + assert encode_query({"hello world": "hello world"}) == {"hello world": "hello world"} + assert encode_query({"hello_world": {"hello": "world"}}) == {"hello_world[hello]": "world"} + assert encode_query({"hello_world": {"hello": {"world": "today"}, "test": "this"}, "hi": "there"}) == { + "hello_world[hello][world]": "today", + "hello_world[test]": "this", + "hi": "there", + } + + +def test_encode_query_with_none() -> None: + encoded = encode_query(None) + assert encoded == None diff --git a/seed/python-sdk/grpc/tests/utils/test_serialization.py b/seed/python-sdk/grpc/tests/utils/test_serialization.py new file mode 100644 index 00000000000..58b1ed66e6d --- /dev/null +++ b/seed/python-sdk/grpc/tests/utils/test_serialization.py @@ -0,0 +1,66 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import Any, List + +from seed.core.serialization import convert_and_respect_annotation_metadata + +from .assets.models import ObjectWithOptionalFieldParams, ShapeParams + +UNION_TEST: ShapeParams = {"radius_measurement": 1.0, "shape_type": "circle", "id": "1"} +UNION_TEST_CONVERTED = {"shapeType": "circle", "radiusMeasurement": 1.0, "id": "1"} + + +def test_convert_and_respect_annotation_metadata() -> None: + data: ObjectWithOptionalFieldParams = { + "string": "string", + "long_": 12345, + "bool_": True, + "literal": "lit_one", + "any": "any", + } + converted = convert_and_respect_annotation_metadata(object_=data, annotation=ObjectWithOptionalFieldParams) + assert converted == {"string": "string", "long": 12345, "bool": True, "literal": "lit_one", "any": "any"} + + +def test_convert_and_respect_annotation_metadata_in_list() -> None: + data: List[ObjectWithOptionalFieldParams] = [ + {"string": "string", "long_": 12345, "bool_": True, "literal": "lit_one", "any": "any"}, + {"string": "another string", "long_": 67890, "list_": [], "literal": "lit_one", "any": "any"}, + ] + converted = convert_and_respect_annotation_metadata(object_=data, annotation=List[ObjectWithOptionalFieldParams]) + + assert converted == [ + {"string": "string", "long": 12345, "bool": True, "literal": "lit_one", "any": "any"}, + {"string": "another string", "long": 67890, "list": [], "literal": "lit_one", "any": "any"}, + ] + + +def test_convert_and_respect_annotation_metadata_in_nested_object() -> None: + data: ObjectWithOptionalFieldParams = { + "string": "string", + "long_": 12345, + "union": UNION_TEST, + "literal": "lit_one", + "any": "any", + } + converted = convert_and_respect_annotation_metadata(object_=data, annotation=ObjectWithOptionalFieldParams) + + assert converted == { + "string": "string", + "long": 12345, + "union": UNION_TEST_CONVERTED, + "literal": "lit_one", + "any": "any", + } + + +def test_convert_and_respect_annotation_metadata_in_union() -> None: + converted = convert_and_respect_annotation_metadata(object_=UNION_TEST, annotation=ShapeParams) + + assert converted == UNION_TEST_CONVERTED + + +def test_convert_and_respect_annotation_metadata_with_empty_object() -> None: + data: Any = {} + converted = convert_and_respect_annotation_metadata(object_=data, annotation=ShapeParams) + assert converted == data diff --git a/seed/python-sdk/idempotency-headers/src/seed/core/http_client.py b/seed/python-sdk/idempotency-headers/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/idempotency-headers/src/seed/core/http_client.py +++ b/seed/python-sdk/idempotency-headers/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/idempotency-headers/tests/utils/test_http_client.py b/seed/python-sdk/idempotency-headers/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/idempotency-headers/tests/utils/test_http_client.py +++ b/seed/python-sdk/idempotency-headers/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/imdb/src/seed/core/http_client.py b/seed/python-sdk/imdb/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/imdb/src/seed/core/http_client.py +++ b/seed/python-sdk/imdb/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/imdb/tests/utils/test_http_client.py b/seed/python-sdk/imdb/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/imdb/tests/utils/test_http_client.py +++ b/seed/python-sdk/imdb/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/literal/no-custom-config/src/seed/core/http_client.py b/seed/python-sdk/literal/no-custom-config/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/literal/no-custom-config/src/seed/core/http_client.py +++ b/seed/python-sdk/literal/no-custom-config/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/literal/no-custom-config/tests/utils/test_http_client.py b/seed/python-sdk/literal/no-custom-config/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/literal/no-custom-config/tests/utils/test_http_client.py +++ b/seed/python-sdk/literal/no-custom-config/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/literal/use_typeddict_requests/src/seed/core/http_client.py b/seed/python-sdk/literal/use_typeddict_requests/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/literal/use_typeddict_requests/src/seed/core/http_client.py +++ b/seed/python-sdk/literal/use_typeddict_requests/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/literal/use_typeddict_requests/tests/utils/test_http_client.py b/seed/python-sdk/literal/use_typeddict_requests/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/literal/use_typeddict_requests/tests/utils/test_http_client.py +++ b/seed/python-sdk/literal/use_typeddict_requests/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/mixed-case/src/seed/core/http_client.py b/seed/python-sdk/mixed-case/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/mixed-case/src/seed/core/http_client.py +++ b/seed/python-sdk/mixed-case/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/mixed-case/tests/utils/test_http_client.py b/seed/python-sdk/mixed-case/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/mixed-case/tests/utils/test_http_client.py +++ b/seed/python-sdk/mixed-case/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/multi-line-docs/src/seed/core/http_client.py b/seed/python-sdk/multi-line-docs/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/multi-line-docs/src/seed/core/http_client.py +++ b/seed/python-sdk/multi-line-docs/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/multi-line-docs/tests/utils/test_http_client.py b/seed/python-sdk/multi-line-docs/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/multi-line-docs/tests/utils/test_http_client.py +++ b/seed/python-sdk/multi-line-docs/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/multi-url-environment-no-default/src/seed/core/http_client.py b/seed/python-sdk/multi-url-environment-no-default/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/multi-url-environment-no-default/src/seed/core/http_client.py +++ b/seed/python-sdk/multi-url-environment-no-default/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/multi-url-environment-no-default/tests/utils/test_http_client.py b/seed/python-sdk/multi-url-environment-no-default/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/multi-url-environment-no-default/tests/utils/test_http_client.py +++ b/seed/python-sdk/multi-url-environment-no-default/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/multi-url-environment/src/seed/core/http_client.py b/seed/python-sdk/multi-url-environment/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/multi-url-environment/src/seed/core/http_client.py +++ b/seed/python-sdk/multi-url-environment/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/multi-url-environment/tests/utils/test_http_client.py b/seed/python-sdk/multi-url-environment/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/multi-url-environment/tests/utils/test_http_client.py +++ b/seed/python-sdk/multi-url-environment/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/no-environment/src/seed/core/http_client.py b/seed/python-sdk/no-environment/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/no-environment/src/seed/core/http_client.py +++ b/seed/python-sdk/no-environment/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/no-environment/tests/utils/test_http_client.py b/seed/python-sdk/no-environment/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/no-environment/tests/utils/test_http_client.py +++ b/seed/python-sdk/no-environment/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/oauth-client-credentials-default/src/seed/core/http_client.py b/seed/python-sdk/oauth-client-credentials-default/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/oauth-client-credentials-default/src/seed/core/http_client.py +++ b/seed/python-sdk/oauth-client-credentials-default/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/oauth-client-credentials-default/tests/utils/test_http_client.py b/seed/python-sdk/oauth-client-credentials-default/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/oauth-client-credentials-default/tests/utils/test_http_client.py +++ b/seed/python-sdk/oauth-client-credentials-default/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/oauth-client-credentials-environment-variables/src/seed/core/http_client.py b/seed/python-sdk/oauth-client-credentials-environment-variables/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/oauth-client-credentials-environment-variables/src/seed/core/http_client.py +++ b/seed/python-sdk/oauth-client-credentials-environment-variables/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/oauth-client-credentials-environment-variables/tests/utils/test_http_client.py b/seed/python-sdk/oauth-client-credentials-environment-variables/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/oauth-client-credentials-environment-variables/tests/utils/test_http_client.py +++ b/seed/python-sdk/oauth-client-credentials-environment-variables/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/oauth-client-credentials-nested-root/src/seed/core/http_client.py b/seed/python-sdk/oauth-client-credentials-nested-root/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/oauth-client-credentials-nested-root/src/seed/core/http_client.py +++ b/seed/python-sdk/oauth-client-credentials-nested-root/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/oauth-client-credentials-nested-root/tests/utils/test_http_client.py b/seed/python-sdk/oauth-client-credentials-nested-root/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/oauth-client-credentials-nested-root/tests/utils/test_http_client.py +++ b/seed/python-sdk/oauth-client-credentials-nested-root/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/oauth-client-credentials/src/seed/core/http_client.py b/seed/python-sdk/oauth-client-credentials/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/oauth-client-credentials/src/seed/core/http_client.py +++ b/seed/python-sdk/oauth-client-credentials/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/oauth-client-credentials/tests/utils/test_http_client.py b/seed/python-sdk/oauth-client-credentials/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/oauth-client-credentials/tests/utils/test_http_client.py +++ b/seed/python-sdk/oauth-client-credentials/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/object/src/seed/core/http_client.py b/seed/python-sdk/object/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/object/src/seed/core/http_client.py +++ b/seed/python-sdk/object/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/object/tests/utils/test_http_client.py b/seed/python-sdk/object/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/object/tests/utils/test_http_client.py +++ b/seed/python-sdk/object/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/objects-with-imports/src/seed/core/http_client.py b/seed/python-sdk/objects-with-imports/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/objects-with-imports/src/seed/core/http_client.py +++ b/seed/python-sdk/objects-with-imports/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/objects-with-imports/tests/utils/test_http_client.py b/seed/python-sdk/objects-with-imports/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/objects-with-imports/tests/utils/test_http_client.py +++ b/seed/python-sdk/objects-with-imports/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/optional/src/seed/core/http_client.py b/seed/python-sdk/optional/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/optional/src/seed/core/http_client.py +++ b/seed/python-sdk/optional/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/optional/tests/utils/test_http_client.py b/seed/python-sdk/optional/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/optional/tests/utils/test_http_client.py +++ b/seed/python-sdk/optional/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/package-yml/src/seed/core/http_client.py b/seed/python-sdk/package-yml/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/package-yml/src/seed/core/http_client.py +++ b/seed/python-sdk/package-yml/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/package-yml/tests/utils/test_http_client.py b/seed/python-sdk/package-yml/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/package-yml/tests/utils/test_http_client.py +++ b/seed/python-sdk/package-yml/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/pagination/src/seed/core/http_client.py b/seed/python-sdk/pagination/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/pagination/src/seed/core/http_client.py +++ b/seed/python-sdk/pagination/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/pagination/tests/utils/test_http_client.py b/seed/python-sdk/pagination/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/pagination/tests/utils/test_http_client.py +++ b/seed/python-sdk/pagination/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/plain-text/src/seed/core/http_client.py b/seed/python-sdk/plain-text/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/plain-text/src/seed/core/http_client.py +++ b/seed/python-sdk/plain-text/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/plain-text/tests/utils/test_http_client.py b/seed/python-sdk/plain-text/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/plain-text/tests/utils/test_http_client.py +++ b/seed/python-sdk/plain-text/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/query-parameters/src/seed/core/http_client.py b/seed/python-sdk/query-parameters/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/query-parameters/src/seed/core/http_client.py +++ b/seed/python-sdk/query-parameters/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/query-parameters/tests/utils/test_http_client.py b/seed/python-sdk/query-parameters/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/query-parameters/tests/utils/test_http_client.py +++ b/seed/python-sdk/query-parameters/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/reserved-keywords/src/seed/core/http_client.py b/seed/python-sdk/reserved-keywords/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/reserved-keywords/src/seed/core/http_client.py +++ b/seed/python-sdk/reserved-keywords/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/reserved-keywords/tests/utils/test_http_client.py b/seed/python-sdk/reserved-keywords/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/reserved-keywords/tests/utils/test_http_client.py +++ b/seed/python-sdk/reserved-keywords/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/response-property/.github/workflows/ci.yml b/seed/python-sdk/response-property/.github/workflows/ci.yml new file mode 100644 index 00000000000..b7316b8cab7 --- /dev/null +++ b/seed/python-sdk/response-property/.github/workflows/ci.yml @@ -0,0 +1,63 @@ +name: ci + +on: [push] +jobs: + compile: + runs-on: ubuntu-20.04 + steps: + - name: Checkout repo + uses: actions/checkout@v3 + - name: Set up python + uses: actions/setup-python@v4 + with: + python-version: 3.8 + - name: Bootstrap poetry + run: | + curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 + - name: Install dependencies + run: poetry install + - name: Compile + run: poetry run mypy . + test: + runs-on: ubuntu-20.04 + steps: + - name: Checkout repo + uses: actions/checkout@v3 + - name: Set up python + uses: actions/setup-python@v4 + with: + python-version: 3.8 + - name: Bootstrap poetry + run: | + curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 + - name: Install dependencies + run: poetry install + + - name: Install Fern + run: npm install -g fern-api + - name: Test + run: fern test --command "poetry run pytest -rP ." + + publish: + needs: [compile, test] + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + runs-on: ubuntu-20.04 + steps: + - name: Checkout repo + uses: actions/checkout@v3 + - name: Set up python + uses: actions/setup-python@v4 + with: + python-version: 3.8 + - name: Bootstrap poetry + run: | + curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 + - name: Install dependencies + run: poetry install + - name: Publish to pypi + run: | + poetry config repositories.remote + poetry --no-interaction -v publish --build --repository remote --username "$" --password "$" + env: + : ${{ secrets. }} + : ${{ secrets. }} diff --git a/seed/python-sdk/response-property/.gitignore b/seed/python-sdk/response-property/.gitignore new file mode 100644 index 00000000000..42cb863501e --- /dev/null +++ b/seed/python-sdk/response-property/.gitignore @@ -0,0 +1,4 @@ +dist/ +.mypy_cache/ +__pycache__/ +poetry.toml diff --git a/seed/python-sdk/response-property/.mock/definition/__package__.yml b/seed/python-sdk/response-property/.mock/definition/__package__.yml new file mode 100644 index 00000000000..708a5a1139d --- /dev/null +++ b/seed/python-sdk/response-property/.mock/definition/__package__.yml @@ -0,0 +1,10 @@ +types: + StringResponse: + properties: + data: string + + OptionalStringResponse: optional + + WithMetadata: + properties: + metadata: map diff --git a/seed/python-sdk/response-property/.mock/definition/api.yml b/seed/python-sdk/response-property/.mock/definition/api.yml new file mode 100644 index 00000000000..4a1bb4a3045 --- /dev/null +++ b/seed/python-sdk/response-property/.mock/definition/api.yml @@ -0,0 +1 @@ +name: response-property diff --git a/seed/python-sdk/response-property/.mock/definition/service.yml b/seed/python-sdk/response-property/.mock/definition/service.yml new file mode 100644 index 00000000000..15de6ab767f --- /dev/null +++ b/seed/python-sdk/response-property/.mock/definition/service.yml @@ -0,0 +1,81 @@ +imports: + root: __package__.yml + +types: + WithDocs: + properties: + docs: string + + OptionalWithDocs: optional + + Movie: + properties: + id: string + name: string + + Response: + extends: + - root.WithMetadata + - WithDocs + properties: + data: Movie + +service: + auth: false + base-path: "" + endpoints: + getMovie: + method: POST + path: /movie + request: string + response: + type: Response + property: data + + getMovieDocs: + method: POST + path: /movie + request: string + response: + type: Response + property: docs + + getMovieName: + method: POST + path: /movie + request: string + response: + type: root.StringResponse + property: data + + getMovieMetadata: + method: POST + path: /movie + request: string + response: + type: Response + property: metadata + + getOptionalMovie: + method: POST + path: /movie + request: string + response: + type: optional + property: data + + getOptionalMovieDocs: + method: POST + path: /movie + request: string + response: + type: OptionalWithDocs + property: docs + + getOptionalMovieName: + method: POST + path: /movie + request: string + response: + type: root.OptionalStringResponse + property: data diff --git a/seed/python-sdk/response-property/.mock/fern.config.json b/seed/python-sdk/response-property/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/python-sdk/response-property/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/python-sdk/response-property/.mock/generators.yml b/seed/python-sdk/response-property/.mock/generators.yml new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/seed/python-sdk/response-property/.mock/generators.yml @@ -0,0 +1 @@ +{} diff --git a/seed/python-sdk/response-property/README.md b/seed/python-sdk/response-property/README.md new file mode 100644 index 00000000000..7b0623c3ad8 --- /dev/null +++ b/seed/python-sdk/response-property/README.md @@ -0,0 +1,134 @@ +# Seed Python Library + +[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-SDK%20generated%20by%20Fern-brightgreen)](https://github.com/fern-api/fern) +[![pypi](https://img.shields.io/pypi/v/fern_response-property)](https://pypi.python.org/pypi/fern_response-property) + +The Seed Python library provides convenient access to the Seed API from Python. + +## Installation + +```sh +pip install fern_response-property +``` + +## Usage + +Instantiate and use the client with the following: + +```python +from seed import SeedResponseProperty + +client = SeedResponseProperty( + base_url="https://yourhost.com/path/to/api", +) +client.service.get_movie( + request="string", +) +``` + +## Async Client + +The SDK also exports an `async` client so that you can make non-blocking calls to our API. + +```python +import asyncio + +from seed import AsyncSeedResponseProperty + +client = AsyncSeedResponseProperty( + base_url="https://yourhost.com/path/to/api", +) + + +async def main() -> None: + await client.service.get_movie( + request="string", + ) + + +asyncio.run(main()) +``` + +## Exception Handling + +When the API returns a non-success status code (4xx or 5xx response), a subclass of the following error +will be thrown. + +```python +from seed.core.api_error import ApiError + +try: + client.service.get_movie(...) +except ApiError as e: + print(e.status_code) + print(e.body) +``` + +## Advanced + +### Retries + +The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long +as the request is deemed retriable and the number of retry attempts has not grown larger than the configured +retry limit (default: 2). + +A request is deemed retriable when any of the following HTTP status codes is returned: + +- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) +- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) +- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) + +Use the `max_retries` request option to configure this behavior. + +```python +client.service.get_movie(..., { + "max_retries": 1 +}) +``` + +### Timeouts + +The SDK defaults to a 60 second timeout. You can configure this with a timeout option at the client or request level. + +```python + +from seed import SeedResponseProperty + +client = SeedResponseProperty( + ..., + timeout=20.0, +) + + +# Override timeout for a specific method +client.service.get_movie(..., { + "timeout_in_seconds": 1 +}) +``` + +### Custom Client + +You can override the `httpx` client to customize it for your use-case. Some common use-cases include support for proxies +and transports. +```python +import httpx +from seed import SeedResponseProperty + +client = SeedResponseProperty( + ..., + httpx_client=httpx.Client( + proxies="http://my.test.proxy.example.com", + transport=httpx.HTTPTransport(local_address="0.0.0.0"), + ), +) +``` + +## Contributing + +While we value open-source contributions to this SDK, this library is generated programmatically. +Additions made directly to this library would have to be moved over to our generation code, +otherwise they would be overwritten upon the next generated release. Feel free to open a PR as +a proof of concept, but know that we will not be able to merge it as-is. We suggest opening +an issue first to discuss with us! + +On the other hand, contributions to the README are always very welcome! diff --git a/seed/python-sdk/response-property/pyproject.toml b/seed/python-sdk/response-property/pyproject.toml new file mode 100644 index 00000000000..135de21d070 --- /dev/null +++ b/seed/python-sdk/response-property/pyproject.toml @@ -0,0 +1,57 @@ +[tool.poetry] +name = "fern_response-property" +version = "0.0.1" +description = "" +readme = "README.md" +authors = [] +keywords = [] + +classifiers = [ + "Intended Audience :: Developers", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Operating System :: OS Independent", + "Operating System :: POSIX", + "Operating System :: MacOS", + "Operating System :: POSIX :: Linux", + "Operating System :: Microsoft :: Windows", + "Topic :: Software Development :: Libraries :: Python Modules", + "Typing :: Typed" +] +packages = [ + { include = "seed", from = "src"} +] + +[project.urls] +Repository = 'https://github.com/response-property/fern' + +[tool.poetry.dependencies] +python = "^3.8" +httpx = ">=0.21.2" +pydantic = ">= 1.9.2" +pydantic-core = "^2.18.2" +typing_extensions = ">= 4.0.0" + +[tool.poetry.dev-dependencies] +mypy = "1.0.1" +pytest = "^7.4.0" +pytest-asyncio = "^0.23.5" +python-dateutil = "^2.9.0" +types-python-dateutil = "^2.9.0.20240316" + +[tool.pytest.ini_options] +testpaths = [ "tests" ] +asyncio_mode = "auto" + +[tool.mypy] +plugins = ["pydantic.mypy"] + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/seed/python-sdk/response-property/reference.md b/seed/python-sdk/response-property/reference.md new file mode 100644 index 00000000000..04a2a3d063e --- /dev/null +++ b/seed/python-sdk/response-property/reference.md @@ -0,0 +1,394 @@ +# Reference +## Service +
client.service.get_movie(...) +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from seed import SeedResponseProperty + +client = SeedResponseProperty( + base_url="https://yourhost.com/path/to/api", +) +client.service.get_movie( + request="string", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request:** `str` + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.service.get_movie_docs(...) +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from seed import SeedResponseProperty + +client = SeedResponseProperty( + base_url="https://yourhost.com/path/to/api", +) +client.service.get_movie_docs( + request="string", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request:** `str` + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.service.get_movie_name(...) +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from seed import SeedResponseProperty + +client = SeedResponseProperty( + base_url="https://yourhost.com/path/to/api", +) +client.service.get_movie_name( + request="string", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request:** `str` + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.service.get_movie_metadata(...) +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from seed import SeedResponseProperty + +client = SeedResponseProperty( + base_url="https://yourhost.com/path/to/api", +) +client.service.get_movie_metadata( + request="string", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request:** `str` + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.service.get_optional_movie(...) +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from seed import SeedResponseProperty + +client = SeedResponseProperty( + base_url="https://yourhost.com/path/to/api", +) +client.service.get_optional_movie( + request="string", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request:** `str` + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.service.get_optional_movie_docs(...) +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from seed import SeedResponseProperty + +client = SeedResponseProperty( + base_url="https://yourhost.com/path/to/api", +) +client.service.get_optional_movie_docs( + request="string", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request:** `str` + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.service.get_optional_movie_name(...) +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from seed import SeedResponseProperty + +client = SeedResponseProperty( + base_url="https://yourhost.com/path/to/api", +) +client.service.get_optional_movie_name( + request="string", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request:** `str` + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ diff --git a/seed/python-sdk/response-property/snippet-templates.json b/seed/python-sdk/response-property/snippet-templates.json new file mode 100644 index 00000000000..0076286c97f --- /dev/null +++ b/seed/python-sdk/response-property/snippet-templates.json @@ -0,0 +1,632 @@ +[ + { + "sdk": { + "package": "fern_response-property", + "version": "0.0.1", + "type": "python" + }, + "endpointId": { + "path": "/movie", + "method": "POST", + "identifierOverride": "endpoint_service.getMovie" + }, + "snippetTemplate": { + "clientInstantiation": { + "imports": [ + "from seed import SeedResponseProperty" + ], + "isOptional": true, + "templateString": "client = SeedResponseProperty(\n base_url=\"https://yourhost.com/path/to/api\",\n)", + "templateInputs": [], + "inputDelimiter": ",", + "type": "generic" + }, + "functionInvocation": { + "imports": [], + "isOptional": true, + "templateString": "client.service.get_movie(\n\t$FERN_INPUT\n)", + "templateInputs": [ + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "request=$FERN_INPUT", + "templateInputs": [ + { + "location": "BODY", + "path": null, + "type": "payload" + } + ], + "type": "generic" + } + } + ], + "inputDelimiter": ",\n\t", + "type": "generic" + }, + "type": "v1" + }, + "additionalTemplates": { + "async": { + "clientInstantiation": { + "imports": [ + "from seed import AsyncSeedResponseProperty" + ], + "isOptional": true, + "templateString": "client = AsyncSeedResponseProperty(\n base_url=\"https://yourhost.com/path/to/api\",\n)", + "templateInputs": [], + "inputDelimiter": ",", + "type": "generic" + }, + "functionInvocation": { + "imports": [], + "isOptional": true, + "templateString": "await client.service.get_movie(\n\t$FERN_INPUT\n)", + "templateInputs": [ + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "request=$FERN_INPUT", + "templateInputs": [ + { + "location": "BODY", + "path": null, + "type": "payload" + } + ], + "type": "generic" + } + } + ], + "inputDelimiter": ",\n\t", + "type": "generic" + }, + "type": "v1" + } + } + }, + { + "sdk": { + "package": "fern_response-property", + "version": "0.0.1", + "type": "python" + }, + "endpointId": { + "path": "/movie", + "method": "POST", + "identifierOverride": "endpoint_service.getMovieDocs" + }, + "snippetTemplate": { + "clientInstantiation": { + "imports": [ + "from seed import SeedResponseProperty" + ], + "isOptional": true, + "templateString": "client = SeedResponseProperty(\n base_url=\"https://yourhost.com/path/to/api\",\n)", + "templateInputs": [], + "inputDelimiter": ",", + "type": "generic" + }, + "functionInvocation": { + "imports": [], + "isOptional": true, + "templateString": "client.service.get_movie_docs(\n\t$FERN_INPUT\n)", + "templateInputs": [ + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "request=$FERN_INPUT", + "templateInputs": [ + { + "location": "BODY", + "path": null, + "type": "payload" + } + ], + "type": "generic" + } + } + ], + "inputDelimiter": ",\n\t", + "type": "generic" + }, + "type": "v1" + }, + "additionalTemplates": { + "async": { + "clientInstantiation": { + "imports": [ + "from seed import AsyncSeedResponseProperty" + ], + "isOptional": true, + "templateString": "client = AsyncSeedResponseProperty(\n base_url=\"https://yourhost.com/path/to/api\",\n)", + "templateInputs": [], + "inputDelimiter": ",", + "type": "generic" + }, + "functionInvocation": { + "imports": [], + "isOptional": true, + "templateString": "await client.service.get_movie_docs(\n\t$FERN_INPUT\n)", + "templateInputs": [ + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "request=$FERN_INPUT", + "templateInputs": [ + { + "location": "BODY", + "path": null, + "type": "payload" + } + ], + "type": "generic" + } + } + ], + "inputDelimiter": ",\n\t", + "type": "generic" + }, + "type": "v1" + } + } + }, + { + "sdk": { + "package": "fern_response-property", + "version": "0.0.1", + "type": "python" + }, + "endpointId": { + "path": "/movie", + "method": "POST", + "identifierOverride": "endpoint_service.getMovieName" + }, + "snippetTemplate": { + "clientInstantiation": { + "imports": [ + "from seed import SeedResponseProperty" + ], + "isOptional": true, + "templateString": "client = SeedResponseProperty(\n base_url=\"https://yourhost.com/path/to/api\",\n)", + "templateInputs": [], + "inputDelimiter": ",", + "type": "generic" + }, + "functionInvocation": { + "imports": [], + "isOptional": true, + "templateString": "client.service.get_movie_name(\n\t$FERN_INPUT\n)", + "templateInputs": [ + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "request=$FERN_INPUT", + "templateInputs": [ + { + "location": "BODY", + "path": null, + "type": "payload" + } + ], + "type": "generic" + } + } + ], + "inputDelimiter": ",\n\t", + "type": "generic" + }, + "type": "v1" + }, + "additionalTemplates": { + "async": { + "clientInstantiation": { + "imports": [ + "from seed import AsyncSeedResponseProperty" + ], + "isOptional": true, + "templateString": "client = AsyncSeedResponseProperty(\n base_url=\"https://yourhost.com/path/to/api\",\n)", + "templateInputs": [], + "inputDelimiter": ",", + "type": "generic" + }, + "functionInvocation": { + "imports": [], + "isOptional": true, + "templateString": "await client.service.get_movie_name(\n\t$FERN_INPUT\n)", + "templateInputs": [ + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "request=$FERN_INPUT", + "templateInputs": [ + { + "location": "BODY", + "path": null, + "type": "payload" + } + ], + "type": "generic" + } + } + ], + "inputDelimiter": ",\n\t", + "type": "generic" + }, + "type": "v1" + } + } + }, + { + "sdk": { + "package": "fern_response-property", + "version": "0.0.1", + "type": "python" + }, + "endpointId": { + "path": "/movie", + "method": "POST", + "identifierOverride": "endpoint_service.getMovieMetadata" + }, + "snippetTemplate": { + "clientInstantiation": { + "imports": [ + "from seed import SeedResponseProperty" + ], + "isOptional": true, + "templateString": "client = SeedResponseProperty(\n base_url=\"https://yourhost.com/path/to/api\",\n)", + "templateInputs": [], + "inputDelimiter": ",", + "type": "generic" + }, + "functionInvocation": { + "imports": [], + "isOptional": true, + "templateString": "client.service.get_movie_metadata(\n\t$FERN_INPUT\n)", + "templateInputs": [ + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "request=$FERN_INPUT", + "templateInputs": [ + { + "location": "BODY", + "path": null, + "type": "payload" + } + ], + "type": "generic" + } + } + ], + "inputDelimiter": ",\n\t", + "type": "generic" + }, + "type": "v1" + }, + "additionalTemplates": { + "async": { + "clientInstantiation": { + "imports": [ + "from seed import AsyncSeedResponseProperty" + ], + "isOptional": true, + "templateString": "client = AsyncSeedResponseProperty(\n base_url=\"https://yourhost.com/path/to/api\",\n)", + "templateInputs": [], + "inputDelimiter": ",", + "type": "generic" + }, + "functionInvocation": { + "imports": [], + "isOptional": true, + "templateString": "await client.service.get_movie_metadata(\n\t$FERN_INPUT\n)", + "templateInputs": [ + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "request=$FERN_INPUT", + "templateInputs": [ + { + "location": "BODY", + "path": null, + "type": "payload" + } + ], + "type": "generic" + } + } + ], + "inputDelimiter": ",\n\t", + "type": "generic" + }, + "type": "v1" + } + } + }, + { + "sdk": { + "package": "fern_response-property", + "version": "0.0.1", + "type": "python" + }, + "endpointId": { + "path": "/movie", + "method": "POST", + "identifierOverride": "endpoint_service.getOptionalMovie" + }, + "snippetTemplate": { + "clientInstantiation": { + "imports": [ + "from seed import SeedResponseProperty" + ], + "isOptional": true, + "templateString": "client = SeedResponseProperty(\n base_url=\"https://yourhost.com/path/to/api\",\n)", + "templateInputs": [], + "inputDelimiter": ",", + "type": "generic" + }, + "functionInvocation": { + "imports": [], + "isOptional": true, + "templateString": "client.service.get_optional_movie(\n\t$FERN_INPUT\n)", + "templateInputs": [ + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "request=$FERN_INPUT", + "templateInputs": [ + { + "location": "BODY", + "path": null, + "type": "payload" + } + ], + "type": "generic" + } + } + ], + "inputDelimiter": ",\n\t", + "type": "generic" + }, + "type": "v1" + }, + "additionalTemplates": { + "async": { + "clientInstantiation": { + "imports": [ + "from seed import AsyncSeedResponseProperty" + ], + "isOptional": true, + "templateString": "client = AsyncSeedResponseProperty(\n base_url=\"https://yourhost.com/path/to/api\",\n)", + "templateInputs": [], + "inputDelimiter": ",", + "type": "generic" + }, + "functionInvocation": { + "imports": [], + "isOptional": true, + "templateString": "await client.service.get_optional_movie(\n\t$FERN_INPUT\n)", + "templateInputs": [ + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "request=$FERN_INPUT", + "templateInputs": [ + { + "location": "BODY", + "path": null, + "type": "payload" + } + ], + "type": "generic" + } + } + ], + "inputDelimiter": ",\n\t", + "type": "generic" + }, + "type": "v1" + } + } + }, + { + "sdk": { + "package": "fern_response-property", + "version": "0.0.1", + "type": "python" + }, + "endpointId": { + "path": "/movie", + "method": "POST", + "identifierOverride": "endpoint_service.getOptionalMovieDocs" + }, + "snippetTemplate": { + "clientInstantiation": { + "imports": [ + "from seed import SeedResponseProperty" + ], + "isOptional": true, + "templateString": "client = SeedResponseProperty(\n base_url=\"https://yourhost.com/path/to/api\",\n)", + "templateInputs": [], + "inputDelimiter": ",", + "type": "generic" + }, + "functionInvocation": { + "imports": [], + "isOptional": true, + "templateString": "client.service.get_optional_movie_docs(\n\t$FERN_INPUT\n)", + "templateInputs": [ + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "request=$FERN_INPUT", + "templateInputs": [ + { + "location": "BODY", + "path": null, + "type": "payload" + } + ], + "type": "generic" + } + } + ], + "inputDelimiter": ",\n\t", + "type": "generic" + }, + "type": "v1" + }, + "additionalTemplates": { + "async": { + "clientInstantiation": { + "imports": [ + "from seed import AsyncSeedResponseProperty" + ], + "isOptional": true, + "templateString": "client = AsyncSeedResponseProperty(\n base_url=\"https://yourhost.com/path/to/api\",\n)", + "templateInputs": [], + "inputDelimiter": ",", + "type": "generic" + }, + "functionInvocation": { + "imports": [], + "isOptional": true, + "templateString": "await client.service.get_optional_movie_docs(\n\t$FERN_INPUT\n)", + "templateInputs": [ + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "request=$FERN_INPUT", + "templateInputs": [ + { + "location": "BODY", + "path": null, + "type": "payload" + } + ], + "type": "generic" + } + } + ], + "inputDelimiter": ",\n\t", + "type": "generic" + }, + "type": "v1" + } + } + }, + { + "sdk": { + "package": "fern_response-property", + "version": "0.0.1", + "type": "python" + }, + "endpointId": { + "path": "/movie", + "method": "POST", + "identifierOverride": "endpoint_service.getOptionalMovieName" + }, + "snippetTemplate": { + "clientInstantiation": { + "imports": [ + "from seed import SeedResponseProperty" + ], + "isOptional": true, + "templateString": "client = SeedResponseProperty(\n base_url=\"https://yourhost.com/path/to/api\",\n)", + "templateInputs": [], + "inputDelimiter": ",", + "type": "generic" + }, + "functionInvocation": { + "imports": [], + "isOptional": true, + "templateString": "client.service.get_optional_movie_name(\n\t$FERN_INPUT\n)", + "templateInputs": [ + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "request=$FERN_INPUT", + "templateInputs": [ + { + "location": "BODY", + "path": null, + "type": "payload" + } + ], + "type": "generic" + } + } + ], + "inputDelimiter": ",\n\t", + "type": "generic" + }, + "type": "v1" + }, + "additionalTemplates": { + "async": { + "clientInstantiation": { + "imports": [ + "from seed import AsyncSeedResponseProperty" + ], + "isOptional": true, + "templateString": "client = AsyncSeedResponseProperty(\n base_url=\"https://yourhost.com/path/to/api\",\n)", + "templateInputs": [], + "inputDelimiter": ",", + "type": "generic" + }, + "functionInvocation": { + "imports": [], + "isOptional": true, + "templateString": "await client.service.get_optional_movie_name(\n\t$FERN_INPUT\n)", + "templateInputs": [ + { + "type": "template", + "value": { + "imports": [], + "isOptional": true, + "templateString": "request=$FERN_INPUT", + "templateInputs": [ + { + "location": "BODY", + "path": null, + "type": "payload" + } + ], + "type": "generic" + } + } + ], + "inputDelimiter": ",\n\t", + "type": "generic" + }, + "type": "v1" + } + } + } +] \ No newline at end of file diff --git a/seed/python-sdk/response-property/snippet.json b/seed/python-sdk/response-property/snippet.json new file mode 100644 index 00000000000..0a309770792 --- /dev/null +++ b/seed/python-sdk/response-property/snippet.json @@ -0,0 +1,96 @@ +{ + "types": {}, + "endpoints": [ + { + "example_identifier": "default", + "id": { + "path": "/movie", + "method": "POST", + "identifier_override": "endpoint_service.getMovie" + }, + "snippet": { + "sync_client": "from seed import SeedResponseProperty\n\nclient = SeedResponseProperty(\n base_url=\"https://yourhost.com/path/to/api\",\n)\nclient.service.get_movie(\n request=\"string\",\n)\n", + "async_client": "import asyncio\n\nfrom seed import AsyncSeedResponseProperty\n\nclient = AsyncSeedResponseProperty(\n base_url=\"https://yourhost.com/path/to/api\",\n)\n\n\nasync def main() -> None:\n await client.service.get_movie(\n request=\"string\",\n )\n\n\nasyncio.run(main())\n", + "type": "python" + } + }, + { + "example_identifier": "default", + "id": { + "path": "/movie", + "method": "POST", + "identifier_override": "endpoint_service.getMovieDocs" + }, + "snippet": { + "sync_client": "from seed import SeedResponseProperty\n\nclient = SeedResponseProperty(\n base_url=\"https://yourhost.com/path/to/api\",\n)\nclient.service.get_movie_docs(\n request=\"string\",\n)\n", + "async_client": "import asyncio\n\nfrom seed import AsyncSeedResponseProperty\n\nclient = AsyncSeedResponseProperty(\n base_url=\"https://yourhost.com/path/to/api\",\n)\n\n\nasync def main() -> None:\n await client.service.get_movie_docs(\n request=\"string\",\n )\n\n\nasyncio.run(main())\n", + "type": "python" + } + }, + { + "example_identifier": "default", + "id": { + "path": "/movie", + "method": "POST", + "identifier_override": "endpoint_service.getMovieName" + }, + "snippet": { + "sync_client": "from seed import SeedResponseProperty\n\nclient = SeedResponseProperty(\n base_url=\"https://yourhost.com/path/to/api\",\n)\nclient.service.get_movie_name(\n request=\"string\",\n)\n", + "async_client": "import asyncio\n\nfrom seed import AsyncSeedResponseProperty\n\nclient = AsyncSeedResponseProperty(\n base_url=\"https://yourhost.com/path/to/api\",\n)\n\n\nasync def main() -> None:\n await client.service.get_movie_name(\n request=\"string\",\n )\n\n\nasyncio.run(main())\n", + "type": "python" + } + }, + { + "example_identifier": "default", + "id": { + "path": "/movie", + "method": "POST", + "identifier_override": "endpoint_service.getMovieMetadata" + }, + "snippet": { + "sync_client": "from seed import SeedResponseProperty\n\nclient = SeedResponseProperty(\n base_url=\"https://yourhost.com/path/to/api\",\n)\nclient.service.get_movie_metadata(\n request=\"string\",\n)\n", + "async_client": "import asyncio\n\nfrom seed import AsyncSeedResponseProperty\n\nclient = AsyncSeedResponseProperty(\n base_url=\"https://yourhost.com/path/to/api\",\n)\n\n\nasync def main() -> None:\n await client.service.get_movie_metadata(\n request=\"string\",\n )\n\n\nasyncio.run(main())\n", + "type": "python" + } + }, + { + "example_identifier": "default", + "id": { + "path": "/movie", + "method": "POST", + "identifier_override": "endpoint_service.getOptionalMovie" + }, + "snippet": { + "sync_client": "from seed import SeedResponseProperty\n\nclient = SeedResponseProperty(\n base_url=\"https://yourhost.com/path/to/api\",\n)\nclient.service.get_optional_movie(\n request=\"string\",\n)\n", + "async_client": "import asyncio\n\nfrom seed import AsyncSeedResponseProperty\n\nclient = AsyncSeedResponseProperty(\n base_url=\"https://yourhost.com/path/to/api\",\n)\n\n\nasync def main() -> None:\n await client.service.get_optional_movie(\n request=\"string\",\n )\n\n\nasyncio.run(main())\n", + "type": "python" + } + }, + { + "example_identifier": "default", + "id": { + "path": "/movie", + "method": "POST", + "identifier_override": "endpoint_service.getOptionalMovieDocs" + }, + "snippet": { + "sync_client": "from seed import SeedResponseProperty\n\nclient = SeedResponseProperty(\n base_url=\"https://yourhost.com/path/to/api\",\n)\nclient.service.get_optional_movie_docs(\n request=\"string\",\n)\n", + "async_client": "import asyncio\n\nfrom seed import AsyncSeedResponseProperty\n\nclient = AsyncSeedResponseProperty(\n base_url=\"https://yourhost.com/path/to/api\",\n)\n\n\nasync def main() -> None:\n await client.service.get_optional_movie_docs(\n request=\"string\",\n )\n\n\nasyncio.run(main())\n", + "type": "python" + } + }, + { + "example_identifier": "default", + "id": { + "path": "/movie", + "method": "POST", + "identifier_override": "endpoint_service.getOptionalMovieName" + }, + "snippet": { + "sync_client": "from seed import SeedResponseProperty\n\nclient = SeedResponseProperty(\n base_url=\"https://yourhost.com/path/to/api\",\n)\nclient.service.get_optional_movie_name(\n request=\"string\",\n)\n", + "async_client": "import asyncio\n\nfrom seed import AsyncSeedResponseProperty\n\nclient = AsyncSeedResponseProperty(\n base_url=\"https://yourhost.com/path/to/api\",\n)\n\n\nasync def main() -> None:\n await client.service.get_optional_movie_name(\n request=\"string\",\n )\n\n\nasyncio.run(main())\n", + "type": "python" + } + } + ] +} \ No newline at end of file diff --git a/seed/python-sdk/response-property/src/seed/__init__.py b/seed/python-sdk/response-property/src/seed/__init__.py new file mode 100644 index 00000000000..67d1a775d1f --- /dev/null +++ b/seed/python-sdk/response-property/src/seed/__init__.py @@ -0,0 +1,21 @@ +# This file was auto-generated by Fern from our API Definition. + +from .types import OptionalStringResponse, StringResponse, WithMetadata +from . import service +from .client import AsyncSeedResponseProperty, SeedResponseProperty +from .service import Movie, OptionalWithDocs, Response, WithDocs +from .version import __version__ + +__all__ = [ + "AsyncSeedResponseProperty", + "Movie", + "OptionalStringResponse", + "OptionalWithDocs", + "Response", + "SeedResponseProperty", + "StringResponse", + "WithDocs", + "WithMetadata", + "__version__", + "service", +] diff --git a/seed/python-sdk/response-property/src/seed/client.py b/seed/python-sdk/response-property/src/seed/client.py new file mode 100644 index 00000000000..ee82d98022c --- /dev/null +++ b/seed/python-sdk/response-property/src/seed/client.py @@ -0,0 +1,104 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import httpx + +from .core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from .service.client import AsyncServiceClient, ServiceClient + + +class SeedResponseProperty: + """ + Use this class to access the different functions within the SDK. You can instantiate any number of clients with different configuration that will propagate to these functions. + + Parameters + ---------- + base_url : str + The base url to use for requests from the client. + + timeout : typing.Optional[float] + The timeout to be used, in seconds, for requests. By default the timeout is 60 seconds, unless a custom httpx client is used, in which case this default is not enforced. + + follow_redirects : typing.Optional[bool] + Whether the default httpx client follows redirects or not, this is irrelevant if a custom httpx client is passed in. + + httpx_client : typing.Optional[httpx.Client] + The httpx client to use for making requests, a preconfigured client is used by default, however this is useful should you want to pass in any custom httpx configuration. + + Examples + -------- + from seed import SeedResponseProperty + + client = SeedResponseProperty( + base_url="https://yourhost.com/path/to/api", + ) + """ + + def __init__( + self, + *, + base_url: str, + timeout: typing.Optional[float] = None, + follow_redirects: typing.Optional[bool] = True, + httpx_client: typing.Optional[httpx.Client] = None + ): + _defaulted_timeout = timeout if timeout is not None else 60 if httpx_client is None else None + self._client_wrapper = SyncClientWrapper( + base_url=base_url, + httpx_client=httpx_client + if httpx_client is not None + else httpx.Client(timeout=_defaulted_timeout, follow_redirects=follow_redirects) + if follow_redirects is not None + else httpx.Client(timeout=_defaulted_timeout), + timeout=_defaulted_timeout, + ) + self.service = ServiceClient(client_wrapper=self._client_wrapper) + + +class AsyncSeedResponseProperty: + """ + Use this class to access the different functions within the SDK. You can instantiate any number of clients with different configuration that will propagate to these functions. + + Parameters + ---------- + base_url : str + The base url to use for requests from the client. + + timeout : typing.Optional[float] + The timeout to be used, in seconds, for requests. By default the timeout is 60 seconds, unless a custom httpx client is used, in which case this default is not enforced. + + follow_redirects : typing.Optional[bool] + Whether the default httpx client follows redirects or not, this is irrelevant if a custom httpx client is passed in. + + httpx_client : typing.Optional[httpx.AsyncClient] + The httpx client to use for making requests, a preconfigured client is used by default, however this is useful should you want to pass in any custom httpx configuration. + + Examples + -------- + from seed import AsyncSeedResponseProperty + + client = AsyncSeedResponseProperty( + base_url="https://yourhost.com/path/to/api", + ) + """ + + def __init__( + self, + *, + base_url: str, + timeout: typing.Optional[float] = None, + follow_redirects: typing.Optional[bool] = True, + httpx_client: typing.Optional[httpx.AsyncClient] = None + ): + _defaulted_timeout = timeout if timeout is not None else 60 if httpx_client is None else None + self._client_wrapper = AsyncClientWrapper( + base_url=base_url, + httpx_client=httpx_client + if httpx_client is not None + else httpx.AsyncClient(timeout=_defaulted_timeout, follow_redirects=follow_redirects) + if follow_redirects is not None + else httpx.AsyncClient(timeout=_defaulted_timeout), + timeout=_defaulted_timeout, + ) + self.service = AsyncServiceClient(client_wrapper=self._client_wrapper) diff --git a/seed/python-sdk/response-property/src/seed/core/__init__.py b/seed/python-sdk/response-property/src/seed/core/__init__.py new file mode 100644 index 00000000000..5a0bee343d0 --- /dev/null +++ b/seed/python-sdk/response-property/src/seed/core/__init__.py @@ -0,0 +1,48 @@ +# This file was auto-generated by Fern from our API Definition. + +from .api_error import ApiError +from .client_wrapper import AsyncClientWrapper, BaseClientWrapper, SyncClientWrapper +from .datetime_utils import serialize_datetime +from .file import File, convert_file_dict_to_httpx_tuples +from .http_client import AsyncHttpClient, HttpClient +from .jsonable_encoder import jsonable_encoder +from .pydantic_utilities import ( + IS_PYDANTIC_V2, + UniversalBaseModel, + UniversalRootModel, + deep_union_pydantic_dicts, + parse_obj_as, + universal_field_validator, + universal_root_validator, + update_forward_refs, +) +from .query_encoder import encode_query +from .remove_none_from_dict import remove_none_from_dict +from .request_options import RequestOptions +from .serialization import FieldMetadata, convert_and_respect_annotation_metadata + +__all__ = [ + "ApiError", + "AsyncClientWrapper", + "AsyncHttpClient", + "BaseClientWrapper", + "FieldMetadata", + "File", + "HttpClient", + "IS_PYDANTIC_V2", + "RequestOptions", + "SyncClientWrapper", + "UniversalBaseModel", + "UniversalRootModel", + "convert_and_respect_annotation_metadata", + "convert_file_dict_to_httpx_tuples", + "deep_union_pydantic_dicts", + "encode_query", + "jsonable_encoder", + "parse_obj_as", + "remove_none_from_dict", + "serialize_datetime", + "universal_field_validator", + "universal_root_validator", + "update_forward_refs", +] diff --git a/seed/python-sdk/response-property/src/seed/core/api_error.py b/seed/python-sdk/response-property/src/seed/core/api_error.py new file mode 100644 index 00000000000..2e9fc5431cd --- /dev/null +++ b/seed/python-sdk/response-property/src/seed/core/api_error.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + + +class ApiError(Exception): + status_code: typing.Optional[int] + body: typing.Any + + def __init__(self, *, status_code: typing.Optional[int] = None, body: typing.Any = None): + self.status_code = status_code + self.body = body + + def __str__(self) -> str: + return f"status_code: {self.status_code}, body: {self.body}" diff --git a/seed/python-sdk/response-property/src/seed/core/client_wrapper.py b/seed/python-sdk/response-property/src/seed/core/client_wrapper.py new file mode 100644 index 00000000000..35303ce1eb4 --- /dev/null +++ b/seed/python-sdk/response-property/src/seed/core/client_wrapper.py @@ -0,0 +1,49 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import httpx + +from .http_client import AsyncHttpClient, HttpClient + + +class BaseClientWrapper: + def __init__(self, *, base_url: str, timeout: typing.Optional[float] = None): + self._base_url = base_url + self._timeout = timeout + + def get_headers(self) -> typing.Dict[str, str]: + headers: typing.Dict[str, str] = { + "X-Fern-Language": "Python", + "X-Fern-SDK-Name": "fern_response-property", + "X-Fern-SDK-Version": "0.0.1", + } + return headers + + def get_base_url(self) -> str: + return self._base_url + + def get_timeout(self) -> typing.Optional[float]: + return self._timeout + + +class SyncClientWrapper(BaseClientWrapper): + def __init__(self, *, base_url: str, timeout: typing.Optional[float] = None, httpx_client: httpx.Client): + super().__init__(base_url=base_url, timeout=timeout) + self.httpx_client = HttpClient( + httpx_client=httpx_client, + base_headers=self.get_headers(), + base_timeout=self.get_timeout(), + base_url=self.get_base_url(), + ) + + +class AsyncClientWrapper(BaseClientWrapper): + def __init__(self, *, base_url: str, timeout: typing.Optional[float] = None, httpx_client: httpx.AsyncClient): + super().__init__(base_url=base_url, timeout=timeout) + self.httpx_client = AsyncHttpClient( + httpx_client=httpx_client, + base_headers=self.get_headers(), + base_timeout=self.get_timeout(), + base_url=self.get_base_url(), + ) diff --git a/seed/python-sdk/response-property/src/seed/core/datetime_utils.py b/seed/python-sdk/response-property/src/seed/core/datetime_utils.py new file mode 100644 index 00000000000..7c9864a944c --- /dev/null +++ b/seed/python-sdk/response-property/src/seed/core/datetime_utils.py @@ -0,0 +1,28 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt + + +def serialize_datetime(v: dt.datetime) -> str: + """ + Serialize a datetime including timezone info. + + Uses the timezone info provided if present, otherwise uses the current runtime's timezone info. + + UTC datetimes end in "Z" while all other timezones are represented as offset from UTC, e.g. +05:00. + """ + + def _serialize_zoned_datetime(v: dt.datetime) -> str: + if v.tzinfo is not None and v.tzinfo.tzname(None) == dt.timezone.utc.tzname(None): + # UTC is a special case where we use "Z" at the end instead of "+00:00" + return v.isoformat().replace("+00:00", "Z") + else: + # Delegate to the typical +/- offset format + return v.isoformat() + + if v.tzinfo is not None: + return _serialize_zoned_datetime(v) + else: + local_tz = dt.datetime.now().astimezone().tzinfo + localized_dt = v.replace(tzinfo=local_tz) + return _serialize_zoned_datetime(localized_dt) diff --git a/seed/python-sdk/response-property/src/seed/core/file.py b/seed/python-sdk/response-property/src/seed/core/file.py new file mode 100644 index 00000000000..cb0d40bbbf3 --- /dev/null +++ b/seed/python-sdk/response-property/src/seed/core/file.py @@ -0,0 +1,38 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +# File typing inspired by the flexibility of types within the httpx library +# https://github.com/encode/httpx/blob/master/httpx/_types.py +FileContent = typing.Union[typing.IO[bytes], bytes, str] +File = typing.Union[ + # file (or bytes) + FileContent, + # (filename, file (or bytes)) + typing.Tuple[typing.Optional[str], FileContent], + # (filename, file (or bytes), content_type) + typing.Tuple[typing.Optional[str], FileContent, typing.Optional[str]], + # (filename, file (or bytes), content_type, headers) + typing.Tuple[typing.Optional[str], FileContent, typing.Optional[str], typing.Mapping[str, str]], +] + + +def convert_file_dict_to_httpx_tuples( + d: typing.Dict[str, typing.Union[File, typing.List[File]]] +) -> typing.List[typing.Tuple[str, File]]: + """ + The format we use is a list of tuples, where the first element is the + name of the file and the second is the file object. Typically HTTPX wants + a dict, but to be able to send lists of files, you have to use the list + approach (which also works for non-lists) + https://github.com/encode/httpx/pull/1032 + """ + + httpx_tuples = [] + for key, file_like in d.items(): + if isinstance(file_like, list): + for file_like_item in file_like: + httpx_tuples.append((key, file_like_item)) + else: + httpx_tuples.append((key, file_like)) + return httpx_tuples diff --git a/seed/python-sdk/response-property/src/seed/core/http_client.py b/seed/python-sdk/response-property/src/seed/core/http_client.py new file mode 100644 index 00000000000..356880bbc3e --- /dev/null +++ b/seed/python-sdk/response-property/src/seed/core/http_client.py @@ -0,0 +1,476 @@ +# This file was auto-generated by Fern from our API Definition. + +import asyncio +import email.utils +import json +import re +import time +import typing +import urllib.parse +from contextlib import asynccontextmanager, contextmanager +from random import random + +import httpx + +from .file import File, convert_file_dict_to_httpx_tuples +from .jsonable_encoder import jsonable_encoder +from .query_encoder import encode_query +from .remove_none_from_dict import remove_none_from_dict +from .request_options import RequestOptions + +INITIAL_RETRY_DELAY_SECONDS = 0.5 +MAX_RETRY_DELAY_SECONDS = 10 +MAX_RETRY_DELAY_SECONDS_FROM_HEADER = 30 + + +def _parse_retry_after(response_headers: httpx.Headers) -> typing.Optional[float]: + """ + This function parses the `Retry-After` header in a HTTP response and returns the number of seconds to wait. + + Inspired by the urllib3 retry implementation. + """ + retry_after_ms = response_headers.get("retry-after-ms") + if retry_after_ms is not None: + try: + return int(retry_after_ms) / 1000 if retry_after_ms > 0 else 0 + except Exception: + pass + + retry_after = response_headers.get("retry-after") + if retry_after is None: + return None + + # Attempt to parse the header as an int. + if re.match(r"^\s*[0-9]+\s*$", retry_after): + seconds = float(retry_after) + # Fallback to parsing it as a date. + else: + retry_date_tuple = email.utils.parsedate_tz(retry_after) + if retry_date_tuple is None: + return None + if retry_date_tuple[9] is None: # Python 2 + # Assume UTC if no timezone was specified + # On Python2.7, parsedate_tz returns None for a timezone offset + # instead of 0 if no timezone is given, where mktime_tz treats + # a None timezone offset as local time. + retry_date_tuple = retry_date_tuple[:9] + (0,) + retry_date_tuple[10:] + + retry_date = email.utils.mktime_tz(retry_date_tuple) + seconds = retry_date - time.time() + + if seconds < 0: + seconds = 0 + + return seconds + + +def _retry_timeout(response: httpx.Response, retries: int) -> float: + """ + Determine the amount of time to wait before retrying a request. + This function begins by trying to parse a retry-after header from the response, and then proceeds to use exponential backoff + with a jitter to determine the number of seconds to wait. + """ + + # If the API asks us to wait a certain amount of time (and it's a reasonable amount), just do what it says. + retry_after = _parse_retry_after(response.headers) + if retry_after is not None and retry_after <= MAX_RETRY_DELAY_SECONDS_FROM_HEADER: + return retry_after + + # Apply exponential backoff, capped at MAX_RETRY_DELAY_SECONDS. + retry_delay = min(INITIAL_RETRY_DELAY_SECONDS * pow(2.0, retries), MAX_RETRY_DELAY_SECONDS) + + # Add a randomness / jitter to the retry delay to avoid overwhelming the server with retries. + timeout = retry_delay * (1 - 0.25 * random()) + return timeout if timeout >= 0 else 0 + + +def _should_retry(response: httpx.Response) -> bool: + retriable_400s = [429, 408, 409] + return response.status_code >= 500 or response.status_code in retriable_400s + + +def remove_omit_from_dict( + original: typing.Dict[str, typing.Optional[typing.Any]], omit: typing.Optional[typing.Any] +) -> typing.Dict[str, typing.Any]: + if omit is None: + return original + new: typing.Dict[str, typing.Any] = {} + for key, value in original.items(): + if value is not omit: + new[key] = value + return new + + +def maybe_filter_request_body( + data: typing.Optional[typing.Any], + request_options: typing.Optional[RequestOptions], + omit: typing.Optional[typing.Any], +) -> typing.Optional[typing.Any]: + if data is None: + return ( + jsonable_encoder(request_options.get("additional_body_parameters", {})) or {} + if request_options is not None + else None + ) + elif not isinstance(data, typing.Mapping): + data_content = jsonable_encoder(data) + else: + data_content = { + **(jsonable_encoder(remove_omit_from_dict(data, omit))), # type: ignore + **( + jsonable_encoder(request_options.get("additional_body_parameters", {})) or {} + if request_options is not None + else {} + ), + } + return data_content + + +# Abstracted out for testing purposes +def get_request_body( + *, + json: typing.Optional[typing.Any], + data: typing.Optional[typing.Any], + request_options: typing.Optional[RequestOptions], + omit: typing.Optional[typing.Any], +) -> typing.Tuple[typing.Optional[typing.Any], typing.Optional[typing.Any]]: + json_body = None + data_body = None + if data is not None: + data_body = maybe_filter_request_body(data, request_options, omit) + else: + # If both data and json are None, we send json data in the event extra properties are specified + json_body = maybe_filter_request_body(json, request_options, omit) + + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None + + +class HttpClient: + def __init__( + self, + *, + httpx_client: httpx.Client, + base_timeout: typing.Optional[float], + base_headers: typing.Dict[str, str], + base_url: typing.Optional[str] = None, + ): + self.base_url = base_url + self.base_timeout = base_timeout + self.base_headers = base_headers + self.httpx_client = httpx_client + + def get_base_url(self, maybe_base_url: typing.Optional[str]) -> str: + base_url = self.base_url if maybe_base_url is None else maybe_base_url + if base_url is None: + raise ValueError("A base_url is required to make this request, please provide one and try again.") + return base_url + + def request( + self, + path: typing.Optional[str] = None, + *, + method: str, + base_url: typing.Optional[str] = None, + params: typing.Optional[typing.Dict[str, typing.Any]] = None, + json: typing.Optional[typing.Any] = None, + data: typing.Optional[typing.Any] = None, + content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None, + files: typing.Optional[typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]]] = None, + headers: typing.Optional[typing.Dict[str, typing.Any]] = None, + request_options: typing.Optional[RequestOptions] = None, + retries: int = 0, + omit: typing.Optional[typing.Any] = None, + ) -> httpx.Response: + base_url = self.get_base_url(base_url) + timeout = ( + request_options.get("timeout_in_seconds") + if request_options is not None and request_options.get("timeout_in_seconds") is not None + else self.base_timeout + ) + + json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit) + + response = self.httpx_client.request( + method=method, + url=urllib.parse.urljoin(f"{base_url}/", path), + headers=jsonable_encoder( + remove_none_from_dict( + { + **self.base_headers, + **(headers if headers is not None else {}), + **(request_options.get("additional_headers", {}) or {} if request_options is not None else {}), + } + ) + ), + params=encode_query( + jsonable_encoder( + remove_none_from_dict( + remove_omit_from_dict( + { + **(params if params is not None else {}), + **( + request_options.get("additional_query_parameters", {}) or {} + if request_options is not None + else {} + ), + }, + omit, + ) + ) + ) + ), + json=json_body, + data=data_body, + content=content, + files=convert_file_dict_to_httpx_tuples(remove_none_from_dict(files)) if files is not None else None, + timeout=timeout, + ) + + max_retries: int = request_options.get("max_retries", 0) if request_options is not None else 0 + if _should_retry(response=response): + if max_retries > retries: + time.sleep(_retry_timeout(response=response, retries=retries)) + return self.request( + path=path, + method=method, + base_url=base_url, + params=params, + json=json, + content=content, + files=files, + headers=headers, + request_options=request_options, + retries=retries + 1, + omit=omit, + ) + + return response + + @contextmanager + def stream( + self, + path: typing.Optional[str] = None, + *, + method: str, + base_url: typing.Optional[str] = None, + params: typing.Optional[typing.Dict[str, typing.Any]] = None, + json: typing.Optional[typing.Any] = None, + data: typing.Optional[typing.Any] = None, + content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None, + files: typing.Optional[typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]]] = None, + headers: typing.Optional[typing.Dict[str, typing.Any]] = None, + request_options: typing.Optional[RequestOptions] = None, + retries: int = 0, + omit: typing.Optional[typing.Any] = None, + ) -> typing.Iterator[httpx.Response]: + base_url = self.get_base_url(base_url) + timeout = ( + request_options.get("timeout_in_seconds") + if request_options is not None and request_options.get("timeout_in_seconds") is not None + else self.base_timeout + ) + + json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit) + + with self.httpx_client.stream( + method=method, + url=urllib.parse.urljoin(f"{base_url}/", path), + headers=jsonable_encoder( + remove_none_from_dict( + { + **self.base_headers, + **(headers if headers is not None else {}), + **(request_options.get("additional_headers", {}) if request_options is not None else {}), + } + ) + ), + params=encode_query( + jsonable_encoder( + remove_none_from_dict( + remove_omit_from_dict( + { + **(params if params is not None else {}), + **( + request_options.get("additional_query_parameters", {}) + if request_options is not None + else {} + ), + }, + omit, + ) + ) + ) + ), + json=json_body, + data=data_body, + content=content, + files=convert_file_dict_to_httpx_tuples(remove_none_from_dict(files)) if files is not None else None, + timeout=timeout, + ) as stream: + yield stream + + +class AsyncHttpClient: + def __init__( + self, + *, + httpx_client: httpx.AsyncClient, + base_timeout: typing.Optional[float], + base_headers: typing.Dict[str, str], + base_url: typing.Optional[str] = None, + ): + self.base_url = base_url + self.base_timeout = base_timeout + self.base_headers = base_headers + self.httpx_client = httpx_client + + def get_base_url(self, maybe_base_url: typing.Optional[str]) -> str: + base_url = self.base_url if maybe_base_url is None else maybe_base_url + if base_url is None: + raise ValueError("A base_url is required to make this request, please provide one and try again.") + return base_url + + async def request( + self, + path: typing.Optional[str] = None, + *, + method: str, + base_url: typing.Optional[str] = None, + params: typing.Optional[typing.Dict[str, typing.Any]] = None, + json: typing.Optional[typing.Any] = None, + data: typing.Optional[typing.Any] = None, + content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None, + files: typing.Optional[typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]]] = None, + headers: typing.Optional[typing.Dict[str, typing.Any]] = None, + request_options: typing.Optional[RequestOptions] = None, + retries: int = 0, + omit: typing.Optional[typing.Any] = None, + ) -> httpx.Response: + base_url = self.get_base_url(base_url) + timeout = ( + request_options.get("timeout_in_seconds") + if request_options is not None and request_options.get("timeout_in_seconds") is not None + else self.base_timeout + ) + + json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit) + + # Add the input to each of these and do None-safety checks + response = await self.httpx_client.request( + method=method, + url=urllib.parse.urljoin(f"{base_url}/", path), + headers=jsonable_encoder( + remove_none_from_dict( + { + **self.base_headers, + **(headers if headers is not None else {}), + **(request_options.get("additional_headers", {}) or {} if request_options is not None else {}), + } + ) + ), + params=encode_query( + jsonable_encoder( + remove_none_from_dict( + remove_omit_from_dict( + { + **(params if params is not None else {}), + **( + request_options.get("additional_query_parameters", {}) or {} + if request_options is not None + else {} + ), + }, + omit, + ) + ) + ) + ), + json=json_body, + data=data_body, + content=content, + files=convert_file_dict_to_httpx_tuples(remove_none_from_dict(files)) if files is not None else None, + timeout=timeout, + ) + + max_retries: int = request_options.get("max_retries", 0) if request_options is not None else 0 + if _should_retry(response=response): + if max_retries > retries: + await asyncio.sleep(_retry_timeout(response=response, retries=retries)) + return await self.request( + path=path, + method=method, + base_url=base_url, + params=params, + json=json, + content=content, + files=files, + headers=headers, + request_options=request_options, + retries=retries + 1, + omit=omit, + ) + return response + + @asynccontextmanager + async def stream( + self, + path: typing.Optional[str] = None, + *, + method: str, + base_url: typing.Optional[str] = None, + params: typing.Optional[typing.Dict[str, typing.Any]] = None, + json: typing.Optional[typing.Any] = None, + data: typing.Optional[typing.Any] = None, + content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None, + files: typing.Optional[typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]]] = None, + headers: typing.Optional[typing.Dict[str, typing.Any]] = None, + request_options: typing.Optional[RequestOptions] = None, + retries: int = 0, + omit: typing.Optional[typing.Any] = None, + ) -> typing.AsyncIterator[httpx.Response]: + base_url = self.get_base_url(base_url) + timeout = ( + request_options.get("timeout_in_seconds") + if request_options is not None and request_options.get("timeout_in_seconds") is not None + else self.base_timeout + ) + + json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit) + + async with self.httpx_client.stream( + method=method, + url=urllib.parse.urljoin(f"{base_url}/", path), + headers=jsonable_encoder( + remove_none_from_dict( + { + **self.base_headers, + **(headers if headers is not None else {}), + **(request_options.get("additional_headers", {}) if request_options is not None else {}), + } + ) + ), + params=encode_query( + jsonable_encoder( + remove_none_from_dict( + remove_omit_from_dict( + { + **(params if params is not None else {}), + **( + request_options.get("additional_query_parameters", {}) + if request_options is not None + else {} + ), + }, + omit=omit, + ) + ) + ) + ), + json=json_body, + data=data_body, + content=content, + files=convert_file_dict_to_httpx_tuples(remove_none_from_dict(files)) if files is not None else None, + timeout=timeout, + ) as stream: + yield stream diff --git a/seed/python-sdk/response-property/src/seed/core/jsonable_encoder.py b/seed/python-sdk/response-property/src/seed/core/jsonable_encoder.py new file mode 100644 index 00000000000..d3fd328fd41 --- /dev/null +++ b/seed/python-sdk/response-property/src/seed/core/jsonable_encoder.py @@ -0,0 +1,97 @@ +# This file was auto-generated by Fern from our API Definition. + +""" +jsonable_encoder converts a Python object to a JSON-friendly dict +(e.g. datetimes to strings, Pydantic models to dicts). + +Taken from FastAPI, and made a bit simpler +https://github.com/tiangolo/fastapi/blob/master/fastapi/encoders.py +""" + +import base64 +import dataclasses +import datetime as dt +from enum import Enum +from pathlib import PurePath +from types import GeneratorType +from typing import Any, Callable, Dict, List, Optional, Set, Union + +import pydantic + +from .datetime_utils import serialize_datetime +from .pydantic_utilities import IS_PYDANTIC_V2, encode_by_type, to_jsonable_with_fallback + +SetIntStr = Set[Union[int, str]] +DictIntStrAny = Dict[Union[int, str], Any] + + +def jsonable_encoder(obj: Any, custom_encoder: Optional[Dict[Any, Callable[[Any], Any]]] = None) -> Any: + custom_encoder = custom_encoder or {} + if custom_encoder: + if type(obj) in custom_encoder: + return custom_encoder[type(obj)](obj) + else: + for encoder_type, encoder_instance in custom_encoder.items(): + if isinstance(obj, encoder_type): + return encoder_instance(obj) + if isinstance(obj, pydantic.BaseModel): + if IS_PYDANTIC_V2: + encoder = getattr(obj.model_config, "json_encoders", {}) # type: ignore # Pydantic v2 + else: + encoder = getattr(obj.__config__, "json_encoders", {}) # type: ignore # Pydantic v1 + if custom_encoder: + encoder.update(custom_encoder) + obj_dict = obj.dict(by_alias=True) + if "__root__" in obj_dict: + obj_dict = obj_dict["__root__"] + if "root" in obj_dict: + obj_dict = obj_dict["root"] + return jsonable_encoder(obj_dict, custom_encoder=encoder) + if dataclasses.is_dataclass(obj): + obj_dict = dataclasses.asdict(obj) # type: ignore + return jsonable_encoder(obj_dict, custom_encoder=custom_encoder) + if isinstance(obj, bytes): + return base64.b64encode(obj).decode("utf-8") + if isinstance(obj, Enum): + return obj.value + if isinstance(obj, PurePath): + return str(obj) + if isinstance(obj, (str, int, float, type(None))): + return obj + if isinstance(obj, dt.datetime): + return serialize_datetime(obj) + if isinstance(obj, dt.date): + return str(obj) + if isinstance(obj, dict): + encoded_dict = {} + allowed_keys = set(obj.keys()) + for key, value in obj.items(): + if key in allowed_keys: + encoded_key = jsonable_encoder(key, custom_encoder=custom_encoder) + encoded_value = jsonable_encoder(value, custom_encoder=custom_encoder) + encoded_dict[encoded_key] = encoded_value + return encoded_dict + if isinstance(obj, (list, set, frozenset, GeneratorType, tuple)): + encoded_list = [] + for item in obj: + encoded_list.append(jsonable_encoder(item, custom_encoder=custom_encoder)) + return encoded_list + + def fallback_serializer(o: Any) -> Any: + attempt_encode = encode_by_type(o) + if attempt_encode is not None: + return attempt_encode + + try: + data = dict(o) + except Exception as e: + errors: List[Exception] = [] + errors.append(e) + try: + data = vars(o) + except Exception as e: + errors.append(e) + raise ValueError(errors) from e + return jsonable_encoder(data, custom_encoder=custom_encoder) + + return to_jsonable_with_fallback(obj, fallback_serializer) diff --git a/seed/python-sdk/response-property/src/seed/core/pydantic_utilities.py b/seed/python-sdk/response-property/src/seed/core/pydantic_utilities.py new file mode 100644 index 00000000000..d7fb87bf581 --- /dev/null +++ b/seed/python-sdk/response-property/src/seed/core/pydantic_utilities.py @@ -0,0 +1,179 @@ +# This file was auto-generated by Fern from our API Definition. + +# nopycln: file +import datetime as dt +import typing +from collections import defaultdict +from functools import wraps + +import pydantic + +from .datetime_utils import serialize_datetime + +IS_PYDANTIC_V2 = pydantic.VERSION.startswith("2.") + +if IS_PYDANTIC_V2: + # isort will try to reformat the comments on these imports, which breaks mypy + # isort: off + from pydantic.v1.datetime_parse import ( # type: ignore # pyright: ignore[reportMissingImports] # Pydantic v2 + parse_date as parse_date, + ) + from pydantic.v1.datetime_parse import ( # pyright: ignore[reportMissingImports] # Pydantic v2 + parse_datetime as parse_datetime, + ) + from pydantic.v1.json import ( # type: ignore # pyright: ignore[reportMissingImports] # Pydantic v2 + ENCODERS_BY_TYPE as encoders_by_type, + ) + from pydantic.v1.typing import ( # type: ignore # pyright: ignore[reportMissingImports] # Pydantic v2 + get_args as get_args, + ) + from pydantic.v1.typing import get_origin as get_origin # pyright: ignore[reportMissingImports] # Pydantic v2 + from pydantic.v1.typing import ( # pyright: ignore[reportMissingImports] # Pydantic v2 + is_literal_type as is_literal_type, + ) + from pydantic.v1.typing import is_union as is_union # pyright: ignore[reportMissingImports] # Pydantic v2 + from pydantic.v1.fields import ModelField as ModelField # type: ignore # pyright: ignore[reportMissingImports] # Pydantic v2 +else: + from pydantic.datetime_parse import parse_date as parse_date # type: ignore # Pydantic v1 + from pydantic.datetime_parse import parse_datetime as parse_datetime # type: ignore # Pydantic v1 + from pydantic.fields import ModelField as ModelField # type: ignore # Pydantic v1 + from pydantic.json import ENCODERS_BY_TYPE as encoders_by_type # type: ignore # Pydantic v1 + from pydantic.typing import get_args as get_args # type: ignore # Pydantic v1 + from pydantic.typing import get_origin as get_origin # type: ignore # Pydantic v1 + from pydantic.typing import is_literal_type as is_literal_type # type: ignore # Pydantic v1 + from pydantic.typing import is_union as is_union # type: ignore # Pydantic v1 + + # isort: on + + +T = typing.TypeVar("T") +Model = typing.TypeVar("Model", bound=pydantic.BaseModel) + + +def deep_union_pydantic_dicts( + source: typing.Dict[str, typing.Any], destination: typing.Dict[str, typing.Any] +) -> typing.Dict[str, typing.Any]: + for key, value in source.items(): + if isinstance(value, dict): + node = destination.setdefault(key, {}) + deep_union_pydantic_dicts(value, node) + else: + destination[key] = value + + return destination + + +def parse_obj_as(type_: typing.Type[T], object_: typing.Any) -> T: + if IS_PYDANTIC_V2: + adapter = pydantic.TypeAdapter(type_) # type: ignore # Pydantic v2 + return adapter.validate_python(object_) + else: + return pydantic.parse_obj_as(type_, object_) + + +def to_jsonable_with_fallback( + obj: typing.Any, fallback_serializer: typing.Callable[[typing.Any], typing.Any] +) -> typing.Any: + if IS_PYDANTIC_V2: + from pydantic_core import to_jsonable_python + + return to_jsonable_python(obj, fallback=fallback_serializer) + else: + return fallback_serializer(obj) + + +class UniversalBaseModel(pydantic.BaseModel): + class Config: + populate_by_name = True + smart_union = True + allow_population_by_field_name = True + json_encoders = {dt.datetime: serialize_datetime} + + def json(self, **kwargs: typing.Any) -> str: + kwargs_with_defaults: typing.Any = {"by_alias": True, "exclude_unset": True, **kwargs} + if IS_PYDANTIC_V2: + return super().model_dump_json(**kwargs_with_defaults) # type: ignore # Pydantic v2 + else: + return super().json(**kwargs_with_defaults) + + def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: + kwargs_with_defaults_exclude_unset: typing.Any = {"by_alias": True, "exclude_unset": True, **kwargs} + kwargs_with_defaults_exclude_none: typing.Any = {"by_alias": True, "exclude_none": True, **kwargs} + + if IS_PYDANTIC_V2: + return deep_union_pydantic_dicts( + super().model_dump(**kwargs_with_defaults_exclude_unset), # type: ignore # Pydantic v2 + super().model_dump(**kwargs_with_defaults_exclude_none), # type: ignore # Pydantic v2 + ) + else: + return deep_union_pydantic_dicts( + super().dict(**kwargs_with_defaults_exclude_unset), super().dict(**kwargs_with_defaults_exclude_none) + ) + + +UniversalRootModel: typing.Type[pydantic.BaseModel] +if IS_PYDANTIC_V2: + + class V2RootModel(UniversalBaseModel, pydantic.RootModel): # type: ignore # Pydantic v2 + pass + + UniversalRootModel = V2RootModel +else: + UniversalRootModel = UniversalBaseModel + + +def encode_by_type(o: typing.Any) -> typing.Any: + encoders_by_class_tuples: typing.Dict[ + typing.Callable[[typing.Any], typing.Any], typing.Tuple[typing.Any, ...] + ] = defaultdict(tuple) + for type_, encoder in encoders_by_type.items(): + encoders_by_class_tuples[encoder] += (type_,) + + if type(o) in encoders_by_type: + return encoders_by_type[type(o)](o) + for encoder, classes_tuple in encoders_by_class_tuples.items(): + if isinstance(o, classes_tuple): + return encoder(o) + + +def update_forward_refs(model: typing.Type["Model"], **localns: typing.Any) -> None: + if IS_PYDANTIC_V2: + model.model_rebuild(force=True, raise_errors=False) # type: ignore # Pydantic v2 + else: + model.update_forward_refs(**localns) + + +# Mirrors Pydantic's internal typing +AnyCallable = typing.Callable[..., typing.Any] + + +def universal_root_validator(pre: bool = False) -> typing.Callable[[AnyCallable], AnyCallable]: + def decorator(func: AnyCallable) -> AnyCallable: + @wraps(func) + def validate(*args: typing.Any, **kwargs: typing.Any) -> AnyCallable: + if IS_PYDANTIC_V2: + wrapped_func = pydantic.model_validator("before" if pre else "after")(func) # type: ignore # Pydantic v2 + else: + wrapped_func = pydantic.root_validator(pre=pre)(func) # type: ignore # Pydantic v1 + + return wrapped_func(*args, **kwargs) # type: ignore # Pydantic v2 + + return validate + + return decorator + + +def universal_field_validator(field_name: str, pre: bool = False) -> typing.Callable[[AnyCallable], AnyCallable]: + def decorator(func: AnyCallable) -> AnyCallable: + @wraps(func) + def validate(*args: typing.Any, **kwargs: typing.Any) -> AnyCallable: + if IS_PYDANTIC_V2: + wrapped_func = pydantic.field_validator(field_name, mode="before" if pre else "after")(func) # type: ignore # Pydantic v2 + else: + wrapped_func = pydantic.validator(field_name, pre=pre)(func) # type: ignore # Pydantic v1 + + return wrapped_func(*args, **kwargs) + + return validate + + return decorator diff --git a/seed/python-sdk/response-property/src/seed/core/query_encoder.py b/seed/python-sdk/response-property/src/seed/core/query_encoder.py new file mode 100644 index 00000000000..24076d72ee9 --- /dev/null +++ b/seed/python-sdk/response-property/src/seed/core/query_encoder.py @@ -0,0 +1,33 @@ +# This file was auto-generated by Fern from our API Definition. + +from collections import ChainMap +from typing import Any, Dict, Optional + +import pydantic + + +# Flattens dicts to be of the form {"key[subkey][subkey2]": value} where value is not a dict +def traverse_query_dict(dict_flat: Dict[str, Any], key_prefix: Optional[str] = None) -> Dict[str, Any]: + result = {} + for k, v in dict_flat.items(): + key = f"{key_prefix}[{k}]" if key_prefix is not None else k + if isinstance(v, dict): + result.update(traverse_query_dict(v, key)) + else: + result[key] = v + return result + + +def single_query_encoder(query_key: str, query_value: Any) -> Dict[str, Any]: + if isinstance(query_value, pydantic.BaseModel) or isinstance(query_value, dict): + if isinstance(query_value, pydantic.BaseModel): + obj_dict = query_value.dict(by_alias=True) + else: + obj_dict = query_value + return traverse_query_dict(obj_dict, query_key) + + return {query_key: query_value} + + +def encode_query(query: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]: + return dict(ChainMap(*[single_query_encoder(k, v) for k, v in query.items()])) if query is not None else None diff --git a/seed/python-sdk/response-property/src/seed/core/remove_none_from_dict.py b/seed/python-sdk/response-property/src/seed/core/remove_none_from_dict.py new file mode 100644 index 00000000000..c2298143f14 --- /dev/null +++ b/seed/python-sdk/response-property/src/seed/core/remove_none_from_dict.py @@ -0,0 +1,11 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import Any, Dict, Mapping, Optional + + +def remove_none_from_dict(original: Mapping[str, Optional[Any]]) -> Dict[str, Any]: + new: Dict[str, Any] = {} + for key, value in original.items(): + if value is not None: + new[key] = value + return new diff --git a/seed/python-sdk/response-property/src/seed/core/request_options.py b/seed/python-sdk/response-property/src/seed/core/request_options.py new file mode 100644 index 00000000000..d0bf0dbcecd --- /dev/null +++ b/seed/python-sdk/response-property/src/seed/core/request_options.py @@ -0,0 +1,32 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +try: + from typing import NotRequired # type: ignore +except ImportError: + from typing_extensions import NotRequired + + +class RequestOptions(typing.TypedDict, total=False): + """ + Additional options for request-specific configuration when calling APIs via the SDK. + This is used primarily as an optional final parameter for service functions. + + Attributes: + - timeout_in_seconds: int. The number of seconds to await an API call before timing out. + + - max_retries: int. The max number of retries to attempt if the API call fails. + + - additional_headers: typing.Dict[str, typing.Any]. A dictionary containing additional parameters to spread into the request's header dict + + - additional_query_parameters: typing.Dict[str, typing.Any]. A dictionary containing additional parameters to spread into the request's query parameters dict + + - additional_body_parameters: typing.Dict[str, typing.Any]. A dictionary containing additional parameters to spread into the request's body parameters dict + """ + + timeout_in_seconds: NotRequired[int] + max_retries: NotRequired[int] + additional_headers: NotRequired[typing.Dict[str, typing.Any]] + additional_query_parameters: NotRequired[typing.Dict[str, typing.Any]] + additional_body_parameters: NotRequired[typing.Dict[str, typing.Any]] diff --git a/seed/python-sdk/response-property/src/seed/core/serialization.py b/seed/python-sdk/response-property/src/seed/core/serialization.py new file mode 100644 index 00000000000..8ad5cf8125f --- /dev/null +++ b/seed/python-sdk/response-property/src/seed/core/serialization.py @@ -0,0 +1,167 @@ +# This file was auto-generated by Fern from our API Definition. + +import collections +import typing + +import typing_extensions + + +class FieldMetadata: + """ + Metadata class used to annotate fields to provide additional information. + + Example: + class MyDict(TypedDict): + field: typing.Annotated[str, FieldMetadata(alias="field_name")] + + Will serialize: `{"field": "value"}` + To: `{"field_name": "value"}` + """ + + alias: str + + def __init__(self, *, alias: str) -> None: + self.alias = alias + + +def convert_and_respect_annotation_metadata( + *, object_: typing.Any, annotation: typing.Any, inner_type: typing.Optional[typing.Any] = None +) -> typing.Any: + """ + Respect the metadata annotations on a field, such as aliasing. This function effectively + manipulates the dict-form of an object to respect the metadata annotations. This is primarily used for + TypedDicts, which cannot support aliasing out of the box, and can be extended for additional + utilities, such as defaults. + + Parameters + ---------- + object_ : typing.Any + + annotation : type + The type we're looking to apply typing annotations from + + inner_type : typing.Optional[type] + + Returns + ------- + typing.Any + """ + + if object_ is None: + return None + if inner_type is None: + inner_type = annotation + + clean_type = _remove_annotations(inner_type) + if typing_extensions.is_typeddict(clean_type) and isinstance(object_, typing.Mapping): + return _convert_typeddict(object_, clean_type) + + if ( + # If you're iterating on a string, do not bother to coerce it to a sequence. + (not isinstance(object_, str)) + and ( + ( + ( + typing_extensions.get_origin(clean_type) == typing.List + or typing_extensions.get_origin(clean_type) == list + or clean_type == typing.List + ) + and isinstance(object_, typing.List) + ) + or ( + ( + typing_extensions.get_origin(clean_type) == typing.Set + or typing_extensions.get_origin(clean_type) == set + or clean_type == typing.Set + ) + and isinstance(object_, typing.Set) + ) + or ( + ( + typing_extensions.get_origin(clean_type) == typing.Sequence + or typing_extensions.get_origin(clean_type) == collections.abc.Sequence + or clean_type == typing.Sequence + ) + and isinstance(object_, typing.Sequence) + ) + ) + ): + inner_type = typing_extensions.get_args(clean_type)[0] + return [ + convert_and_respect_annotation_metadata(object_=item, annotation=annotation, inner_type=inner_type) + for item in object_ + ] + + if typing_extensions.get_origin(clean_type) == typing.Union: + # We should be able to ~relatively~ safely try to convert keys against all + # member types in the union, the edge case here is if one member aliases a field + # of the same name to a different name from another member + # Or if another member aliases a field of the same name that another member does not. + for member in typing_extensions.get_args(clean_type): + object_ = convert_and_respect_annotation_metadata(object_=object_, annotation=annotation, inner_type=member) + return object_ + + annotated_type = _get_annotation(annotation) + if annotated_type is None: + return object_ + + # If the object is not a TypedDict, a Union, or other container (list, set, sequence, etc.) + # Then we can safely call it on the recursive conversion. + return object_ + + +def _convert_typeddict(object_: typing.Mapping[str, object], expected_type: typing.Any) -> typing.Mapping[str, object]: + converted_object: typing.Dict[str, object] = {} + annotations = typing_extensions.get_type_hints(expected_type, include_extras=True) + for key, value in object_.items(): + type_ = annotations.get(key) + if type_ is None: + converted_object[key] = value + else: + converted_object[_alias_key(key, type_)] = convert_and_respect_annotation_metadata( + object_=value, annotation=type_ + ) + return converted_object + + +def _get_annotation(type_: typing.Any) -> typing.Optional[typing.Any]: + maybe_annotated_type = typing_extensions.get_origin(type_) + if maybe_annotated_type is None: + return None + + if maybe_annotated_type == typing_extensions.NotRequired: + type_ = typing_extensions.get_args(type_)[0] + maybe_annotated_type = typing_extensions.get_origin(type_) + + if maybe_annotated_type == typing_extensions.Annotated: + return type_ + + return None + + +def _remove_annotations(type_: typing.Any) -> typing.Any: + maybe_annotated_type = typing_extensions.get_origin(type_) + if maybe_annotated_type is None: + return type_ + + if maybe_annotated_type == typing_extensions.NotRequired: + return _remove_annotations(typing_extensions.get_args(type_)[0]) + + if maybe_annotated_type == typing_extensions.Annotated: + return _remove_annotations(typing_extensions.get_args(type_)[0]) + + return type_ + + +def _alias_key(key: str, type_: typing.Any) -> str: + maybe_annotated_type = _get_annotation(type_) + + if maybe_annotated_type is not None: + # The actual annotations are 1 onward, the first is the annotated type + annotations = typing_extensions.get_args(maybe_annotated_type)[1:] + + for annotation in annotations: + if isinstance(annotation, FieldMetadata) and annotation.alias is not None: + return annotation.alias + + return key diff --git a/seed/python-sdk/response-property/src/seed/py.typed b/seed/python-sdk/response-property/src/seed/py.typed new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/python-sdk/response-property/src/seed/service/__init__.py b/seed/python-sdk/response-property/src/seed/service/__init__.py new file mode 100644 index 00000000000..dd885d02ab5 --- /dev/null +++ b/seed/python-sdk/response-property/src/seed/service/__init__.py @@ -0,0 +1,5 @@ +# This file was auto-generated by Fern from our API Definition. + +from .types import Movie, OptionalWithDocs, Response, WithDocs + +__all__ = ["Movie", "OptionalWithDocs", "Response", "WithDocs"] diff --git a/seed/python-sdk/response-property/src/seed/service/client.py b/seed/python-sdk/response-property/src/seed/service/client.py new file mode 100644 index 00000000000..645bf18eb5e --- /dev/null +++ b/seed/python-sdk/response-property/src/seed/service/client.py @@ -0,0 +1,605 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from ..types.optional_string_response import OptionalStringResponse +from ..types.string_response import StringResponse +from .types.movie import Movie +from .types.optional_with_docs import OptionalWithDocs +from .types.response import Response + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class ServiceClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def get_movie(self, *, request: str, request_options: typing.Optional[RequestOptions] = None) -> Movie: + """ + Parameters + ---------- + request : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Movie + + Examples + -------- + from seed import SeedResponseProperty + + client = SeedResponseProperty( + base_url="https://yourhost.com/path/to/api", + ) + client.service.get_movie( + request="string", + ) + """ + _response = self._client_wrapper.httpx_client.request( + "movie", method="POST", json=request, request_options=request_options, omit=OMIT + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast(Response, parse_obj_as(type_=Response, object_=_response.json())) # type: ignore + + return _parsed_response.data + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + + def get_movie_docs(self, *, request: str, request_options: typing.Optional[RequestOptions] = None) -> str: + """ + Parameters + ---------- + request : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + str + + Examples + -------- + from seed import SeedResponseProperty + + client = SeedResponseProperty( + base_url="https://yourhost.com/path/to/api", + ) + client.service.get_movie_docs( + request="string", + ) + """ + _response = self._client_wrapper.httpx_client.request( + "movie", method="POST", json=request, request_options=request_options, omit=OMIT + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast(Response, parse_obj_as(type_=Response, object_=_response.json())) # type: ignore + + return _parsed_response.docs + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + + def get_movie_name(self, *, request: str, request_options: typing.Optional[RequestOptions] = None) -> str: + """ + Parameters + ---------- + request : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + str + + Examples + -------- + from seed import SeedResponseProperty + + client = SeedResponseProperty( + base_url="https://yourhost.com/path/to/api", + ) + client.service.get_movie_name( + request="string", + ) + """ + _response = self._client_wrapper.httpx_client.request( + "movie", method="POST", json=request, request_options=request_options, omit=OMIT + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast(StringResponse, parse_obj_as(type_=StringResponse, object_=_response.json())) # type: ignore + + return _parsed_response.data + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + + def get_movie_metadata( + self, *, request: str, request_options: typing.Optional[RequestOptions] = None + ) -> typing.Dict[str, str]: + """ + Parameters + ---------- + request : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.Dict[str, str] + + Examples + -------- + from seed import SeedResponseProperty + + client = SeedResponseProperty( + base_url="https://yourhost.com/path/to/api", + ) + client.service.get_movie_metadata( + request="string", + ) + """ + _response = self._client_wrapper.httpx_client.request( + "movie", method="POST", json=request, request_options=request_options, omit=OMIT + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast(Response, parse_obj_as(type_=Response, object_=_response.json())) # type: ignore + + return _parsed_response.metadata + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + + def get_optional_movie( + self, *, request: str, request_options: typing.Optional[RequestOptions] = None + ) -> typing.Optional[Movie]: + """ + Parameters + ---------- + request : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.Optional[Movie] + + Examples + -------- + from seed import SeedResponseProperty + + client = SeedResponseProperty( + base_url="https://yourhost.com/path/to/api", + ) + client.service.get_optional_movie( + request="string", + ) + """ + _response = self._client_wrapper.httpx_client.request( + "movie", method="POST", json=request, request_options=request_options, omit=OMIT + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast(typing.Optional[Response], parse_obj_as(type_=typing.Optional[Response], object_=_response.json())) # type: ignore + + return _parsed_response.data if _parsed_response is not None else _parsed_response + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + + def get_optional_movie_docs(self, *, request: str, request_options: typing.Optional[RequestOptions] = None) -> str: + """ + Parameters + ---------- + request : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + str + + Examples + -------- + from seed import SeedResponseProperty + + client = SeedResponseProperty( + base_url="https://yourhost.com/path/to/api", + ) + client.service.get_optional_movie_docs( + request="string", + ) + """ + _response = self._client_wrapper.httpx_client.request( + "movie", method="POST", json=request, request_options=request_options, omit=OMIT + ) + try: + if 200 <= _response.status_code < 300: + return typing.cast(OptionalWithDocs, parse_obj_as(type_=OptionalWithDocs, object_=_response.json())) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + + def get_optional_movie_name(self, *, request: str, request_options: typing.Optional[RequestOptions] = None) -> str: + """ + Parameters + ---------- + request : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + str + + Examples + -------- + from seed import SeedResponseProperty + + client = SeedResponseProperty( + base_url="https://yourhost.com/path/to/api", + ) + client.service.get_optional_movie_name( + request="string", + ) + """ + _response = self._client_wrapper.httpx_client.request( + "movie", method="POST", json=request, request_options=request_options, omit=OMIT + ) + try: + if 200 <= _response.status_code < 300: + return typing.cast(OptionalStringResponse, parse_obj_as(type_=OptionalStringResponse, object_=_response.json())) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + + +class AsyncServiceClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def get_movie(self, *, request: str, request_options: typing.Optional[RequestOptions] = None) -> Movie: + """ + Parameters + ---------- + request : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + Movie + + Examples + -------- + import asyncio + + from seed import AsyncSeedResponseProperty + + client = AsyncSeedResponseProperty( + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.service.get_movie( + request="string", + ) + + + asyncio.run(main()) + """ + _response = await self._client_wrapper.httpx_client.request( + "movie", method="POST", json=request, request_options=request_options, omit=OMIT + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast(Response, parse_obj_as(type_=Response, object_=_response.json())) # type: ignore + + return _parsed_response.data + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + + async def get_movie_docs(self, *, request: str, request_options: typing.Optional[RequestOptions] = None) -> str: + """ + Parameters + ---------- + request : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + str + + Examples + -------- + import asyncio + + from seed import AsyncSeedResponseProperty + + client = AsyncSeedResponseProperty( + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.service.get_movie_docs( + request="string", + ) + + + asyncio.run(main()) + """ + _response = await self._client_wrapper.httpx_client.request( + "movie", method="POST", json=request, request_options=request_options, omit=OMIT + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast(Response, parse_obj_as(type_=Response, object_=_response.json())) # type: ignore + + return _parsed_response.docs + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + + async def get_movie_name(self, *, request: str, request_options: typing.Optional[RequestOptions] = None) -> str: + """ + Parameters + ---------- + request : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + str + + Examples + -------- + import asyncio + + from seed import AsyncSeedResponseProperty + + client = AsyncSeedResponseProperty( + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.service.get_movie_name( + request="string", + ) + + + asyncio.run(main()) + """ + _response = await self._client_wrapper.httpx_client.request( + "movie", method="POST", json=request, request_options=request_options, omit=OMIT + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast(StringResponse, parse_obj_as(type_=StringResponse, object_=_response.json())) # type: ignore + + return _parsed_response.data + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + + async def get_movie_metadata( + self, *, request: str, request_options: typing.Optional[RequestOptions] = None + ) -> typing.Dict[str, str]: + """ + Parameters + ---------- + request : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.Dict[str, str] + + Examples + -------- + import asyncio + + from seed import AsyncSeedResponseProperty + + client = AsyncSeedResponseProperty( + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.service.get_movie_metadata( + request="string", + ) + + + asyncio.run(main()) + """ + _response = await self._client_wrapper.httpx_client.request( + "movie", method="POST", json=request, request_options=request_options, omit=OMIT + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast(Response, parse_obj_as(type_=Response, object_=_response.json())) # type: ignore + + return _parsed_response.metadata + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + + async def get_optional_movie( + self, *, request: str, request_options: typing.Optional[RequestOptions] = None + ) -> typing.Optional[Movie]: + """ + Parameters + ---------- + request : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + typing.Optional[Movie] + + Examples + -------- + import asyncio + + from seed import AsyncSeedResponseProperty + + client = AsyncSeedResponseProperty( + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.service.get_optional_movie( + request="string", + ) + + + asyncio.run(main()) + """ + _response = await self._client_wrapper.httpx_client.request( + "movie", method="POST", json=request, request_options=request_options, omit=OMIT + ) + try: + if 200 <= _response.status_code < 300: + _parsed_response = typing.cast(typing.Optional[Response], parse_obj_as(type_=typing.Optional[Response], object_=_response.json())) # type: ignore + + return _parsed_response.data if _parsed_response is not None else _parsed_response + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + + async def get_optional_movie_docs( + self, *, request: str, request_options: typing.Optional[RequestOptions] = None + ) -> str: + """ + Parameters + ---------- + request : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + str + + Examples + -------- + import asyncio + + from seed import AsyncSeedResponseProperty + + client = AsyncSeedResponseProperty( + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.service.get_optional_movie_docs( + request="string", + ) + + + asyncio.run(main()) + """ + _response = await self._client_wrapper.httpx_client.request( + "movie", method="POST", json=request, request_options=request_options, omit=OMIT + ) + try: + if 200 <= _response.status_code < 300: + return typing.cast(OptionalWithDocs, parse_obj_as(type_=OptionalWithDocs, object_=_response.json())) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) + + async def get_optional_movie_name( + self, *, request: str, request_options: typing.Optional[RequestOptions] = None + ) -> str: + """ + Parameters + ---------- + request : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + str + + Examples + -------- + import asyncio + + from seed import AsyncSeedResponseProperty + + client = AsyncSeedResponseProperty( + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.service.get_optional_movie_name( + request="string", + ) + + + asyncio.run(main()) + """ + _response = await self._client_wrapper.httpx_client.request( + "movie", method="POST", json=request, request_options=request_options, omit=OMIT + ) + try: + if 200 <= _response.status_code < 300: + return typing.cast(OptionalStringResponse, parse_obj_as(type_=OptionalStringResponse, object_=_response.json())) # type: ignore + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, body=_response.text) + raise ApiError(status_code=_response.status_code, body=_response_json) diff --git a/seed/python-sdk/response-property/src/seed/service/types/__init__.py b/seed/python-sdk/response-property/src/seed/service/types/__init__.py new file mode 100644 index 00000000000..c856a2a230c --- /dev/null +++ b/seed/python-sdk/response-property/src/seed/service/types/__init__.py @@ -0,0 +1,8 @@ +# This file was auto-generated by Fern from our API Definition. + +from .movie import Movie +from .optional_with_docs import OptionalWithDocs +from .response import Response +from .with_docs import WithDocs + +__all__ = ["Movie", "OptionalWithDocs", "Response", "WithDocs"] diff --git a/seed/python-sdk/response-property/src/seed/service/types/movie.py b/seed/python-sdk/response-property/src/seed/service/types/movie.py new file mode 100644 index 00000000000..c6fc813b7d5 --- /dev/null +++ b/seed/python-sdk/response-property/src/seed/service/types/movie.py @@ -0,0 +1,21 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic + +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class Movie(UniversalBaseModel): + id: str + name: str + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/seed/python-sdk/response-property/src/seed/service/types/optional_with_docs.py b/seed/python-sdk/response-property/src/seed/service/types/optional_with_docs.py new file mode 100644 index 00000000000..66a04dd87ae --- /dev/null +++ b/seed/python-sdk/response-property/src/seed/service/types/optional_with_docs.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from .with_docs import WithDocs + +OptionalWithDocs = typing.Optional[WithDocs] diff --git a/seed/python-sdk/response-property/src/seed/service/types/response.py b/seed/python-sdk/response-property/src/seed/service/types/response.py new file mode 100644 index 00000000000..4a2c3672cee --- /dev/null +++ b/seed/python-sdk/response-property/src/seed/service/types/response.py @@ -0,0 +1,23 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic + +from ...core.pydantic_utilities import IS_PYDANTIC_V2 +from ...types.with_metadata import WithMetadata +from .movie import Movie +from .with_docs import WithDocs + + +class Response(WithMetadata, WithDocs): + data: Movie + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/seed/python-sdk/response-property/src/seed/service/types/with_docs.py b/seed/python-sdk/response-property/src/seed/service/types/with_docs.py new file mode 100644 index 00000000000..07945cf00c3 --- /dev/null +++ b/seed/python-sdk/response-property/src/seed/service/types/with_docs.py @@ -0,0 +1,20 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic + +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class WithDocs(UniversalBaseModel): + docs: str + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/seed/python-sdk/response-property/src/seed/types/__init__.py b/seed/python-sdk/response-property/src/seed/types/__init__.py new file mode 100644 index 00000000000..218937165a0 --- /dev/null +++ b/seed/python-sdk/response-property/src/seed/types/__init__.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +from .optional_string_response import OptionalStringResponse +from .string_response import StringResponse +from .with_metadata import WithMetadata + +__all__ = ["OptionalStringResponse", "StringResponse", "WithMetadata"] diff --git a/seed/python-sdk/response-property/src/seed/types/optional_string_response.py b/seed/python-sdk/response-property/src/seed/types/optional_string_response.py new file mode 100644 index 00000000000..587d8cb4304 --- /dev/null +++ b/seed/python-sdk/response-property/src/seed/types/optional_string_response.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from .string_response import StringResponse + +OptionalStringResponse = typing.Optional[StringResponse] diff --git a/seed/python-sdk/response-property/src/seed/types/string_response.py b/seed/python-sdk/response-property/src/seed/types/string_response.py new file mode 100644 index 00000000000..e7a1a52ab60 --- /dev/null +++ b/seed/python-sdk/response-property/src/seed/types/string_response.py @@ -0,0 +1,20 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic + +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class StringResponse(UniversalBaseModel): + data: str + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/seed/python-sdk/response-property/src/seed/types/with_metadata.py b/seed/python-sdk/response-property/src/seed/types/with_metadata.py new file mode 100644 index 00000000000..8e35717387b --- /dev/null +++ b/seed/python-sdk/response-property/src/seed/types/with_metadata.py @@ -0,0 +1,20 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic + +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class WithMetadata(UniversalBaseModel): + metadata: typing.Dict[str, str] + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/seed/python-sdk/response-property/src/seed/version.py b/seed/python-sdk/response-property/src/seed/version.py new file mode 100644 index 00000000000..f3da3e93291 --- /dev/null +++ b/seed/python-sdk/response-property/src/seed/version.py @@ -0,0 +1,4 @@ + +from importlib import metadata + +__version__ = metadata.version("fern_response-property") diff --git a/seed/python-sdk/response-property/tests/__init__.py b/seed/python-sdk/response-property/tests/__init__.py new file mode 100644 index 00000000000..f3ea2659bb1 --- /dev/null +++ b/seed/python-sdk/response-property/tests/__init__.py @@ -0,0 +1,2 @@ +# This file was auto-generated by Fern from our API Definition. + diff --git a/seed/python-sdk/response-property/tests/conftest.py b/seed/python-sdk/response-property/tests/conftest.py new file mode 100644 index 00000000000..86df378ef4f --- /dev/null +++ b/seed/python-sdk/response-property/tests/conftest.py @@ -0,0 +1,16 @@ +# This file was auto-generated by Fern from our API Definition. + +import os + +import pytest +from seed import AsyncSeedResponseProperty, SeedResponseProperty + + +@pytest.fixture +def client() -> SeedResponseProperty: + return SeedResponseProperty(base_url=os.getenv("TESTS_BASE_URL", "base_url")) + + +@pytest.fixture +def async_client() -> AsyncSeedResponseProperty: + return AsyncSeedResponseProperty(base_url=os.getenv("TESTS_BASE_URL", "base_url")) diff --git a/seed/python-sdk/response-property/tests/custom/test_client.py b/seed/python-sdk/response-property/tests/custom/test_client.py new file mode 100644 index 00000000000..60a58e64c27 --- /dev/null +++ b/seed/python-sdk/response-property/tests/custom/test_client.py @@ -0,0 +1,6 @@ +import pytest + +# Get started with writing tests with pytest at https://docs.pytest.org +@pytest.mark.skip(reason="Unimplemented") +def test_client() -> None: + assert True == True diff --git a/seed/python-sdk/response-property/tests/utilities.py b/seed/python-sdk/response-property/tests/utilities.py new file mode 100644 index 00000000000..13da208eb3a --- /dev/null +++ b/seed/python-sdk/response-property/tests/utilities.py @@ -0,0 +1,138 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +import uuid + +import pydantic +from dateutil import parser + + +def cast_field(json_expectation: typing.Any, type_expectation: typing.Any) -> typing.Any: + # Cast these specific types which come through as string and expect our + # models to cast to the correct type. + if type_expectation == "uuid": + return uuid.UUID(json_expectation) + elif type_expectation == "date": + return parser.parse(json_expectation).date() + elif type_expectation == "datetime": + return parser.parse(json_expectation) + elif type_expectation == "set": + return set(json_expectation) + elif type_expectation == "integer": + # Necessary as we allow numeric keys, but JSON makes them strings + return int(json_expectation) + + return json_expectation + + +def validate_field(response: typing.Any, json_expectation: typing.Any, type_expectation: typing.Any) -> None: + # Allow for an escape hatch if the object cannot be validated + if type_expectation == "no_validate": + return + + is_container_of_complex_type = False + # Parse types in containers, note that dicts are handled within `validate_response` + if isinstance(json_expectation, list): + if isinstance(type_expectation, tuple): + container_expectation = type_expectation[0] + contents_expectation = type_expectation[1] + + cast_json_expectation = [] + for idx, ex in enumerate(json_expectation): + if isinstance(contents_expectation, dict): + entry_expectation = contents_expectation.get(idx) + if isinstance(entry_expectation, dict): + is_container_of_complex_type = True + validate_response( + response=response[idx], json_expectation=ex, type_expectations=entry_expectation + ) + else: + cast_json_expectation.append(cast_field(ex, entry_expectation)) + else: + cast_json_expectation.append(ex) + json_expectation = cast_json_expectation + + # Note that we explicitly do not allow for sets of pydantic models as they are not hashable, so + # if any of the values of the set have a type_expectation of a dict, we're assuming it's a pydantic + # model and keeping it a list. + if container_expectation != "set" or not any( + map(lambda value: isinstance(value, dict), list(contents_expectation.values())) + ): + json_expectation = cast_field(json_expectation, container_expectation) + elif isinstance(type_expectation, tuple): + container_expectation = type_expectation[0] + contents_expectation = type_expectation[1] + if isinstance(contents_expectation, dict): + json_expectation = { + cast_field( + key, contents_expectation.get(idx)[0] if contents_expectation.get(idx) is not None else None # type: ignore + ): cast_field( + value, contents_expectation.get(idx)[1] if contents_expectation.get(idx) is not None else None # type: ignore + ) + for idx, (key, value) in enumerate(json_expectation.items()) + } + else: + json_expectation = cast_field(json_expectation, container_expectation) + elif type_expectation is not None: + json_expectation = cast_field(json_expectation, type_expectation) + + # When dealing with containers of models, etc. we're validating them implicitly, so no need to check the resultant list + if not is_container_of_complex_type: + assert ( + json_expectation == response + ), "Primitives found, expected: {0} (type: {1}), Actual: {2} (type: {3})".format( + json_expectation, type(json_expectation), response, type(response) + ) + + +# Arg type_expectations is a deeply nested structure that matches the response, but with the values replaced with the expected types +def validate_response(response: typing.Any, json_expectation: typing.Any, type_expectations: typing.Any) -> None: + # Allow for an escape hatch if the object cannot be validated + if type_expectations == "no_validate": + return + + if ( + not isinstance(response, list) + and not isinstance(response, dict) + and not issubclass(type(response), pydantic.BaseModel) + ): + validate_field(response=response, json_expectation=json_expectation, type_expectation=type_expectations) + return + + if isinstance(response, list): + assert len(response) == len(json_expectation), "Length mismatch, expected: {0}, Actual: {1}".format( + len(response), len(json_expectation) + ) + content_expectation = type_expectations + if isinstance(type_expectations, tuple): + content_expectation = type_expectations[1] + for idx, item in enumerate(response): + validate_response( + response=item, json_expectation=json_expectation[idx], type_expectations=content_expectation[idx] + ) + else: + response_json = response + if issubclass(type(response), pydantic.BaseModel): + response_json = response.dict(by_alias=True) + + for key, value in json_expectation.items(): + assert key in response_json, "Field {0} not found within the response object: {1}".format( + key, response_json + ) + + type_expectation = None + if type_expectations is not None and isinstance(type_expectations, dict): + type_expectation = type_expectations.get(key) + + # If your type_expectation is a tuple then you have a container field, process it as such + # Otherwise, we're just validating a single field that's a pydantic model. + if isinstance(value, dict) and not isinstance(type_expectation, tuple): + validate_response( + response=response_json[key], json_expectation=value, type_expectations=type_expectation + ) + else: + validate_field(response=response_json[key], json_expectation=value, type_expectation=type_expectation) + + # Ensure there are no additional fields here either + del response_json[key] + assert len(response_json) == 0, "Additional fields found, expected None: {0}".format(response_json) diff --git a/seed/python-sdk/response-property/tests/utils/__init__.py b/seed/python-sdk/response-property/tests/utils/__init__.py new file mode 100644 index 00000000000..f3ea2659bb1 --- /dev/null +++ b/seed/python-sdk/response-property/tests/utils/__init__.py @@ -0,0 +1,2 @@ +# This file was auto-generated by Fern from our API Definition. + diff --git a/seed/python-sdk/response-property/tests/utils/assets/models/__init__.py b/seed/python-sdk/response-property/tests/utils/assets/models/__init__.py new file mode 100644 index 00000000000..2cf01263529 --- /dev/null +++ b/seed/python-sdk/response-property/tests/utils/assets/models/__init__.py @@ -0,0 +1,21 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +from .circle import CircleParams +from .object_with_defaults import ObjectWithDefaultsParams +from .object_with_optional_field import ObjectWithOptionalFieldParams +from .shape import Shape_CircleParams, Shape_SquareParams, ShapeParams +from .square import SquareParams +from .undiscriminated_shape import UndiscriminatedShapeParams + +__all__ = [ + "CircleParams", + "ObjectWithDefaultsParams", + "ObjectWithOptionalFieldParams", + "ShapeParams", + "Shape_CircleParams", + "Shape_SquareParams", + "SquareParams", + "UndiscriminatedShapeParams", +] diff --git a/seed/python-sdk/response-property/tests/utils/assets/models/circle.py b/seed/python-sdk/response-property/tests/utils/assets/models/circle.py new file mode 100644 index 00000000000..af7a1bf8a8e --- /dev/null +++ b/seed/python-sdk/response-property/tests/utils/assets/models/circle.py @@ -0,0 +1,10 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +import typing_extensions +from seed.core.serialization import FieldMetadata + + +class CircleParams(typing_extensions.TypedDict): + radius_measurement: typing_extensions.Annotated[float, FieldMetadata(alias="radiusMeasurement")] diff --git a/seed/python-sdk/response-property/tests/utils/assets/models/color.py b/seed/python-sdk/response-property/tests/utils/assets/models/color.py new file mode 100644 index 00000000000..2aa2c4c52f0 --- /dev/null +++ b/seed/python-sdk/response-property/tests/utils/assets/models/color.py @@ -0,0 +1,7 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +import typing + +Color = typing.Union[typing.Literal["red", "blue"], typing.Any] diff --git a/seed/python-sdk/response-property/tests/utils/assets/models/object_with_defaults.py b/seed/python-sdk/response-property/tests/utils/assets/models/object_with_defaults.py new file mode 100644 index 00000000000..a977b1d2aa1 --- /dev/null +++ b/seed/python-sdk/response-property/tests/utils/assets/models/object_with_defaults.py @@ -0,0 +1,15 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +import typing_extensions + + +class ObjectWithDefaultsParams(typing_extensions.TypedDict): + """ + Defines properties with default values and validation rules. + """ + + decimal: typing_extensions.NotRequired[float] + string: typing_extensions.NotRequired[str] + required_string: str diff --git a/seed/python-sdk/response-property/tests/utils/assets/models/object_with_optional_field.py b/seed/python-sdk/response-property/tests/utils/assets/models/object_with_optional_field.py new file mode 100644 index 00000000000..3ad93d5f305 --- /dev/null +++ b/seed/python-sdk/response-property/tests/utils/assets/models/object_with_optional_field.py @@ -0,0 +1,35 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing +import uuid + +import typing_extensions +from seed.core.serialization import FieldMetadata + +from .color import Color +from .shape import ShapeParams +from .undiscriminated_shape import UndiscriminatedShapeParams + + +class ObjectWithOptionalFieldParams(typing_extensions.TypedDict): + literal: typing.Literal["lit_one"] + string: typing_extensions.NotRequired[str] + integer: typing_extensions.NotRequired[int] + long_: typing_extensions.NotRequired[typing_extensions.Annotated[int, FieldMetadata(alias="long")]] + double: typing_extensions.NotRequired[float] + bool_: typing_extensions.NotRequired[typing_extensions.Annotated[bool, FieldMetadata(alias="bool")]] + datetime: typing_extensions.NotRequired[dt.datetime] + date: typing_extensions.NotRequired[dt.date] + uuid_: typing_extensions.NotRequired[typing_extensions.Annotated[uuid.UUID, FieldMetadata(alias="uuid")]] + base_64: typing_extensions.NotRequired[typing_extensions.Annotated[str, FieldMetadata(alias="base64")]] + list_: typing_extensions.NotRequired[typing_extensions.Annotated[typing.Sequence[str], FieldMetadata(alias="list")]] + set_: typing_extensions.NotRequired[typing_extensions.Annotated[typing.Set[str], FieldMetadata(alias="set")]] + map_: typing_extensions.NotRequired[typing_extensions.Annotated[typing.Dict[int, str], FieldMetadata(alias="map")]] + enum: typing_extensions.NotRequired[Color] + union: typing_extensions.NotRequired[ShapeParams] + second_union: typing_extensions.NotRequired[ShapeParams] + undiscriminated_union: typing_extensions.NotRequired[UndiscriminatedShapeParams] + any: typing.Any diff --git a/seed/python-sdk/response-property/tests/utils/assets/models/shape.py b/seed/python-sdk/response-property/tests/utils/assets/models/shape.py new file mode 100644 index 00000000000..2c33c877951 --- /dev/null +++ b/seed/python-sdk/response-property/tests/utils/assets/models/shape.py @@ -0,0 +1,27 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +import typing_extensions +from seed.core.serialization import FieldMetadata + + +class Base(typing_extensions.TypedDict): + id: str + + +class Shape_CircleParams(Base): + shape_type: typing_extensions.Annotated[typing.Literal["circle"], FieldMetadata(alias="shapeType")] + radius_measurement: typing_extensions.Annotated[float, FieldMetadata(alias="radiusMeasurement")] + + +class Shape_SquareParams(Base): + shape_type: typing_extensions.Annotated[typing.Literal["square"], FieldMetadata(alias="shapeType")] + length_measurement: typing_extensions.Annotated[float, FieldMetadata(alias="lengthMeasurement")] + + +ShapeParams = typing.Union[Shape_CircleParams, Shape_SquareParams] diff --git a/seed/python-sdk/response-property/tests/utils/assets/models/square.py b/seed/python-sdk/response-property/tests/utils/assets/models/square.py new file mode 100644 index 00000000000..b9b7dd319bc --- /dev/null +++ b/seed/python-sdk/response-property/tests/utils/assets/models/square.py @@ -0,0 +1,10 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +import typing_extensions +from seed.core.serialization import FieldMetadata + + +class SquareParams(typing_extensions.TypedDict): + length_measurement: typing_extensions.Annotated[float, FieldMetadata(alias="lengthMeasurement")] diff --git a/seed/python-sdk/response-property/tests/utils/assets/models/undiscriminated_shape.py b/seed/python-sdk/response-property/tests/utils/assets/models/undiscriminated_shape.py new file mode 100644 index 00000000000..99f12b300d1 --- /dev/null +++ b/seed/python-sdk/response-property/tests/utils/assets/models/undiscriminated_shape.py @@ -0,0 +1,10 @@ +# This file was auto-generated by Fern from our API Definition. + +# This file was auto-generated by Fern from our API Definition. + +import typing + +from .circle import CircleParams +from .square import SquareParams + +UndiscriminatedShapeParams = typing.Union[CircleParams, SquareParams] diff --git a/seed/python-sdk/response-property/tests/utils/test_http_client.py b/seed/python-sdk/response-property/tests/utils/test_http_client.py new file mode 100644 index 00000000000..a541bae6531 --- /dev/null +++ b/seed/python-sdk/response-property/tests/utils/test_http_client.py @@ -0,0 +1,61 @@ +# This file was auto-generated by Fern from our API Definition. + +from seed.core.http_client import get_request_body +from seed.core.request_options import RequestOptions + + +def get_request_options() -> RequestOptions: + return {"additional_body_parameters": {"see you": "later"}} + + +def test_get_json_request_body() -> None: + json_body, data_body = get_request_body(json={"hello": "world"}, data=None, request_options=None, omit=None) + assert json_body == {"hello": "world"} + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={"goodbye": "world"}, data=None, request_options=get_request_options(), omit=None + ) + + assert json_body_extras == {"goodbye": "world", "see you": "later"} + assert data_body_extras is None + + +def test_get_files_request_body() -> None: + json_body, data_body = get_request_body(json=None, data={"hello": "world"}, request_options=None, omit=None) + assert data_body == {"hello": "world"} + assert json_body is None + + json_body_extras, data_body_extras = get_request_body( + json=None, data={"goodbye": "world"}, request_options=get_request_options(), omit=None + ) + + assert data_body_extras == {"goodbye": "world", "see you": "later"} + assert json_body_extras is None + + +def test_get_none_request_body() -> None: + json_body, data_body = get_request_body(json=None, data=None, request_options=None, omit=None) + assert data_body is None + assert json_body is None + + json_body_extras, data_body_extras = get_request_body( + json=None, data=None, request_options=get_request_options(), omit=None + ) + + assert json_body_extras == {"see you": "later"} + assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/response-property/tests/utils/test_query_encoding.py b/seed/python-sdk/response-property/tests/utils/test_query_encoding.py new file mode 100644 index 00000000000..247e1551b46 --- /dev/null +++ b/seed/python-sdk/response-property/tests/utils/test_query_encoding.py @@ -0,0 +1,18 @@ +# This file was auto-generated by Fern from our API Definition. + +from seed.core.query_encoder import encode_query + + +def test_query_encoding() -> None: + assert encode_query({"hello world": "hello world"}) == {"hello world": "hello world"} + assert encode_query({"hello_world": {"hello": "world"}}) == {"hello_world[hello]": "world"} + assert encode_query({"hello_world": {"hello": {"world": "today"}, "test": "this"}, "hi": "there"}) == { + "hello_world[hello][world]": "today", + "hello_world[test]": "this", + "hi": "there", + } + + +def test_encode_query_with_none() -> None: + encoded = encode_query(None) + assert encoded == None diff --git a/seed/python-sdk/response-property/tests/utils/test_serialization.py b/seed/python-sdk/response-property/tests/utils/test_serialization.py new file mode 100644 index 00000000000..58b1ed66e6d --- /dev/null +++ b/seed/python-sdk/response-property/tests/utils/test_serialization.py @@ -0,0 +1,66 @@ +# This file was auto-generated by Fern from our API Definition. + +from typing import Any, List + +from seed.core.serialization import convert_and_respect_annotation_metadata + +from .assets.models import ObjectWithOptionalFieldParams, ShapeParams + +UNION_TEST: ShapeParams = {"radius_measurement": 1.0, "shape_type": "circle", "id": "1"} +UNION_TEST_CONVERTED = {"shapeType": "circle", "radiusMeasurement": 1.0, "id": "1"} + + +def test_convert_and_respect_annotation_metadata() -> None: + data: ObjectWithOptionalFieldParams = { + "string": "string", + "long_": 12345, + "bool_": True, + "literal": "lit_one", + "any": "any", + } + converted = convert_and_respect_annotation_metadata(object_=data, annotation=ObjectWithOptionalFieldParams) + assert converted == {"string": "string", "long": 12345, "bool": True, "literal": "lit_one", "any": "any"} + + +def test_convert_and_respect_annotation_metadata_in_list() -> None: + data: List[ObjectWithOptionalFieldParams] = [ + {"string": "string", "long_": 12345, "bool_": True, "literal": "lit_one", "any": "any"}, + {"string": "another string", "long_": 67890, "list_": [], "literal": "lit_one", "any": "any"}, + ] + converted = convert_and_respect_annotation_metadata(object_=data, annotation=List[ObjectWithOptionalFieldParams]) + + assert converted == [ + {"string": "string", "long": 12345, "bool": True, "literal": "lit_one", "any": "any"}, + {"string": "another string", "long": 67890, "list": [], "literal": "lit_one", "any": "any"}, + ] + + +def test_convert_and_respect_annotation_metadata_in_nested_object() -> None: + data: ObjectWithOptionalFieldParams = { + "string": "string", + "long_": 12345, + "union": UNION_TEST, + "literal": "lit_one", + "any": "any", + } + converted = convert_and_respect_annotation_metadata(object_=data, annotation=ObjectWithOptionalFieldParams) + + assert converted == { + "string": "string", + "long": 12345, + "union": UNION_TEST_CONVERTED, + "literal": "lit_one", + "any": "any", + } + + +def test_convert_and_respect_annotation_metadata_in_union() -> None: + converted = convert_and_respect_annotation_metadata(object_=UNION_TEST, annotation=ShapeParams) + + assert converted == UNION_TEST_CONVERTED + + +def test_convert_and_respect_annotation_metadata_with_empty_object() -> None: + data: Any = {} + converted = convert_and_respect_annotation_metadata(object_=data, annotation=ShapeParams) + assert converted == data diff --git a/seed/python-sdk/seed.yml b/seed/python-sdk/seed.yml index 79169f96257..916e25a9394 100644 --- a/seed/python-sdk/seed.yml +++ b/seed/python-sdk/seed.yml @@ -177,15 +177,17 @@ allowedFailures: - exhaustive:pydantic-v1-wrapped # TODO(FER-1837): Address this list - - response-property - websocket - exhaustive:union-utils - extra-properties - trace # Remove the following fixtures soon as a CLI is released with - # default enum value support and the latest primitive types. - # This causes a 'fern check' issue when 'fern test' is run. + # default enum value support, the latest primitive types, and gRPC + # configuration. This otherwise causes a 'fern check' issue when + # 'fern test' is run. + - grpc + - grpc-proto - object - validation:no-custom-config - validation:with-defaults diff --git a/seed/python-sdk/server-sent-events/src/seed/core/http_client.py b/seed/python-sdk/server-sent-events/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/server-sent-events/src/seed/core/http_client.py +++ b/seed/python-sdk/server-sent-events/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/server-sent-events/tests/utils/test_http_client.py b/seed/python-sdk/server-sent-events/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/server-sent-events/tests/utils/test_http_client.py +++ b/seed/python-sdk/server-sent-events/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/single-url-environment-default/src/seed/core/http_client.py b/seed/python-sdk/single-url-environment-default/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/single-url-environment-default/src/seed/core/http_client.py +++ b/seed/python-sdk/single-url-environment-default/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/single-url-environment-default/tests/utils/test_http_client.py b/seed/python-sdk/single-url-environment-default/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/single-url-environment-default/tests/utils/test_http_client.py +++ b/seed/python-sdk/single-url-environment-default/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/single-url-environment-no-default/src/seed/core/http_client.py b/seed/python-sdk/single-url-environment-no-default/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/single-url-environment-no-default/src/seed/core/http_client.py +++ b/seed/python-sdk/single-url-environment-no-default/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/single-url-environment-no-default/tests/utils/test_http_client.py b/seed/python-sdk/single-url-environment-no-default/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/single-url-environment-no-default/tests/utils/test_http_client.py +++ b/seed/python-sdk/single-url-environment-no-default/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/streaming-parameter/src/seed/core/http_client.py b/seed/python-sdk/streaming-parameter/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/streaming-parameter/src/seed/core/http_client.py +++ b/seed/python-sdk/streaming-parameter/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/streaming-parameter/tests/utils/test_http_client.py b/seed/python-sdk/streaming-parameter/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/streaming-parameter/tests/utils/test_http_client.py +++ b/seed/python-sdk/streaming-parameter/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/streaming/no-custom-config/src/seed/core/http_client.py b/seed/python-sdk/streaming/no-custom-config/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/streaming/no-custom-config/src/seed/core/http_client.py +++ b/seed/python-sdk/streaming/no-custom-config/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/streaming/no-custom-config/tests/utils/test_http_client.py b/seed/python-sdk/streaming/no-custom-config/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/streaming/no-custom-config/tests/utils/test_http_client.py +++ b/seed/python-sdk/streaming/no-custom-config/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/streaming/skip-pydantic-validation/src/seed/core/http_client.py b/seed/python-sdk/streaming/skip-pydantic-validation/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/streaming/skip-pydantic-validation/src/seed/core/http_client.py +++ b/seed/python-sdk/streaming/skip-pydantic-validation/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/streaming/skip-pydantic-validation/tests/utils/test_http_client.py b/seed/python-sdk/streaming/skip-pydantic-validation/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/streaming/skip-pydantic-validation/tests/utils/test_http_client.py +++ b/seed/python-sdk/streaming/skip-pydantic-validation/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/undiscriminated-unions/src/seed/core/http_client.py b/seed/python-sdk/undiscriminated-unions/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/undiscriminated-unions/src/seed/core/http_client.py +++ b/seed/python-sdk/undiscriminated-unions/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/undiscriminated-unions/tests/utils/test_http_client.py b/seed/python-sdk/undiscriminated-unions/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/undiscriminated-unions/tests/utils/test_http_client.py +++ b/seed/python-sdk/undiscriminated-unions/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/unions/no-custom-config/src/seed/core/http_client.py b/seed/python-sdk/unions/no-custom-config/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/unions/no-custom-config/src/seed/core/http_client.py +++ b/seed/python-sdk/unions/no-custom-config/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/unions/no-custom-config/tests/utils/test_http_client.py b/seed/python-sdk/unions/no-custom-config/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/unions/no-custom-config/tests/utils/test_http_client.py +++ b/seed/python-sdk/unions/no-custom-config/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/unions/union-utils/src/seed/core/http_client.py b/seed/python-sdk/unions/union-utils/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/unions/union-utils/src/seed/core/http_client.py +++ b/seed/python-sdk/unions/union-utils/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/unions/union-utils/tests/utils/test_http_client.py b/seed/python-sdk/unions/union-utils/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/unions/union-utils/tests/utils/test_http_client.py +++ b/seed/python-sdk/unions/union-utils/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/unknown/src/seed/core/http_client.py b/seed/python-sdk/unknown/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/unknown/src/seed/core/http_client.py +++ b/seed/python-sdk/unknown/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/unknown/tests/utils/test_http_client.py b/seed/python-sdk/unknown/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/unknown/tests/utils/test_http_client.py +++ b/seed/python-sdk/unknown/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/validation/no-custom-config/src/seed/core/http_client.py b/seed/python-sdk/validation/no-custom-config/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/validation/no-custom-config/src/seed/core/http_client.py +++ b/seed/python-sdk/validation/no-custom-config/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/validation/no-custom-config/tests/utils/test_http_client.py b/seed/python-sdk/validation/no-custom-config/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/validation/no-custom-config/tests/utils/test_http_client.py +++ b/seed/python-sdk/validation/no-custom-config/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/validation/with-defaults/src/seed/core/http_client.py b/seed/python-sdk/validation/with-defaults/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/validation/with-defaults/src/seed/core/http_client.py +++ b/seed/python-sdk/validation/with-defaults/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/validation/with-defaults/tests/utils/test_http_client.py b/seed/python-sdk/validation/with-defaults/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/validation/with-defaults/tests/utils/test_http_client.py +++ b/seed/python-sdk/validation/with-defaults/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/variables/src/seed/core/http_client.py b/seed/python-sdk/variables/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/variables/src/seed/core/http_client.py +++ b/seed/python-sdk/variables/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/variables/tests/utils/test_http_client.py b/seed/python-sdk/variables/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/variables/tests/utils/test_http_client.py +++ b/seed/python-sdk/variables/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/version-no-default/src/seed/core/http_client.py b/seed/python-sdk/version-no-default/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/version-no-default/src/seed/core/http_client.py +++ b/seed/python-sdk/version-no-default/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/version-no-default/tests/utils/test_http_client.py b/seed/python-sdk/version-no-default/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/version-no-default/tests/utils/test_http_client.py +++ b/seed/python-sdk/version-no-default/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/version/src/seed/core/http_client.py b/seed/python-sdk/version/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/version/src/seed/core/http_client.py +++ b/seed/python-sdk/version/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/version/tests/utils/test_http_client.py b/seed/python-sdk/version/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/version/tests/utils/test_http_client.py +++ b/seed/python-sdk/version/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/python-sdk/websocket/src/seed/core/http_client.py b/seed/python-sdk/websocket/src/seed/core/http_client.py index 9333d8a7f15..356880bbc3e 100644 --- a/seed/python-sdk/websocket/src/seed/core/http_client.py +++ b/seed/python-sdk/websocket/src/seed/core/http_client.py @@ -142,7 +142,8 @@ def get_request_body( # If both data and json are None, we send json data in the event extra properties are specified json_body = maybe_filter_request_body(json, request_options, omit) - return json_body, data_body + # If you have an empty JSON body, you should just send None + return (json_body if json_body != {} else None), data_body if data_body != {} else None class HttpClient: diff --git a/seed/python-sdk/websocket/tests/utils/test_http_client.py b/seed/python-sdk/websocket/tests/utils/test_http_client.py index edd11ca7afb..a541bae6531 100644 --- a/seed/python-sdk/websocket/tests/utils/test_http_client.py +++ b/seed/python-sdk/websocket/tests/utils/test_http_client.py @@ -45,3 +45,17 @@ def test_get_none_request_body() -> None: assert json_body_extras == {"see you": "later"} assert data_body_extras is None + + +def test_get_empty_json_request_body() -> None: + unrelated_request_options: RequestOptions = {"max_retries": 3} + json_body, data_body = get_request_body(json=None, data=None, request_options=unrelated_request_options, omit=None) + assert json_body is None + assert data_body is None + + json_body_extras, data_body_extras = get_request_body( + json={}, data=None, request_options=unrelated_request_options, omit=None + ) + + assert json_body_extras is None + assert data_body_extras is None diff --git a/seed/ruby-model/grpc-proto/.mock/fern.config.json b/seed/ruby-model/grpc-proto/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/ruby-model/grpc-proto/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/ruby-model/grpc-proto/.mock/generators.yml b/seed/ruby-model/grpc-proto/.mock/generators.yml new file mode 100644 index 00000000000..902c4f50168 --- /dev/null +++ b/seed/ruby-model/grpc-proto/.mock/generators.yml @@ -0,0 +1,5 @@ +api: + - proto: + root: proto + target: proto/user/v1/user.proto + local-generation: true \ No newline at end of file diff --git a/seed/ruby-model/grpc-proto/.mock/proto/google/api/annotations.proto b/seed/ruby-model/grpc-proto/.mock/proto/google/api/annotations.proto new file mode 100644 index 00000000000..8ff42098404 --- /dev/null +++ b/seed/ruby-model/grpc-proto/.mock/proto/google/api/annotations.proto @@ -0,0 +1,31 @@ +// Copyright 2015 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/api/http.proto"; +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "AnnotationsProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // See `HttpRule`. + HttpRule http = 72295728; +} \ No newline at end of file diff --git a/seed/ruby-model/grpc-proto/.mock/proto/google/api/field_behavior.proto b/seed/ruby-model/grpc-proto/.mock/proto/google/api/field_behavior.proto new file mode 100644 index 00000000000..128799c558d --- /dev/null +++ b/seed/ruby-model/grpc-proto/.mock/proto/google/api/field_behavior.proto @@ -0,0 +1,104 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "FieldBehaviorProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.FieldOptions { + // A designation of a specific field behavior (required, output only, etc.) + // in protobuf messages. + // + // Examples: + // + // string name = 1 [(google.api.field_behavior) = REQUIRED]; + // State state = 1 [(google.api.field_behavior) = OUTPUT_ONLY]; + // google.protobuf.Duration ttl = 1 + // [(google.api.field_behavior) = INPUT_ONLY]; + // google.protobuf.Timestamp expire_time = 1 + // [(google.api.field_behavior) = OUTPUT_ONLY, + // (google.api.field_behavior) = IMMUTABLE]; + repeated google.api.FieldBehavior field_behavior = 1052; +} + +// An indicator of the behavior of a given field (for example, that a field +// is required in requests, or given as output but ignored as input). +// This **does not** change the behavior in protocol buffers itself; it only +// denotes the behavior and may affect how API tooling handles the field. +// +// Note: This enum **may** receive new values in the future. +enum FieldBehavior { + // Conventional default for enums. Do not use this. + FIELD_BEHAVIOR_UNSPECIFIED = 0; + + // Specifically denotes a field as optional. + // While all fields in protocol buffers are optional, this may be specified + // for emphasis if appropriate. + OPTIONAL = 1; + + // Denotes a field as required. + // This indicates that the field **must** be provided as part of the request, + // and failure to do so will cause an error (usually `INVALID_ARGUMENT`). + REQUIRED = 2; + + // Denotes a field as output only. + // This indicates that the field is provided in responses, but including the + // field in a request does nothing (the server *must* ignore it and + // *must not* throw an error as a result of the field's presence). + OUTPUT_ONLY = 3; + + // Denotes a field as input only. + // This indicates that the field is provided in requests, and the + // corresponding field is not included in output. + INPUT_ONLY = 4; + + // Denotes a field as immutable. + // This indicates that the field may be set once in a request to create a + // resource, but may not be changed thereafter. + IMMUTABLE = 5; + + // Denotes that a (repeated) field is an unordered list. + // This indicates that the service may provide the elements of the list + // in any arbitrary order, rather than the order the user originally + // provided. Additionally, the list's order may or may not be stable. + UNORDERED_LIST = 6; + + // Denotes that this field returns a non-empty default value if not set. + // This indicates that if the user provides the empty value in a request, + // a non-empty value will be returned. The user will not be aware of what + // non-empty value to expect. + NON_EMPTY_DEFAULT = 7; + + // Denotes that the field in a resource (a message annotated with + // google.api.resource) is used in the resource name to uniquely identify the + // resource. For AIP-compliant APIs, this should only be applied to the + // `name` field on the resource. + // + // This behavior should not be applied to references to other resources within + // the message. + // + // The identifier field of resources often have different field behavior + // depending on the request it is embedded in (e.g. for Create methods name + // is optional and unused, while for Update methods it is required). Instead + // of method-specific annotations, only `IDENTIFIER` is required. + IDENTIFIER = 8; +} \ No newline at end of file diff --git a/seed/ruby-model/grpc-proto/.mock/proto/google/api/http.proto b/seed/ruby-model/grpc-proto/.mock/proto/google/api/http.proto new file mode 100644 index 00000000000..c8392381eb9 --- /dev/null +++ b/seed/ruby-model/grpc-proto/.mock/proto/google/api/http.proto @@ -0,0 +1,379 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "HttpProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Defines the HTTP configuration for an API service. It contains a list of +// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method +// to one or more HTTP REST API methods. +message Http { + // A list of HTTP configuration rules that apply to individual API methods. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated HttpRule rules = 1; + + // When set to true, URL path parameters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // + // The default behavior is to not decode RFC 6570 reserved characters in multi + // segment matches. + bool fully_decode_reserved_expansion = 2; +} + +// # gRPC Transcoding +// +// gRPC Transcoding is a feature for mapping between a gRPC method and one or +// more HTTP REST endpoints. It allows developers to build a single API service +// that supports both gRPC APIs and REST APIs. Many systems, including [Google +// APIs](https://github.com/googleapis/googleapis), +// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC +// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), +// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature +// and use it for large scale production services. +// +// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies +// how different portions of the gRPC request message are mapped to the URL +// path, URL query parameters, and HTTP request body. It also controls how the +// gRPC response message is mapped to the HTTP response body. `HttpRule` is +// typically specified as an `google.api.http` annotation on the gRPC method. +// +// Each mapping specifies a URL path template and an HTTP method. The path +// template may refer to one or more fields in the gRPC request message, as long +// as each field is a non-repeated field with a primitive (non-message) type. +// The path template controls how fields of the request message are mapped to +// the URL path. +// +// Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/{name=messages/*}" +// }; +// } +// } +// message GetMessageRequest { +// string name = 1; // Mapped to URL path. +// } +// message Message { +// string text = 1; // The resource content. +// } +// +// This enables an HTTP REST to gRPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` +// +// Any fields in the request message which are not bound by the path template +// automatically become HTTP query parameters if there is no HTTP request body. +// For example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get:"/v1/messages/{message_id}" +// }; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // Mapped to URL path. +// int64 revision = 2; // Mapped to URL query parameter `revision`. +// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. +// } +// +// This enables a HTTP JSON to RPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | +// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: +// "foo"))` +// +// Note that fields which are mapped to URL query parameters must have a +// primitive type or a repeated primitive type or a non-repeated message type. +// In the case of a repeated type, the parameter can be repeated in the URL +// as `...?param=A¶m=B`. In the case of a message type, each field of the +// message is mapped to a separate parameter, such as +// `...?foo.a=A&foo.b=B&foo.c=C`. +// +// For HTTP methods that allow a request body, the `body` field +// specifies the mapping. Consider a REST update method on the +// message resource collection: +// +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } +// +// The following HTTP JSON to RPC mapping is enabled, where the +// representation of the JSON in the request body is determined by +// protos JSON encoding: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" message { text: "Hi!" })` +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. This enables the following alternative definition of +// the update method: +// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } +// +// +// The following HTTP JSON to RPC mapping is enabled: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" text: "Hi!")` +// +// Note that when using `*` in the body mapping, it is not possible to +// have HTTP parameters, as all fields not bound by the path end in +// the body. This makes this option more rarely used in practice when +// defining REST APIs. The common usage of `*` is in custom methods +// which don't use the URL at all for transferring data. +// +// It is possible to define multiple HTTP methods for one RPC by using +// the `additional_bindings` option. Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } +// +// This enables the following two alternative HTTP JSON to RPC mappings: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` +// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: +// "123456")` +// +// ## Rules for HTTP mapping +// +// 1. Leaf request fields (recursive expansion nested messages in the request +// message) are classified into three categories: +// - Fields referred by the path template. They are passed via the URL path. +// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They +// are passed via the HTTP +// request body. +// - All other fields are passed via the URL query parameters, and the +// parameter name is the field path in the request message. A repeated +// field can be represented as multiple query parameters under the same +// name. +// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL +// query parameter, all fields +// are passed via URL path and HTTP request body. +// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP +// request body, all +// fields are passed via URL path and URL query parameters. +// +// ### Path template syntax +// +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +// +// The syntax `*` matches a single URL path segment. The syntax `**` matches +// zero or more URL path segments, which must be the last part of the URL path +// except the `Verb`. +// +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable +// matches a single path segment, its template may be omitted, e.g. `{var}` +// is equivalent to `{var=*}`. +// +// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` +// contains any reserved character, such characters should be percent-encoded +// before the matching. +// +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path on the client +// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The +// server side does the reverse decoding. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{var}`. +// +// If a variable contains multiple path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path on the +// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. +// The server side does the reverse decoding, except "%2F" and "%2f" are left +// unchanged. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{+var}`. +// +// ## Using gRPC API Service Configuration +// +// gRPC API Service Configuration (service config) is a configuration language +// for configuring a gRPC service to become a user-facing product. The +// service config is simply the YAML representation of the `google.api.Service` +// proto message. +// +// As an alternative to annotating your proto file, you can configure gRPC +// transcoding in your service config YAML files. You do this by specifying a +// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same +// effect as the proto annotation. This can be particularly useful if you +// have a proto that is reused in multiple services. Note that any transcoding +// specified in the service config will override any matching transcoding +// configuration in the proto. +// +// Example: +// +// http: +// rules: +// # Selects a gRPC method and applies HttpRule to it. +// - selector: example.v1.Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} +// +// ## Special notes +// +// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the +// proto to JSON conversion must follow the [proto3 +// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). +// +// While the single segment variable follows the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String +// Expansion, the multi segment variable **does not** follow RFC 6570 Section +// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding +// for multi segment variables. +// +// The path variables **must not** refer to any repeated or mapped field, +// because client libraries are not capable of handling such variable expansion. +// +// The path variables **must not** capture the leading "/" character. The reason +// is that the most common use case "{var}" does not capture the leading "/" +// character. For consistency, all path variables must share the same behavior. +// +// Repeated message fields must not be mapped to URL query parameters, because +// no client library can support such complicated mapping. +// +// If an API needs to use a JSON array for request or response body, it can map +// the request or response body to a repeated field. However, some gRPC +// Transcoding implementations may not support this feature. +message HttpRule { + // Selects a method to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax + // details. + string selector = 1; + + // Determines the URL pattern is matched by this rules. This pattern can be + // used with any of the {get|put|post|delete|patch} methods. A custom method + // can be defined using the 'custom' field. + oneof pattern { + // Maps to HTTP GET. Used for listing and getting information about + // resources. + string get = 2; + + // Maps to HTTP PUT. Used for replacing a resource. + string put = 3; + + // Maps to HTTP POST. Used for creating a resource or performing an action. + string post = 4; + + // Maps to HTTP DELETE. Used for deleting a resource. + string delete = 5; + + // Maps to HTTP PATCH. Used for updating a resource. + string patch = 6; + + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. + CustomHttpPattern custom = 8; + } + + // The name of the request field whose value is mapped to the HTTP request + // body, or `*` for mapping all request fields not captured by the path + // pattern to the HTTP body, or omitted for not having any HTTP request body. + // + // NOTE: the referred field must be present at the top-level of the request + // message type. + string body = 7; + + // Optional. The name of the response field whose value is mapped to the HTTP + // response body. When omitted, the entire response message will be used + // as the HTTP response body. + // + // NOTE: The referred field must be present at the top-level of the response + // message type. + string response_body = 12; + + // Additional HTTP bindings for the selector. Nested bindings must + // not contain an `additional_bindings` field themselves (that is, + // the nesting may only be one level deep). + repeated HttpRule additional_bindings = 11; +} + +// A custom pattern is used for defining custom HTTP verb. +message CustomHttpPattern { + // The name of this custom HTTP verb. + string kind = 1; + + // The path matched by this custom verb. + string path = 2; +} \ No newline at end of file diff --git a/seed/ruby-model/grpc-proto/.mock/proto/user/v1/user.proto b/seed/ruby-model/grpc-proto/.mock/proto/user/v1/user.proto new file mode 100644 index 00000000000..a3ad0195bd4 --- /dev/null +++ b/seed/ruby-model/grpc-proto/.mock/proto/user/v1/user.proto @@ -0,0 +1,39 @@ +syntax = "proto3"; + +package user.v1; + +import "google/api/annotations.proto"; +import "google/api/field_behavior.proto"; +import "google/protobuf/struct.proto"; + +option go_package = "user/v1"; +option csharp_namespace = "User.V1"; + +message UserModel { + string username = 1; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateRequest { + string username = 1; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateResponse { + UserModel user = 1; +} + +service User { + rpc Create(CreateRequest) returns (CreateResponse) { + option (google.api.http) = { + post: "/users" + body: "*" + }; + } +} \ No newline at end of file diff --git a/seed/ruby-model/grpc-proto/.rubocop.yml b/seed/ruby-model/grpc-proto/.rubocop.yml new file mode 100644 index 00000000000..c1d2344d6e6 --- /dev/null +++ b/seed/ruby-model/grpc-proto/.rubocop.yml @@ -0,0 +1,36 @@ +AllCops: + TargetRubyVersion: 2.7 + +Style/StringLiterals: + Enabled: true + EnforcedStyle: double_quotes + +Style/StringLiteralsInInterpolation: + Enabled: true + EnforcedStyle: double_quotes + +Layout/FirstHashElementLineBreak: + Enabled: true + +Layout/MultilineHashKeyLineBreaks: + Enabled: true + +# Generated files may be more complex than standard, disable +# these rules for now as a known limitation. +Metrics/ParameterLists: + Enabled: false + +Metrics/MethodLength: + Enabled: false + +Metrics/AbcSize: + Enabled: false + +Metrics/ClassLength: + Enabled: false + +Metrics/CyclomaticComplexity: + Enabled: false + +Metrics/PerceivedComplexity: + Enabled: false diff --git a/seed/ruby-model/grpc-proto/Gemfile b/seed/ruby-model/grpc-proto/Gemfile new file mode 100644 index 00000000000..49bd9cd0173 --- /dev/null +++ b/seed/ruby-model/grpc-proto/Gemfile @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +gemspec + +gem "minitest", "~> 5.0" +gem "rake", "~> 13.0" +gem "rubocop", "~> 1.21" diff --git a/seed/ruby-model/grpc-proto/Rakefile b/seed/ruby-model/grpc-proto/Rakefile new file mode 100644 index 00000000000..2bdbce0cf2c --- /dev/null +++ b/seed/ruby-model/grpc-proto/Rakefile @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require "rake/testtask" +require "rubocop/rake_task" + +task default: %i[test rubocop] + +Rake::TestTask.new do |t| + t.pattern = "./test/**/test_*.rb" +end + +RuboCop::RakeTask.new diff --git a/seed/ruby-model/grpc-proto/lib/gemconfig.rb b/seed/ruby-model/grpc-proto/lib/gemconfig.rb new file mode 100644 index 00000000000..b4c1f83d5b0 --- /dev/null +++ b/seed/ruby-model/grpc-proto/lib/gemconfig.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module SeedApiClient + module Gemconfig + VERSION = "" + AUTHORS = [""].freeze + EMAIL = "" + SUMMARY = "" + DESCRIPTION = "" + HOMEPAGE = "https://github.com/REPO/URL" + SOURCE_CODE_URI = "https://github.com/REPO/URL" + CHANGELOG_URI = "https://github.com/REPO/URL/blob/master/CHANGELOG.md" + end +end diff --git a/seed/ruby-model/grpc-proto/lib/seed_api_client.rb b/seed/ruby-model/grpc-proto/lib/seed_api_client.rb new file mode 100644 index 00000000000..09e26f17e22 --- /dev/null +++ b/seed/ruby-model/grpc-proto/lib/seed_api_client.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +require_relative "seed_api_client/types/create_response" +require_relative "seed_api_client/types/user_model" diff --git a/seed/ruby-model/grpc-proto/lib/seed_api_client/types/create_response.rb b/seed/ruby-model/grpc-proto/lib/seed_api_client/types/create_response.rb new file mode 100644 index 00000000000..00c5e0f6f63 --- /dev/null +++ b/seed/ruby-model/grpc-proto/lib/seed_api_client/types/create_response.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require_relative "user_model" +require "ostruct" +require "json" + +module SeedApiClient + class CreateResponse + # @return [SeedApiClient::UserModel] + attr_reader :user + # @return [OpenStruct] Additional properties unmapped to the current class definition + attr_reader :additional_properties + # @return [Object] + attr_reader :_field_set + protected :_field_set + + OMIT = Object.new + + # @param user [SeedApiClient::UserModel] + # @param additional_properties [OpenStruct] Additional properties unmapped to the current class definition + # @return [SeedApiClient::CreateResponse] + def initialize(user: OMIT, additional_properties: nil) + @user = user if user != OMIT + @additional_properties = additional_properties + @_field_set = { "user": user }.reject do |_k, v| + v == OMIT + end + end + + # Deserialize a JSON object to an instance of CreateResponse + # + # @param json_object [String] + # @return [SeedApiClient::CreateResponse] + def self.from_json(json_object:) + struct = JSON.parse(json_object, object_class: OpenStruct) + parsed_json = JSON.parse(json_object) + if parsed_json["user"].nil? + user = nil + else + user = parsed_json["user"].to_json + user = SeedApiClient::UserModel.from_json(json_object: user) + end + new(user: user, additional_properties: struct) + end + + # Serialize an instance of CreateResponse to a JSON object + # + # @return [String] + def to_json(*_args) + @_field_set&.to_json + end + + # Leveraged for Union-type generation, validate_raw attempts to parse the given + # hash and check each fields type against the current object's property + # definitions. + # + # @param obj [Object] + # @return [Void] + def self.validate_raw(obj:) + obj.user.nil? || SeedApiClient::UserModel.validate_raw(obj: obj.user) + end + end +end diff --git a/seed/ruby-model/grpc-proto/lib/seed_api_client/types/user_model.rb b/seed/ruby-model/grpc-proto/lib/seed_api_client/types/user_model.rb new file mode 100644 index 00000000000..157004d3be3 --- /dev/null +++ b/seed/ruby-model/grpc-proto/lib/seed_api_client/types/user_model.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +require "ostruct" +require "json" + +module SeedApiClient + class UserModel + # @return [String] + attr_reader :username + # @return [String] + attr_reader :email + # @return [Integer] + attr_reader :age + # @return [Float] + attr_reader :weight + # @return [Hash{String => Object}] + attr_reader :metadata + # @return [OpenStruct] Additional properties unmapped to the current class definition + attr_reader :additional_properties + # @return [Object] + attr_reader :_field_set + protected :_field_set + + OMIT = Object.new + + # @param username [String] + # @param email [String] + # @param age [Integer] + # @param weight [Float] + # @param metadata [Hash{String => Object}] + # @param additional_properties [OpenStruct] Additional properties unmapped to the current class definition + # @return [SeedApiClient::UserModel] + def initialize(username: OMIT, email: OMIT, age: OMIT, weight: OMIT, metadata: OMIT, additional_properties: nil) + @username = username if username != OMIT + @email = email if email != OMIT + @age = age if age != OMIT + @weight = weight if weight != OMIT + @metadata = metadata if metadata != OMIT + @additional_properties = additional_properties + @_field_set = { + "username": username, + "email": email, + "age": age, + "weight": weight, + "metadata": metadata + }.reject do |_k, v| + v == OMIT + end + end + + # Deserialize a JSON object to an instance of UserModel + # + # @param json_object [String] + # @return [SeedApiClient::UserModel] + def self.from_json(json_object:) + struct = JSON.parse(json_object, object_class: OpenStruct) + parsed_json = JSON.parse(json_object) + username = parsed_json["username"] + email = parsed_json["email"] + age = parsed_json["age"] + weight = parsed_json["weight"] + metadata = parsed_json["metadata"] + new( + username: username, + email: email, + age: age, + weight: weight, + metadata: metadata, + additional_properties: struct + ) + end + + # Serialize an instance of UserModel to a JSON object + # + # @return [String] + def to_json(*_args) + @_field_set&.to_json + end + + # Leveraged for Union-type generation, validate_raw attempts to parse the given + # hash and check each fields type against the current object's property + # definitions. + # + # @param obj [Object] + # @return [Void] + def self.validate_raw(obj:) + obj.username&.is_a?(String) != false || raise("Passed value for field obj.username is not the expected type, validation failed.") + obj.email&.is_a?(String) != false || raise("Passed value for field obj.email is not the expected type, validation failed.") + obj.age&.is_a?(Integer) != false || raise("Passed value for field obj.age is not the expected type, validation failed.") + obj.weight&.is_a?(Float) != false || raise("Passed value for field obj.weight is not the expected type, validation failed.") + obj.metadata&.is_a?(Hash) != false || raise("Passed value for field obj.metadata is not the expected type, validation failed.") + end + end +end diff --git a/seed/ruby-model/grpc-proto/seed_api_client.gemspec b/seed/ruby-model/grpc-proto/seed_api_client.gemspec new file mode 100644 index 00000000000..70ca9e56ece --- /dev/null +++ b/seed/ruby-model/grpc-proto/seed_api_client.gemspec @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require_relative "lib/gemconfig" + +Gem::Specification.new do |spec| + spec.name = "seed_api_client" + spec.version = SeedApiClient::Gemconfig::VERSION + spec.authors = SeedApiClient::Gemconfig::AUTHORS + spec.email = SeedApiClient::Gemconfig::EMAIL + spec.summary = SeedApiClient::Gemconfig::SUMMARY + spec.description = SeedApiClient::Gemconfig::DESCRIPTION + spec.homepage = SeedApiClient::Gemconfig::HOMEPAGE + spec.required_ruby_version = ">= 2.7.0" + spec.metadata["homepage_uri"] = spec.homepage + spec.metadata["source_code_uri"] = SeedApiClient::Gemconfig::SOURCE_CODE_URI + spec.metadata["changelog_uri"] = SeedApiClient::Gemconfig::CHANGELOG_URI + spec.files = Dir.glob("lib/**/*") + spec.bindir = "exe" + spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } + spec.require_paths = ["lib"] +end diff --git a/seed/ruby-model/grpc-proto/snippet-templates.json b/seed/ruby-model/grpc-proto/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/ruby-model/grpc-proto/snippet.json b/seed/ruby-model/grpc-proto/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/ruby-model/grpc-proto/test/test_helper.rb b/seed/ruby-model/grpc-proto/test/test_helper.rb new file mode 100644 index 00000000000..a8adf420794 --- /dev/null +++ b/seed/ruby-model/grpc-proto/test/test_helper.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +$LOAD_PATH.unshift File.expand_path("../lib", __dir__) + +require "minitest/autorun" +require "seed_api_client" diff --git a/seed/ruby-model/grpc-proto/test/test_seed_api_client.rb b/seed/ruby-model/grpc-proto/test/test_seed_api_client.rb new file mode 100644 index 00000000000..a151098d108 --- /dev/null +++ b/seed/ruby-model/grpc-proto/test/test_seed_api_client.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require_relative "test_helper" +require "seed_api_client" + +# Basic SeedApiClient tests +class TestSeedApiClient < Minitest::Test + def test_function + # SeedApiClient::Client.new + end +end diff --git a/seed/ruby-model/pagination/.mock/ir.json b/seed/ruby-model/pagination/.mock/ir.json deleted file mode 100644 index ef50cbc3fc6..00000000000 --- a/seed/ruby-model/pagination/.mock/ir.json +++ /dev/null @@ -1,6992 +0,0 @@ -{ - "apiName": { - "originalName": "pagination", - "camelCase": { - "unsafeName": "pagination", - "safeName": "pagination" - }, - "snakeCase": { - "unsafeName": "pagination", - "safeName": "pagination" - }, - "screamingSnakeCase": { - "unsafeName": "PAGINATION", - "safeName": "PAGINATION" - }, - "pascalCase": { - "unsafeName": "Pagination", - "safeName": "Pagination" - } - }, - "apiDisplayName": null, - "apiDocs": null, - "auth": { - "requirement": "ALL", - "schemes": [ - { - "_type": "bearer", - "token": { - "originalName": "token", - "camelCase": { - "unsafeName": "token", - "safeName": "token" - }, - "snakeCase": { - "unsafeName": "token", - "safeName": "token" - }, - "screamingSnakeCase": { - "unsafeName": "TOKEN", - "safeName": "TOKEN" - }, - "pascalCase": { - "unsafeName": "Token", - "safeName": "Token" - } - }, - "tokenEnvVar": null, - "docs": null - } - ], - "docs": null - }, - "headers": [], - "idempotencyHeaders": [], - "types": { - "type_:UsernameCursor": { - "name": { - "name": { - "originalName": "UsernameCursor", - "camelCase": { - "unsafeName": "usernameCursor", - "safeName": "usernameCursor" - }, - "snakeCase": { - "unsafeName": "username_cursor", - "safeName": "username_cursor" - }, - "screamingSnakeCase": { - "unsafeName": "USERNAME_CURSOR", - "safeName": "USERNAME_CURSOR" - }, - "pascalCase": { - "unsafeName": "UsernameCursor", - "safeName": "UsernameCursor" - } - }, - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "typeId": "type_:UsernameCursor" - }, - "shape": { - "_type": "object", - "extends": [], - "properties": [ - { - "name": { - "name": { - "originalName": "cursor", - "camelCase": { - "unsafeName": "cursor", - "safeName": "cursor" - }, - "snakeCase": { - "unsafeName": "cursor", - "safeName": "cursor" - }, - "screamingSnakeCase": { - "unsafeName": "CURSOR", - "safeName": "CURSOR" - }, - "pascalCase": { - "unsafeName": "Cursor", - "safeName": "Cursor" - } - }, - "wireValue": "cursor" - }, - "valueType": { - "_type": "named", - "name": { - "originalName": "UsernamePage", - "camelCase": { - "unsafeName": "usernamePage", - "safeName": "usernamePage" - }, - "snakeCase": { - "unsafeName": "username_page", - "safeName": "username_page" - }, - "screamingSnakeCase": { - "unsafeName": "USERNAME_PAGE", - "safeName": "USERNAME_PAGE" - }, - "pascalCase": { - "unsafeName": "UsernamePage", - "safeName": "UsernamePage" - } - }, - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "typeId": "type_:UsernamePage" - }, - "availability": null, - "docs": null - } - ], - "extra-properties": false - }, - "referencedTypes": [ - "type_:UsernamePage" - ], - "examples": [], - "availability": null, - "docs": null - }, - "type_:UsernamePage": { - "name": { - "name": { - "originalName": "UsernamePage", - "camelCase": { - "unsafeName": "usernamePage", - "safeName": "usernamePage" - }, - "snakeCase": { - "unsafeName": "username_page", - "safeName": "username_page" - }, - "screamingSnakeCase": { - "unsafeName": "USERNAME_PAGE", - "safeName": "USERNAME_PAGE" - }, - "pascalCase": { - "unsafeName": "UsernamePage", - "safeName": "UsernamePage" - } - }, - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "typeId": "type_:UsernamePage" - }, - "shape": { - "_type": "object", - "extends": [], - "properties": [ - { - "name": { - "name": { - "originalName": "after", - "camelCase": { - "unsafeName": "after", - "safeName": "after" - }, - "snakeCase": { - "unsafeName": "after", - "safeName": "after" - }, - "screamingSnakeCase": { - "unsafeName": "AFTER", - "safeName": "AFTER" - }, - "pascalCase": { - "unsafeName": "After", - "safeName": "After" - } - }, - "wireValue": "after" - }, - "valueType": { - "_type": "container", - "container": { - "_type": "optional", - "optional": { - "_type": "primitive", - "primitive": "STRING" - } - } - }, - "availability": null, - "docs": null - }, - { - "name": { - "name": { - "originalName": "data", - "camelCase": { - "unsafeName": "data", - "safeName": "data" - }, - "snakeCase": { - "unsafeName": "data", - "safeName": "data" - }, - "screamingSnakeCase": { - "unsafeName": "DATA", - "safeName": "DATA" - }, - "pascalCase": { - "unsafeName": "Data", - "safeName": "Data" - } - }, - "wireValue": "data" - }, - "valueType": { - "_type": "container", - "container": { - "_type": "list", - "list": { - "_type": "primitive", - "primitive": "STRING" - } - } - }, - "availability": null, - "docs": null - } - ], - "extra-properties": false - }, - "referencedTypes": [], - "examples": [], - "availability": null, - "docs": null - }, - "type_users:Order": { - "name": { - "name": { - "originalName": "Order", - "camelCase": { - "unsafeName": "order", - "safeName": "order" - }, - "snakeCase": { - "unsafeName": "order", - "safeName": "order" - }, - "screamingSnakeCase": { - "unsafeName": "ORDER", - "safeName": "ORDER" - }, - "pascalCase": { - "unsafeName": "Order", - "safeName": "Order" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:Order" - }, - "shape": { - "_type": "enum", - "values": [ - { - "name": { - "name": { - "originalName": "asc", - "camelCase": { - "unsafeName": "asc", - "safeName": "asc" - }, - "snakeCase": { - "unsafeName": "asc", - "safeName": "asc" - }, - "screamingSnakeCase": { - "unsafeName": "ASC", - "safeName": "ASC" - }, - "pascalCase": { - "unsafeName": "Asc", - "safeName": "Asc" - } - }, - "wireValue": "asc" - }, - "availability": null, - "docs": null - }, - { - "name": { - "name": { - "originalName": "desc", - "camelCase": { - "unsafeName": "desc", - "safeName": "desc" - }, - "snakeCase": { - "unsafeName": "desc", - "safeName": "desc" - }, - "screamingSnakeCase": { - "unsafeName": "DESC", - "safeName": "DESC" - }, - "pascalCase": { - "unsafeName": "Desc", - "safeName": "Desc" - } - }, - "wireValue": "desc" - }, - "availability": null, - "docs": null - } - ] - }, - "referencedTypes": [], - "examples": [], - "availability": null, - "docs": null - }, - "type_users:UserListContainer": { - "name": { - "name": { - "originalName": "UserListContainer", - "camelCase": { - "unsafeName": "userListContainer", - "safeName": "userListContainer" - }, - "snakeCase": { - "unsafeName": "user_list_container", - "safeName": "user_list_container" - }, - "screamingSnakeCase": { - "unsafeName": "USER_LIST_CONTAINER", - "safeName": "USER_LIST_CONTAINER" - }, - "pascalCase": { - "unsafeName": "UserListContainer", - "safeName": "UserListContainer" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:UserListContainer" - }, - "shape": { - "_type": "object", - "extends": [], - "properties": [ - { - "name": { - "name": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - }, - "wireValue": "users" - }, - "valueType": { - "_type": "container", - "container": { - "_type": "list", - "list": { - "_type": "named", - "name": { - "originalName": "User", - "camelCase": { - "unsafeName": "user", - "safeName": "user" - }, - "snakeCase": { - "unsafeName": "user", - "safeName": "user" - }, - "screamingSnakeCase": { - "unsafeName": "USER", - "safeName": "USER" - }, - "pascalCase": { - "unsafeName": "User", - "safeName": "User" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:User" - } - } - }, - "availability": null, - "docs": null - } - ], - "extra-properties": false - }, - "referencedTypes": [ - "type_users:User" - ], - "examples": [], - "availability": null, - "docs": null - }, - "type_users:UserPage": { - "name": { - "name": { - "originalName": "UserPage", - "camelCase": { - "unsafeName": "userPage", - "safeName": "userPage" - }, - "snakeCase": { - "unsafeName": "user_page", - "safeName": "user_page" - }, - "screamingSnakeCase": { - "unsafeName": "USER_PAGE", - "safeName": "USER_PAGE" - }, - "pascalCase": { - "unsafeName": "UserPage", - "safeName": "UserPage" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:UserPage" - }, - "shape": { - "_type": "object", - "extends": [], - "properties": [ - { - "name": { - "name": { - "originalName": "data", - "camelCase": { - "unsafeName": "data", - "safeName": "data" - }, - "snakeCase": { - "unsafeName": "data", - "safeName": "data" - }, - "screamingSnakeCase": { - "unsafeName": "DATA", - "safeName": "DATA" - }, - "pascalCase": { - "unsafeName": "Data", - "safeName": "Data" - } - }, - "wireValue": "data" - }, - "valueType": { - "_type": "named", - "name": { - "originalName": "UserListContainer", - "camelCase": { - "unsafeName": "userListContainer", - "safeName": "userListContainer" - }, - "snakeCase": { - "unsafeName": "user_list_container", - "safeName": "user_list_container" - }, - "screamingSnakeCase": { - "unsafeName": "USER_LIST_CONTAINER", - "safeName": "USER_LIST_CONTAINER" - }, - "pascalCase": { - "unsafeName": "UserListContainer", - "safeName": "UserListContainer" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:UserListContainer" - }, - "availability": null, - "docs": null - }, - { - "name": { - "name": { - "originalName": "next", - "camelCase": { - "unsafeName": "next", - "safeName": "next" - }, - "snakeCase": { - "unsafeName": "next", - "safeName": "next" - }, - "screamingSnakeCase": { - "unsafeName": "NEXT", - "safeName": "NEXT" - }, - "pascalCase": { - "unsafeName": "Next", - "safeName": "Next" - } - }, - "wireValue": "next" - }, - "valueType": { - "_type": "container", - "container": { - "_type": "optional", - "optional": { - "_type": "primitive", - "primitive": "UUID" - } - } - }, - "availability": null, - "docs": null - } - ], - "extra-properties": false - }, - "referencedTypes": [ - "type_users:UserListContainer", - "type_users:User" - ], - "examples": [], - "availability": null, - "docs": null - }, - "type_users:UsernameContainer": { - "name": { - "name": { - "originalName": "UsernameContainer", - "camelCase": { - "unsafeName": "usernameContainer", - "safeName": "usernameContainer" - }, - "snakeCase": { - "unsafeName": "username_container", - "safeName": "username_container" - }, - "screamingSnakeCase": { - "unsafeName": "USERNAME_CONTAINER", - "safeName": "USERNAME_CONTAINER" - }, - "pascalCase": { - "unsafeName": "UsernameContainer", - "safeName": "UsernameContainer" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:UsernameContainer" - }, - "shape": { - "_type": "object", - "extends": [], - "properties": [ - { - "name": { - "name": { - "originalName": "results", - "camelCase": { - "unsafeName": "results", - "safeName": "results" - }, - "snakeCase": { - "unsafeName": "results", - "safeName": "results" - }, - "screamingSnakeCase": { - "unsafeName": "RESULTS", - "safeName": "RESULTS" - }, - "pascalCase": { - "unsafeName": "Results", - "safeName": "Results" - } - }, - "wireValue": "results" - }, - "valueType": { - "_type": "container", - "container": { - "_type": "list", - "list": { - "_type": "primitive", - "primitive": "STRING" - } - } - }, - "availability": null, - "docs": null - } - ], - "extra-properties": false - }, - "referencedTypes": [], - "examples": [], - "availability": null, - "docs": null - }, - "type_users:ListUsersExtendedResponse": { - "name": { - "name": { - "originalName": "ListUsersExtendedResponse", - "camelCase": { - "unsafeName": "listUsersExtendedResponse", - "safeName": "listUsersExtendedResponse" - }, - "snakeCase": { - "unsafeName": "list_users_extended_response", - "safeName": "list_users_extended_response" - }, - "screamingSnakeCase": { - "unsafeName": "LIST_USERS_EXTENDED_RESPONSE", - "safeName": "LIST_USERS_EXTENDED_RESPONSE" - }, - "pascalCase": { - "unsafeName": "ListUsersExtendedResponse", - "safeName": "ListUsersExtendedResponse" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:ListUsersExtendedResponse" - }, - "shape": { - "_type": "object", - "extends": [ - { - "name": { - "originalName": "UserPage", - "camelCase": { - "unsafeName": "userPage", - "safeName": "userPage" - }, - "snakeCase": { - "unsafeName": "user_page", - "safeName": "user_page" - }, - "screamingSnakeCase": { - "unsafeName": "USER_PAGE", - "safeName": "USER_PAGE" - }, - "pascalCase": { - "unsafeName": "UserPage", - "safeName": "UserPage" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:UserPage" - } - ], - "properties": [ - { - "name": { - "name": { - "originalName": "total_count", - "camelCase": { - "unsafeName": "totalCount", - "safeName": "totalCount" - }, - "snakeCase": { - "unsafeName": "total_count", - "safeName": "total_count" - }, - "screamingSnakeCase": { - "unsafeName": "TOTAL_COUNT", - "safeName": "TOTAL_COUNT" - }, - "pascalCase": { - "unsafeName": "TotalCount", - "safeName": "TotalCount" - } - }, - "wireValue": "total_count" - }, - "valueType": { - "_type": "primitive", - "primitive": "INTEGER" - }, - "availability": null, - "docs": "The totall number of /users" - } - ], - "extra-properties": false - }, - "referencedTypes": [ - "type_users:UserPage", - "type_users:UserListContainer", - "type_users:User" - ], - "examples": [], - "availability": null, - "docs": null - }, - "type_users:ListUsersPaginationResponse": { - "name": { - "name": { - "originalName": "ListUsersPaginationResponse", - "camelCase": { - "unsafeName": "listUsersPaginationResponse", - "safeName": "listUsersPaginationResponse" - }, - "snakeCase": { - "unsafeName": "list_users_pagination_response", - "safeName": "list_users_pagination_response" - }, - "screamingSnakeCase": { - "unsafeName": "LIST_USERS_PAGINATION_RESPONSE", - "safeName": "LIST_USERS_PAGINATION_RESPONSE" - }, - "pascalCase": { - "unsafeName": "ListUsersPaginationResponse", - "safeName": "ListUsersPaginationResponse" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:ListUsersPaginationResponse" - }, - "shape": { - "_type": "object", - "extends": [], - "properties": [ - { - "name": { - "name": { - "originalName": "page", - "camelCase": { - "unsafeName": "page", - "safeName": "page" - }, - "snakeCase": { - "unsafeName": "page", - "safeName": "page" - }, - "screamingSnakeCase": { - "unsafeName": "PAGE", - "safeName": "PAGE" - }, - "pascalCase": { - "unsafeName": "Page", - "safeName": "Page" - } - }, - "wireValue": "page" - }, - "valueType": { - "_type": "container", - "container": { - "_type": "optional", - "optional": { - "_type": "named", - "name": { - "originalName": "Page", - "camelCase": { - "unsafeName": "page", - "safeName": "page" - }, - "snakeCase": { - "unsafeName": "page", - "safeName": "page" - }, - "screamingSnakeCase": { - "unsafeName": "PAGE", - "safeName": "PAGE" - }, - "pascalCase": { - "unsafeName": "Page", - "safeName": "Page" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:Page" - } - } - }, - "availability": null, - "docs": null - }, - { - "name": { - "name": { - "originalName": "total_count", - "camelCase": { - "unsafeName": "totalCount", - "safeName": "totalCount" - }, - "snakeCase": { - "unsafeName": "total_count", - "safeName": "total_count" - }, - "screamingSnakeCase": { - "unsafeName": "TOTAL_COUNT", - "safeName": "TOTAL_COUNT" - }, - "pascalCase": { - "unsafeName": "TotalCount", - "safeName": "TotalCount" - } - }, - "wireValue": "total_count" - }, - "valueType": { - "_type": "primitive", - "primitive": "INTEGER" - }, - "availability": null, - "docs": "The totall number of /users" - }, - { - "name": { - "name": { - "originalName": "data", - "camelCase": { - "unsafeName": "data", - "safeName": "data" - }, - "snakeCase": { - "unsafeName": "data", - "safeName": "data" - }, - "screamingSnakeCase": { - "unsafeName": "DATA", - "safeName": "DATA" - }, - "pascalCase": { - "unsafeName": "Data", - "safeName": "Data" - } - }, - "wireValue": "data" - }, - "valueType": { - "_type": "container", - "container": { - "_type": "list", - "list": { - "_type": "named", - "name": { - "originalName": "User", - "camelCase": { - "unsafeName": "user", - "safeName": "user" - }, - "snakeCase": { - "unsafeName": "user", - "safeName": "user" - }, - "screamingSnakeCase": { - "unsafeName": "USER", - "safeName": "USER" - }, - "pascalCase": { - "unsafeName": "User", - "safeName": "User" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:User" - } - } - }, - "availability": null, - "docs": null - } - ], - "extra-properties": false - }, - "referencedTypes": [ - "type_users:Page", - "type_users:NextPage", - "type_users:User" - ], - "examples": [], - "availability": null, - "docs": null - }, - "type_users:Page": { - "name": { - "name": { - "originalName": "Page", - "camelCase": { - "unsafeName": "page", - "safeName": "page" - }, - "snakeCase": { - "unsafeName": "page", - "safeName": "page" - }, - "screamingSnakeCase": { - "unsafeName": "PAGE", - "safeName": "PAGE" - }, - "pascalCase": { - "unsafeName": "Page", - "safeName": "Page" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:Page" - }, - "shape": { - "_type": "object", - "extends": [], - "properties": [ - { - "name": { - "name": { - "originalName": "page", - "camelCase": { - "unsafeName": "page", - "safeName": "page" - }, - "snakeCase": { - "unsafeName": "page", - "safeName": "page" - }, - "screamingSnakeCase": { - "unsafeName": "PAGE", - "safeName": "PAGE" - }, - "pascalCase": { - "unsafeName": "Page", - "safeName": "Page" - } - }, - "wireValue": "page" - }, - "valueType": { - "_type": "primitive", - "primitive": "INTEGER" - }, - "availability": null, - "docs": "The current page" - }, - { - "name": { - "name": { - "originalName": "next", - "camelCase": { - "unsafeName": "next", - "safeName": "next" - }, - "snakeCase": { - "unsafeName": "next", - "safeName": "next" - }, - "screamingSnakeCase": { - "unsafeName": "NEXT", - "safeName": "NEXT" - }, - "pascalCase": { - "unsafeName": "Next", - "safeName": "Next" - } - }, - "wireValue": "next" - }, - "valueType": { - "_type": "container", - "container": { - "_type": "optional", - "optional": { - "_type": "named", - "name": { - "originalName": "NextPage", - "camelCase": { - "unsafeName": "nextPage", - "safeName": "nextPage" - }, - "snakeCase": { - "unsafeName": "next_page", - "safeName": "next_page" - }, - "screamingSnakeCase": { - "unsafeName": "NEXT_PAGE", - "safeName": "NEXT_PAGE" - }, - "pascalCase": { - "unsafeName": "NextPage", - "safeName": "NextPage" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:NextPage" - } - } - }, - "availability": null, - "docs": null - }, - { - "name": { - "name": { - "originalName": "per_page", - "camelCase": { - "unsafeName": "perPage", - "safeName": "perPage" - }, - "snakeCase": { - "unsafeName": "per_page", - "safeName": "per_page" - }, - "screamingSnakeCase": { - "unsafeName": "PER_PAGE", - "safeName": "PER_PAGE" - }, - "pascalCase": { - "unsafeName": "PerPage", - "safeName": "PerPage" - } - }, - "wireValue": "per_page" - }, - "valueType": { - "_type": "primitive", - "primitive": "INTEGER" - }, - "availability": null, - "docs": null - }, - { - "name": { - "name": { - "originalName": "total_page", - "camelCase": { - "unsafeName": "totalPage", - "safeName": "totalPage" - }, - "snakeCase": { - "unsafeName": "total_page", - "safeName": "total_page" - }, - "screamingSnakeCase": { - "unsafeName": "TOTAL_PAGE", - "safeName": "TOTAL_PAGE" - }, - "pascalCase": { - "unsafeName": "TotalPage", - "safeName": "TotalPage" - } - }, - "wireValue": "total_page" - }, - "valueType": { - "_type": "primitive", - "primitive": "INTEGER" - }, - "availability": null, - "docs": null - } - ], - "extra-properties": false - }, - "referencedTypes": [ - "type_users:NextPage" - ], - "examples": [], - "availability": null, - "docs": null - }, - "type_users:NextPage": { - "name": { - "name": { - "originalName": "NextPage", - "camelCase": { - "unsafeName": "nextPage", - "safeName": "nextPage" - }, - "snakeCase": { - "unsafeName": "next_page", - "safeName": "next_page" - }, - "screamingSnakeCase": { - "unsafeName": "NEXT_PAGE", - "safeName": "NEXT_PAGE" - }, - "pascalCase": { - "unsafeName": "NextPage", - "safeName": "NextPage" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:NextPage" - }, - "shape": { - "_type": "object", - "extends": [], - "properties": [ - { - "name": { - "name": { - "originalName": "page", - "camelCase": { - "unsafeName": "page", - "safeName": "page" - }, - "snakeCase": { - "unsafeName": "page", - "safeName": "page" - }, - "screamingSnakeCase": { - "unsafeName": "PAGE", - "safeName": "PAGE" - }, - "pascalCase": { - "unsafeName": "Page", - "safeName": "Page" - } - }, - "wireValue": "page" - }, - "valueType": { - "_type": "primitive", - "primitive": "INTEGER" - }, - "availability": null, - "docs": null - }, - { - "name": { - "name": { - "originalName": "starting_after", - "camelCase": { - "unsafeName": "startingAfter", - "safeName": "startingAfter" - }, - "snakeCase": { - "unsafeName": "starting_after", - "safeName": "starting_after" - }, - "screamingSnakeCase": { - "unsafeName": "STARTING_AFTER", - "safeName": "STARTING_AFTER" - }, - "pascalCase": { - "unsafeName": "StartingAfter", - "safeName": "StartingAfter" - } - }, - "wireValue": "starting_after" - }, - "valueType": { - "_type": "primitive", - "primitive": "STRING" - }, - "availability": null, - "docs": null - } - ], - "extra-properties": false - }, - "referencedTypes": [], - "examples": [], - "availability": null, - "docs": null - }, - "type_users:User": { - "name": { - "name": { - "originalName": "User", - "camelCase": { - "unsafeName": "user", - "safeName": "user" - }, - "snakeCase": { - "unsafeName": "user", - "safeName": "user" - }, - "screamingSnakeCase": { - "unsafeName": "USER", - "safeName": "USER" - }, - "pascalCase": { - "unsafeName": "User", - "safeName": "User" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:User" - }, - "shape": { - "_type": "object", - "extends": [], - "properties": [ - { - "name": { - "name": { - "originalName": "name", - "camelCase": { - "unsafeName": "name", - "safeName": "name" - }, - "snakeCase": { - "unsafeName": "name", - "safeName": "name" - }, - "screamingSnakeCase": { - "unsafeName": "NAME", - "safeName": "NAME" - }, - "pascalCase": { - "unsafeName": "Name", - "safeName": "Name" - } - }, - "wireValue": "name" - }, - "valueType": { - "_type": "primitive", - "primitive": "STRING" - }, - "availability": null, - "docs": null - }, - { - "name": { - "name": { - "originalName": "id", - "camelCase": { - "unsafeName": "id", - "safeName": "id" - }, - "snakeCase": { - "unsafeName": "id", - "safeName": "id" - }, - "screamingSnakeCase": { - "unsafeName": "ID", - "safeName": "ID" - }, - "pascalCase": { - "unsafeName": "Id", - "safeName": "Id" - } - }, - "wireValue": "id" - }, - "valueType": { - "_type": "primitive", - "primitive": "INTEGER" - }, - "availability": null, - "docs": null - } - ], - "extra-properties": false - }, - "referencedTypes": [], - "examples": [], - "availability": null, - "docs": null - } - }, - "errors": {}, - "services": { - "service_users": { - "availability": null, - "name": { - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - } - }, - "displayName": null, - "basePath": { - "head": "/users", - "parts": [] - }, - "headers": [], - "pathParameters": [], - "endpoints": [ - { - "id": "endpoint_users.listWithCursorPagination", - "name": { - "originalName": "listWithCursorPagination", - "camelCase": { - "unsafeName": "listWithCursorPagination", - "safeName": "listWithCursorPagination" - }, - "snakeCase": { - "unsafeName": "list_with_cursor_pagination", - "safeName": "list_with_cursor_pagination" - }, - "screamingSnakeCase": { - "unsafeName": "LIST_WITH_CURSOR_PAGINATION", - "safeName": "LIST_WITH_CURSOR_PAGINATION" - }, - "pascalCase": { - "unsafeName": "ListWithCursorPagination", - "safeName": "ListWithCursorPagination" - } - }, - "displayName": null, - "auth": false, - "idempotent": false, - "baseUrl": null, - "method": "GET", - "path": { - "head": "", - "parts": [] - }, - "fullPath": { - "head": "/users", - "parts": [] - }, - "pathParameters": [], - "allPathParameters": [], - "queryParameters": [ - { - "name": { - "name": { - "originalName": "page", - "camelCase": { - "unsafeName": "page", - "safeName": "page" - }, - "snakeCase": { - "unsafeName": "page", - "safeName": "page" - }, - "screamingSnakeCase": { - "unsafeName": "PAGE", - "safeName": "PAGE" - }, - "pascalCase": { - "unsafeName": "Page", - "safeName": "Page" - } - }, - "wireValue": "page" - }, - "valueType": { - "_type": "container", - "container": { - "_type": "optional", - "optional": { - "_type": "primitive", - "primitive": "INTEGER" - } - } - }, - "allowMultiple": false, - "availability": null, - "docs": "Defaults to first page" - }, - { - "name": { - "name": { - "originalName": "per_page", - "camelCase": { - "unsafeName": "perPage", - "safeName": "perPage" - }, - "snakeCase": { - "unsafeName": "per_page", - "safeName": "per_page" - }, - "screamingSnakeCase": { - "unsafeName": "PER_PAGE", - "safeName": "PER_PAGE" - }, - "pascalCase": { - "unsafeName": "PerPage", - "safeName": "PerPage" - } - }, - "wireValue": "per_page" - }, - "valueType": { - "_type": "container", - "container": { - "_type": "optional", - "optional": { - "_type": "primitive", - "primitive": "INTEGER" - } - } - }, - "allowMultiple": false, - "availability": null, - "docs": "Defaults to per page" - }, - { - "name": { - "name": { - "originalName": "order", - "camelCase": { - "unsafeName": "order", - "safeName": "order" - }, - "snakeCase": { - "unsafeName": "order", - "safeName": "order" - }, - "screamingSnakeCase": { - "unsafeName": "ORDER", - "safeName": "ORDER" - }, - "pascalCase": { - "unsafeName": "Order", - "safeName": "Order" - } - }, - "wireValue": "order" - }, - "valueType": { - "_type": "container", - "container": { - "_type": "optional", - "optional": { - "_type": "named", - "name": { - "originalName": "Order", - "camelCase": { - "unsafeName": "order", - "safeName": "order" - }, - "snakeCase": { - "unsafeName": "order", - "safeName": "order" - }, - "screamingSnakeCase": { - "unsafeName": "ORDER", - "safeName": "ORDER" - }, - "pascalCase": { - "unsafeName": "Order", - "safeName": "Order" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:Order" - } - } - }, - "allowMultiple": false, - "availability": null, - "docs": null - }, - { - "name": { - "name": { - "originalName": "starting_after", - "camelCase": { - "unsafeName": "startingAfter", - "safeName": "startingAfter" - }, - "snakeCase": { - "unsafeName": "starting_after", - "safeName": "starting_after" - }, - "screamingSnakeCase": { - "unsafeName": "STARTING_AFTER", - "safeName": "STARTING_AFTER" - }, - "pascalCase": { - "unsafeName": "StartingAfter", - "safeName": "StartingAfter" - } - }, - "wireValue": "starting_after" - }, - "valueType": { - "_type": "container", - "container": { - "_type": "optional", - "optional": { - "_type": "primitive", - "primitive": "STRING" - } - } - }, - "allowMultiple": false, - "availability": null, - "docs": "The cursor used for pagination in order to fetch\nthe next page of results." - } - ], - "headers": [], - "requestBody": null, - "sdkRequest": { - "shape": { - "type": "wrapper", - "wrapperName": { - "originalName": "ListUsersCursorPaginationRequest", - "camelCase": { - "unsafeName": "listUsersCursorPaginationRequest", - "safeName": "listUsersCursorPaginationRequest" - }, - "snakeCase": { - "unsafeName": "list_users_cursor_pagination_request", - "safeName": "list_users_cursor_pagination_request" - }, - "screamingSnakeCase": { - "unsafeName": "LIST_USERS_CURSOR_PAGINATION_REQUEST", - "safeName": "LIST_USERS_CURSOR_PAGINATION_REQUEST" - }, - "pascalCase": { - "unsafeName": "ListUsersCursorPaginationRequest", - "safeName": "ListUsersCursorPaginationRequest" - } - }, - "bodyKey": { - "originalName": "body", - "camelCase": { - "unsafeName": "body", - "safeName": "body" - }, - "snakeCase": { - "unsafeName": "body", - "safeName": "body" - }, - "screamingSnakeCase": { - "unsafeName": "BODY", - "safeName": "BODY" - }, - "pascalCase": { - "unsafeName": "Body", - "safeName": "Body" - } - } - }, - "requestParameterName": { - "originalName": "request", - "camelCase": { - "unsafeName": "request", - "safeName": "request" - }, - "snakeCase": { - "unsafeName": "request", - "safeName": "request" - }, - "screamingSnakeCase": { - "unsafeName": "REQUEST", - "safeName": "REQUEST" - }, - "pascalCase": { - "unsafeName": "Request", - "safeName": "Request" - } - } - }, - "response": { - "body": { - "type": "json", - "value": { - "type": "response", - "responseBodyType": { - "_type": "named", - "name": { - "originalName": "ListUsersPaginationResponse", - "camelCase": { - "unsafeName": "listUsersPaginationResponse", - "safeName": "listUsersPaginationResponse" - }, - "snakeCase": { - "unsafeName": "list_users_pagination_response", - "safeName": "list_users_pagination_response" - }, - "screamingSnakeCase": { - "unsafeName": "LIST_USERS_PAGINATION_RESPONSE", - "safeName": "LIST_USERS_PAGINATION_RESPONSE" - }, - "pascalCase": { - "unsafeName": "ListUsersPaginationResponse", - "safeName": "ListUsersPaginationResponse" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:ListUsersPaginationResponse" - }, - "docs": null - } - }, - "status-code": null - }, - "errors": [], - "examples": [ - { - "exampleType": "generated", - "url": "", - "rootPathParameters": [], - "servicePathParameters": [], - "endpointPathParameters": [], - "serviceHeaders": [], - "endpointHeaders": [], - "queryParameters": [ - { - "name": { - "name": { - "originalName": "page", - "camelCase": { - "unsafeName": "page", - "safeName": "page" - }, - "snakeCase": { - "unsafeName": "page", - "safeName": "page" - }, - "screamingSnakeCase": { - "unsafeName": "PAGE", - "safeName": "PAGE" - }, - "pascalCase": { - "unsafeName": "Page", - "safeName": "Page" - } - }, - "wireValue": "page" - }, - "value": { - "shape": { - "type": "primitive", - "primitive": { - "type": "integer", - "integer": 1 - } - }, - "jsonExample": 1 - } - }, - { - "name": { - "name": { - "originalName": "per_page", - "camelCase": { - "unsafeName": "perPage", - "safeName": "perPage" - }, - "snakeCase": { - "unsafeName": "per_page", - "safeName": "per_page" - }, - "screamingSnakeCase": { - "unsafeName": "PER_PAGE", - "safeName": "PER_PAGE" - }, - "pascalCase": { - "unsafeName": "PerPage", - "safeName": "PerPage" - } - }, - "wireValue": "per_page" - }, - "value": { - "shape": { - "type": "primitive", - "primitive": { - "type": "integer", - "integer": 1 - } - }, - "jsonExample": 1 - } - }, - { - "name": { - "name": { - "originalName": "order", - "camelCase": { - "unsafeName": "order", - "safeName": "order" - }, - "snakeCase": { - "unsafeName": "order", - "safeName": "order" - }, - "screamingSnakeCase": { - "unsafeName": "ORDER", - "safeName": "ORDER" - }, - "pascalCase": { - "unsafeName": "Order", - "safeName": "Order" - } - }, - "wireValue": "order" - }, - "value": { - "shape": { - "type": "named", - "typeName": { - "name": { - "originalName": "Order", - "camelCase": { - "unsafeName": "order", - "safeName": "order" - }, - "snakeCase": { - "unsafeName": "order", - "safeName": "order" - }, - "screamingSnakeCase": { - "unsafeName": "ORDER", - "safeName": "ORDER" - }, - "pascalCase": { - "unsafeName": "Order", - "safeName": "Order" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:Order" - }, - "shape": { - "type": "enum", - "value": { - "name": { - "originalName": "asc", - "camelCase": { - "unsafeName": "asc", - "safeName": "asc" - }, - "snakeCase": { - "unsafeName": "asc", - "safeName": "asc" - }, - "screamingSnakeCase": { - "unsafeName": "ASC", - "safeName": "ASC" - }, - "pascalCase": { - "unsafeName": "Asc", - "safeName": "Asc" - } - }, - "wireValue": "asc" - } - } - }, - "jsonExample": "asc" - } - }, - { - "name": { - "name": { - "originalName": "starting_after", - "camelCase": { - "unsafeName": "startingAfter", - "safeName": "startingAfter" - }, - "snakeCase": { - "unsafeName": "starting_after", - "safeName": "starting_after" - }, - "screamingSnakeCase": { - "unsafeName": "STARTING_AFTER", - "safeName": "STARTING_AFTER" - }, - "pascalCase": { - "unsafeName": "StartingAfter", - "safeName": "StartingAfter" - } - }, - "wireValue": "starting_after" - }, - "value": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "string" - } - } - }, - "jsonExample": "string" - } - } - ], - "request": null, - "name": null, - "codeSamples": null, - "response": { - "type": "ok", - "body": { - "shape": { - "type": "named", - "typeName": { - "name": { - "originalName": "ListUsersPaginationResponse", - "camelCase": { - "unsafeName": "listUsersPaginationResponse", - "safeName": "listUsersPaginationResponse" - }, - "snakeCase": { - "unsafeName": "list_users_pagination_response", - "safeName": "list_users_pagination_response" - }, - "screamingSnakeCase": { - "unsafeName": "LIST_USERS_PAGINATION_RESPONSE", - "safeName": "LIST_USERS_PAGINATION_RESPONSE" - }, - "pascalCase": { - "unsafeName": "ListUsersPaginationResponse", - "safeName": "ListUsersPaginationResponse" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:ListUsersPaginationResponse" - }, - "shape": { - "type": "object", - "properties": [ - { - "name": { - "name": { - "originalName": "page", - "camelCase": { - "unsafeName": "page", - "safeName": "page" - }, - "snakeCase": { - "unsafeName": "page", - "safeName": "page" - }, - "screamingSnakeCase": { - "unsafeName": "PAGE", - "safeName": "PAGE" - }, - "pascalCase": { - "unsafeName": "Page", - "safeName": "Page" - } - }, - "wireValue": "page" - }, - "value": { - "shape": { - "type": "named", - "typeName": { - "name": { - "originalName": "Page", - "camelCase": { - "unsafeName": "page", - "safeName": "page" - }, - "snakeCase": { - "unsafeName": "page", - "safeName": "page" - }, - "screamingSnakeCase": { - "unsafeName": "PAGE", - "safeName": "PAGE" - }, - "pascalCase": { - "unsafeName": "Page", - "safeName": "Page" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:Page" - }, - "shape": { - "type": "object", - "properties": [] - } - }, - "jsonExample": {} - }, - "originalTypeDeclaration": { - "name": { - "originalName": "ListUsersPaginationResponse", - "camelCase": { - "unsafeName": "listUsersPaginationResponse", - "safeName": "listUsersPaginationResponse" - }, - "snakeCase": { - "unsafeName": "list_users_pagination_response", - "safeName": "list_users_pagination_response" - }, - "screamingSnakeCase": { - "unsafeName": "LIST_USERS_PAGINATION_RESPONSE", - "safeName": "LIST_USERS_PAGINATION_RESPONSE" - }, - "pascalCase": { - "unsafeName": "ListUsersPaginationResponse", - "safeName": "ListUsersPaginationResponse" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:ListUsersPaginationResponse" - } - }, - { - "name": { - "name": { - "originalName": "total_count", - "camelCase": { - "unsafeName": "totalCount", - "safeName": "totalCount" - }, - "snakeCase": { - "unsafeName": "total_count", - "safeName": "total_count" - }, - "screamingSnakeCase": { - "unsafeName": "TOTAL_COUNT", - "safeName": "TOTAL_COUNT" - }, - "pascalCase": { - "unsafeName": "TotalCount", - "safeName": "TotalCount" - } - }, - "wireValue": "total_count" - }, - "value": { - "shape": { - "type": "primitive", - "primitive": { - "type": "integer", - "integer": 1 - } - }, - "jsonExample": 1 - }, - "originalTypeDeclaration": { - "name": { - "originalName": "ListUsersPaginationResponse", - "camelCase": { - "unsafeName": "listUsersPaginationResponse", - "safeName": "listUsersPaginationResponse" - }, - "snakeCase": { - "unsafeName": "list_users_pagination_response", - "safeName": "list_users_pagination_response" - }, - "screamingSnakeCase": { - "unsafeName": "LIST_USERS_PAGINATION_RESPONSE", - "safeName": "LIST_USERS_PAGINATION_RESPONSE" - }, - "pascalCase": { - "unsafeName": "ListUsersPaginationResponse", - "safeName": "ListUsersPaginationResponse" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:ListUsersPaginationResponse" - } - }, - { - "name": { - "name": { - "originalName": "data", - "camelCase": { - "unsafeName": "data", - "safeName": "data" - }, - "snakeCase": { - "unsafeName": "data", - "safeName": "data" - }, - "screamingSnakeCase": { - "unsafeName": "DATA", - "safeName": "DATA" - }, - "pascalCase": { - "unsafeName": "Data", - "safeName": "Data" - } - }, - "wireValue": "data" - }, - "value": { - "shape": { - "type": "container", - "container": { - "type": "list", - "list": [ - { - "shape": { - "type": "named", - "typeName": { - "name": { - "originalName": "User", - "camelCase": { - "unsafeName": "user", - "safeName": "user" - }, - "snakeCase": { - "unsafeName": "user", - "safeName": "user" - }, - "screamingSnakeCase": { - "unsafeName": "USER", - "safeName": "USER" - }, - "pascalCase": { - "unsafeName": "User", - "safeName": "User" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:User" - }, - "shape": { - "type": "object", - "properties": [] - } - }, - "jsonExample": {} - } - ] - } - }, - "jsonExample": [ - {} - ] - }, - "originalTypeDeclaration": { - "name": { - "originalName": "ListUsersPaginationResponse", - "camelCase": { - "unsafeName": "listUsersPaginationResponse", - "safeName": "listUsersPaginationResponse" - }, - "snakeCase": { - "unsafeName": "list_users_pagination_response", - "safeName": "list_users_pagination_response" - }, - "screamingSnakeCase": { - "unsafeName": "LIST_USERS_PAGINATION_RESPONSE", - "safeName": "LIST_USERS_PAGINATION_RESPONSE" - }, - "pascalCase": { - "unsafeName": "ListUsersPaginationResponse", - "safeName": "ListUsersPaginationResponse" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:ListUsersPaginationResponse" - } - } - ] - } - }, - "jsonExample": { - "page": {}, - "total_count": 1, - "data": [ - {} - ] - } - } - }, - "docs": null - } - ], - "pagination": { - "type": "cursor", - "page": { - "name": { - "name": { - "originalName": "starting_after", - "camelCase": { - "unsafeName": "startingAfter", - "safeName": "startingAfter" - }, - "snakeCase": { - "unsafeName": "starting_after", - "safeName": "starting_after" - }, - "screamingSnakeCase": { - "unsafeName": "STARTING_AFTER", - "safeName": "STARTING_AFTER" - }, - "pascalCase": { - "unsafeName": "StartingAfter", - "safeName": "StartingAfter" - } - }, - "wireValue": "starting_after" - }, - "valueType": { - "_type": "container", - "container": { - "_type": "optional", - "optional": { - "_type": "primitive", - "primitive": "STRING" - } - } - }, - "allowMultiple": false, - "availability": null, - "docs": "The cursor used for pagination in order to fetch\nthe next page of results." - }, - "next": { - "propertyPath": [ - { - "originalName": "page", - "camelCase": { - "unsafeName": "page", - "safeName": "page" - }, - "snakeCase": { - "unsafeName": "page", - "safeName": "page" - }, - "screamingSnakeCase": { - "unsafeName": "PAGE", - "safeName": "PAGE" - }, - "pascalCase": { - "unsafeName": "Page", - "safeName": "Page" - } - }, - { - "originalName": "next", - "camelCase": { - "unsafeName": "next", - "safeName": "next" - }, - "snakeCase": { - "unsafeName": "next", - "safeName": "next" - }, - "screamingSnakeCase": { - "unsafeName": "NEXT", - "safeName": "NEXT" - }, - "pascalCase": { - "unsafeName": "Next", - "safeName": "Next" - } - } - ], - "property": { - "name": { - "name": { - "originalName": "starting_after", - "camelCase": { - "unsafeName": "startingAfter", - "safeName": "startingAfter" - }, - "snakeCase": { - "unsafeName": "starting_after", - "safeName": "starting_after" - }, - "screamingSnakeCase": { - "unsafeName": "STARTING_AFTER", - "safeName": "STARTING_AFTER" - }, - "pascalCase": { - "unsafeName": "StartingAfter", - "safeName": "StartingAfter" - } - }, - "wireValue": "starting_after" - }, - "valueType": { - "_type": "primitive", - "primitive": "STRING" - }, - "availability": null, - "docs": null - } - }, - "results": { - "propertyPath": [], - "property": { - "name": { - "name": { - "originalName": "data", - "camelCase": { - "unsafeName": "data", - "safeName": "data" - }, - "snakeCase": { - "unsafeName": "data", - "safeName": "data" - }, - "screamingSnakeCase": { - "unsafeName": "DATA", - "safeName": "DATA" - }, - "pascalCase": { - "unsafeName": "Data", - "safeName": "Data" - } - }, - "wireValue": "data" - }, - "valueType": { - "_type": "container", - "container": { - "_type": "list", - "list": { - "_type": "named", - "name": { - "originalName": "User", - "camelCase": { - "unsafeName": "user", - "safeName": "user" - }, - "snakeCase": { - "unsafeName": "user", - "safeName": "user" - }, - "screamingSnakeCase": { - "unsafeName": "USER", - "safeName": "USER" - }, - "pascalCase": { - "unsafeName": "User", - "safeName": "User" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:User" - } - } - }, - "availability": null, - "docs": null - } - } - }, - "availability": null, - "docs": null - }, - { - "id": "endpoint_users.listWithOffsetPagination", - "name": { - "originalName": "listWithOffsetPagination", - "camelCase": { - "unsafeName": "listWithOffsetPagination", - "safeName": "listWithOffsetPagination" - }, - "snakeCase": { - "unsafeName": "list_with_offset_pagination", - "safeName": "list_with_offset_pagination" - }, - "screamingSnakeCase": { - "unsafeName": "LIST_WITH_OFFSET_PAGINATION", - "safeName": "LIST_WITH_OFFSET_PAGINATION" - }, - "pascalCase": { - "unsafeName": "ListWithOffsetPagination", - "safeName": "ListWithOffsetPagination" - } - }, - "displayName": null, - "auth": false, - "idempotent": false, - "baseUrl": null, - "method": "GET", - "path": { - "head": "", - "parts": [] - }, - "fullPath": { - "head": "/users", - "parts": [] - }, - "pathParameters": [], - "allPathParameters": [], - "queryParameters": [ - { - "name": { - "name": { - "originalName": "page", - "camelCase": { - "unsafeName": "page", - "safeName": "page" - }, - "snakeCase": { - "unsafeName": "page", - "safeName": "page" - }, - "screamingSnakeCase": { - "unsafeName": "PAGE", - "safeName": "PAGE" - }, - "pascalCase": { - "unsafeName": "Page", - "safeName": "Page" - } - }, - "wireValue": "page" - }, - "valueType": { - "_type": "container", - "container": { - "_type": "optional", - "optional": { - "_type": "primitive", - "primitive": "INTEGER" - } - } - }, - "allowMultiple": false, - "availability": null, - "docs": "Defaults to first page" - }, - { - "name": { - "name": { - "originalName": "per_page", - "camelCase": { - "unsafeName": "perPage", - "safeName": "perPage" - }, - "snakeCase": { - "unsafeName": "per_page", - "safeName": "per_page" - }, - "screamingSnakeCase": { - "unsafeName": "PER_PAGE", - "safeName": "PER_PAGE" - }, - "pascalCase": { - "unsafeName": "PerPage", - "safeName": "PerPage" - } - }, - "wireValue": "per_page" - }, - "valueType": { - "_type": "container", - "container": { - "_type": "optional", - "optional": { - "_type": "primitive", - "primitive": "INTEGER" - } - } - }, - "allowMultiple": false, - "availability": null, - "docs": "Defaults to per page" - }, - { - "name": { - "name": { - "originalName": "order", - "camelCase": { - "unsafeName": "order", - "safeName": "order" - }, - "snakeCase": { - "unsafeName": "order", - "safeName": "order" - }, - "screamingSnakeCase": { - "unsafeName": "ORDER", - "safeName": "ORDER" - }, - "pascalCase": { - "unsafeName": "Order", - "safeName": "Order" - } - }, - "wireValue": "order" - }, - "valueType": { - "_type": "container", - "container": { - "_type": "optional", - "optional": { - "_type": "named", - "name": { - "originalName": "Order", - "camelCase": { - "unsafeName": "order", - "safeName": "order" - }, - "snakeCase": { - "unsafeName": "order", - "safeName": "order" - }, - "screamingSnakeCase": { - "unsafeName": "ORDER", - "safeName": "ORDER" - }, - "pascalCase": { - "unsafeName": "Order", - "safeName": "Order" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:Order" - } - } - }, - "allowMultiple": false, - "availability": null, - "docs": null - }, - { - "name": { - "name": { - "originalName": "starting_after", - "camelCase": { - "unsafeName": "startingAfter", - "safeName": "startingAfter" - }, - "snakeCase": { - "unsafeName": "starting_after", - "safeName": "starting_after" - }, - "screamingSnakeCase": { - "unsafeName": "STARTING_AFTER", - "safeName": "STARTING_AFTER" - }, - "pascalCase": { - "unsafeName": "StartingAfter", - "safeName": "StartingAfter" - } - }, - "wireValue": "starting_after" - }, - "valueType": { - "_type": "container", - "container": { - "_type": "optional", - "optional": { - "_type": "primitive", - "primitive": "STRING" - } - } - }, - "allowMultiple": false, - "availability": null, - "docs": "The cursor used for pagination in order to fetch\nthe next page of results." - } - ], - "headers": [], - "requestBody": null, - "sdkRequest": { - "shape": { - "type": "wrapper", - "wrapperName": { - "originalName": "ListUsersOffsetPaginationRequest", - "camelCase": { - "unsafeName": "listUsersOffsetPaginationRequest", - "safeName": "listUsersOffsetPaginationRequest" - }, - "snakeCase": { - "unsafeName": "list_users_offset_pagination_request", - "safeName": "list_users_offset_pagination_request" - }, - "screamingSnakeCase": { - "unsafeName": "LIST_USERS_OFFSET_PAGINATION_REQUEST", - "safeName": "LIST_USERS_OFFSET_PAGINATION_REQUEST" - }, - "pascalCase": { - "unsafeName": "ListUsersOffsetPaginationRequest", - "safeName": "ListUsersOffsetPaginationRequest" - } - }, - "bodyKey": { - "originalName": "body", - "camelCase": { - "unsafeName": "body", - "safeName": "body" - }, - "snakeCase": { - "unsafeName": "body", - "safeName": "body" - }, - "screamingSnakeCase": { - "unsafeName": "BODY", - "safeName": "BODY" - }, - "pascalCase": { - "unsafeName": "Body", - "safeName": "Body" - } - } - }, - "requestParameterName": { - "originalName": "request", - "camelCase": { - "unsafeName": "request", - "safeName": "request" - }, - "snakeCase": { - "unsafeName": "request", - "safeName": "request" - }, - "screamingSnakeCase": { - "unsafeName": "REQUEST", - "safeName": "REQUEST" - }, - "pascalCase": { - "unsafeName": "Request", - "safeName": "Request" - } - } - }, - "response": { - "body": { - "type": "json", - "value": { - "type": "response", - "responseBodyType": { - "_type": "named", - "name": { - "originalName": "ListUsersPaginationResponse", - "camelCase": { - "unsafeName": "listUsersPaginationResponse", - "safeName": "listUsersPaginationResponse" - }, - "snakeCase": { - "unsafeName": "list_users_pagination_response", - "safeName": "list_users_pagination_response" - }, - "screamingSnakeCase": { - "unsafeName": "LIST_USERS_PAGINATION_RESPONSE", - "safeName": "LIST_USERS_PAGINATION_RESPONSE" - }, - "pascalCase": { - "unsafeName": "ListUsersPaginationResponse", - "safeName": "ListUsersPaginationResponse" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:ListUsersPaginationResponse" - }, - "docs": null - } - }, - "status-code": null - }, - "errors": [], - "examples": [ - { - "exampleType": "generated", - "url": "", - "rootPathParameters": [], - "servicePathParameters": [], - "endpointPathParameters": [], - "serviceHeaders": [], - "endpointHeaders": [], - "queryParameters": [ - { - "name": { - "name": { - "originalName": "page", - "camelCase": { - "unsafeName": "page", - "safeName": "page" - }, - "snakeCase": { - "unsafeName": "page", - "safeName": "page" - }, - "screamingSnakeCase": { - "unsafeName": "PAGE", - "safeName": "PAGE" - }, - "pascalCase": { - "unsafeName": "Page", - "safeName": "Page" - } - }, - "wireValue": "page" - }, - "value": { - "shape": { - "type": "primitive", - "primitive": { - "type": "integer", - "integer": 1 - } - }, - "jsonExample": 1 - } - }, - { - "name": { - "name": { - "originalName": "per_page", - "camelCase": { - "unsafeName": "perPage", - "safeName": "perPage" - }, - "snakeCase": { - "unsafeName": "per_page", - "safeName": "per_page" - }, - "screamingSnakeCase": { - "unsafeName": "PER_PAGE", - "safeName": "PER_PAGE" - }, - "pascalCase": { - "unsafeName": "PerPage", - "safeName": "PerPage" - } - }, - "wireValue": "per_page" - }, - "value": { - "shape": { - "type": "primitive", - "primitive": { - "type": "integer", - "integer": 1 - } - }, - "jsonExample": 1 - } - }, - { - "name": { - "name": { - "originalName": "order", - "camelCase": { - "unsafeName": "order", - "safeName": "order" - }, - "snakeCase": { - "unsafeName": "order", - "safeName": "order" - }, - "screamingSnakeCase": { - "unsafeName": "ORDER", - "safeName": "ORDER" - }, - "pascalCase": { - "unsafeName": "Order", - "safeName": "Order" - } - }, - "wireValue": "order" - }, - "value": { - "shape": { - "type": "named", - "typeName": { - "name": { - "originalName": "Order", - "camelCase": { - "unsafeName": "order", - "safeName": "order" - }, - "snakeCase": { - "unsafeName": "order", - "safeName": "order" - }, - "screamingSnakeCase": { - "unsafeName": "ORDER", - "safeName": "ORDER" - }, - "pascalCase": { - "unsafeName": "Order", - "safeName": "Order" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:Order" - }, - "shape": { - "type": "enum", - "value": { - "name": { - "originalName": "asc", - "camelCase": { - "unsafeName": "asc", - "safeName": "asc" - }, - "snakeCase": { - "unsafeName": "asc", - "safeName": "asc" - }, - "screamingSnakeCase": { - "unsafeName": "ASC", - "safeName": "ASC" - }, - "pascalCase": { - "unsafeName": "Asc", - "safeName": "Asc" - } - }, - "wireValue": "asc" - } - } - }, - "jsonExample": "asc" - } - }, - { - "name": { - "name": { - "originalName": "starting_after", - "camelCase": { - "unsafeName": "startingAfter", - "safeName": "startingAfter" - }, - "snakeCase": { - "unsafeName": "starting_after", - "safeName": "starting_after" - }, - "screamingSnakeCase": { - "unsafeName": "STARTING_AFTER", - "safeName": "STARTING_AFTER" - }, - "pascalCase": { - "unsafeName": "StartingAfter", - "safeName": "StartingAfter" - } - }, - "wireValue": "starting_after" - }, - "value": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "string" - } - } - }, - "jsonExample": "string" - } - } - ], - "request": null, - "name": null, - "codeSamples": null, - "response": { - "type": "ok", - "body": { - "shape": { - "type": "named", - "typeName": { - "name": { - "originalName": "ListUsersPaginationResponse", - "camelCase": { - "unsafeName": "listUsersPaginationResponse", - "safeName": "listUsersPaginationResponse" - }, - "snakeCase": { - "unsafeName": "list_users_pagination_response", - "safeName": "list_users_pagination_response" - }, - "screamingSnakeCase": { - "unsafeName": "LIST_USERS_PAGINATION_RESPONSE", - "safeName": "LIST_USERS_PAGINATION_RESPONSE" - }, - "pascalCase": { - "unsafeName": "ListUsersPaginationResponse", - "safeName": "ListUsersPaginationResponse" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:ListUsersPaginationResponse" - }, - "shape": { - "type": "object", - "properties": [ - { - "name": { - "name": { - "originalName": "page", - "camelCase": { - "unsafeName": "page", - "safeName": "page" - }, - "snakeCase": { - "unsafeName": "page", - "safeName": "page" - }, - "screamingSnakeCase": { - "unsafeName": "PAGE", - "safeName": "PAGE" - }, - "pascalCase": { - "unsafeName": "Page", - "safeName": "Page" - } - }, - "wireValue": "page" - }, - "value": { - "shape": { - "type": "named", - "typeName": { - "name": { - "originalName": "Page", - "camelCase": { - "unsafeName": "page", - "safeName": "page" - }, - "snakeCase": { - "unsafeName": "page", - "safeName": "page" - }, - "screamingSnakeCase": { - "unsafeName": "PAGE", - "safeName": "PAGE" - }, - "pascalCase": { - "unsafeName": "Page", - "safeName": "Page" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:Page" - }, - "shape": { - "type": "object", - "properties": [] - } - }, - "jsonExample": {} - }, - "originalTypeDeclaration": { - "name": { - "originalName": "ListUsersPaginationResponse", - "camelCase": { - "unsafeName": "listUsersPaginationResponse", - "safeName": "listUsersPaginationResponse" - }, - "snakeCase": { - "unsafeName": "list_users_pagination_response", - "safeName": "list_users_pagination_response" - }, - "screamingSnakeCase": { - "unsafeName": "LIST_USERS_PAGINATION_RESPONSE", - "safeName": "LIST_USERS_PAGINATION_RESPONSE" - }, - "pascalCase": { - "unsafeName": "ListUsersPaginationResponse", - "safeName": "ListUsersPaginationResponse" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:ListUsersPaginationResponse" - } - }, - { - "name": { - "name": { - "originalName": "total_count", - "camelCase": { - "unsafeName": "totalCount", - "safeName": "totalCount" - }, - "snakeCase": { - "unsafeName": "total_count", - "safeName": "total_count" - }, - "screamingSnakeCase": { - "unsafeName": "TOTAL_COUNT", - "safeName": "TOTAL_COUNT" - }, - "pascalCase": { - "unsafeName": "TotalCount", - "safeName": "TotalCount" - } - }, - "wireValue": "total_count" - }, - "value": { - "shape": { - "type": "primitive", - "primitive": { - "type": "integer", - "integer": 1 - } - }, - "jsonExample": 1 - }, - "originalTypeDeclaration": { - "name": { - "originalName": "ListUsersPaginationResponse", - "camelCase": { - "unsafeName": "listUsersPaginationResponse", - "safeName": "listUsersPaginationResponse" - }, - "snakeCase": { - "unsafeName": "list_users_pagination_response", - "safeName": "list_users_pagination_response" - }, - "screamingSnakeCase": { - "unsafeName": "LIST_USERS_PAGINATION_RESPONSE", - "safeName": "LIST_USERS_PAGINATION_RESPONSE" - }, - "pascalCase": { - "unsafeName": "ListUsersPaginationResponse", - "safeName": "ListUsersPaginationResponse" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:ListUsersPaginationResponse" - } - }, - { - "name": { - "name": { - "originalName": "data", - "camelCase": { - "unsafeName": "data", - "safeName": "data" - }, - "snakeCase": { - "unsafeName": "data", - "safeName": "data" - }, - "screamingSnakeCase": { - "unsafeName": "DATA", - "safeName": "DATA" - }, - "pascalCase": { - "unsafeName": "Data", - "safeName": "Data" - } - }, - "wireValue": "data" - }, - "value": { - "shape": { - "type": "container", - "container": { - "type": "list", - "list": [ - { - "shape": { - "type": "named", - "typeName": { - "name": { - "originalName": "User", - "camelCase": { - "unsafeName": "user", - "safeName": "user" - }, - "snakeCase": { - "unsafeName": "user", - "safeName": "user" - }, - "screamingSnakeCase": { - "unsafeName": "USER", - "safeName": "USER" - }, - "pascalCase": { - "unsafeName": "User", - "safeName": "User" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:User" - }, - "shape": { - "type": "object", - "properties": [] - } - }, - "jsonExample": {} - } - ] - } - }, - "jsonExample": [ - {} - ] - }, - "originalTypeDeclaration": { - "name": { - "originalName": "ListUsersPaginationResponse", - "camelCase": { - "unsafeName": "listUsersPaginationResponse", - "safeName": "listUsersPaginationResponse" - }, - "snakeCase": { - "unsafeName": "list_users_pagination_response", - "safeName": "list_users_pagination_response" - }, - "screamingSnakeCase": { - "unsafeName": "LIST_USERS_PAGINATION_RESPONSE", - "safeName": "LIST_USERS_PAGINATION_RESPONSE" - }, - "pascalCase": { - "unsafeName": "ListUsersPaginationResponse", - "safeName": "ListUsersPaginationResponse" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:ListUsersPaginationResponse" - } - } - ] - } - }, - "jsonExample": { - "page": {}, - "total_count": 1, - "data": [ - {} - ] - } - } - }, - "docs": null - } - ], - "pagination": { - "type": "offset", - "page": { - "name": { - "name": { - "originalName": "page", - "camelCase": { - "unsafeName": "page", - "safeName": "page" - }, - "snakeCase": { - "unsafeName": "page", - "safeName": "page" - }, - "screamingSnakeCase": { - "unsafeName": "PAGE", - "safeName": "PAGE" - }, - "pascalCase": { - "unsafeName": "Page", - "safeName": "Page" - } - }, - "wireValue": "page" - }, - "valueType": { - "_type": "container", - "container": { - "_type": "optional", - "optional": { - "_type": "primitive", - "primitive": "INTEGER" - } - } - }, - "allowMultiple": false, - "availability": null, - "docs": "Defaults to first page" - }, - "results": { - "propertyPath": [], - "property": { - "name": { - "name": { - "originalName": "data", - "camelCase": { - "unsafeName": "data", - "safeName": "data" - }, - "snakeCase": { - "unsafeName": "data", - "safeName": "data" - }, - "screamingSnakeCase": { - "unsafeName": "DATA", - "safeName": "DATA" - }, - "pascalCase": { - "unsafeName": "Data", - "safeName": "Data" - } - }, - "wireValue": "data" - }, - "valueType": { - "_type": "container", - "container": { - "_type": "list", - "list": { - "_type": "named", - "name": { - "originalName": "User", - "camelCase": { - "unsafeName": "user", - "safeName": "user" - }, - "snakeCase": { - "unsafeName": "user", - "safeName": "user" - }, - "screamingSnakeCase": { - "unsafeName": "USER", - "safeName": "USER" - }, - "pascalCase": { - "unsafeName": "User", - "safeName": "User" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:User" - } - } - }, - "availability": null, - "docs": null - } - } - }, - "availability": null, - "docs": null - }, - { - "id": "endpoint_users.listWithExtendedResults", - "name": { - "originalName": "listWithExtendedResults", - "camelCase": { - "unsafeName": "listWithExtendedResults", - "safeName": "listWithExtendedResults" - }, - "snakeCase": { - "unsafeName": "list_with_extended_results", - "safeName": "list_with_extended_results" - }, - "screamingSnakeCase": { - "unsafeName": "LIST_WITH_EXTENDED_RESULTS", - "safeName": "LIST_WITH_EXTENDED_RESULTS" - }, - "pascalCase": { - "unsafeName": "ListWithExtendedResults", - "safeName": "ListWithExtendedResults" - } - }, - "displayName": null, - "auth": false, - "idempotent": false, - "baseUrl": null, - "method": "GET", - "path": { - "head": "", - "parts": [] - }, - "fullPath": { - "head": "/users", - "parts": [] - }, - "pathParameters": [], - "allPathParameters": [], - "queryParameters": [ - { - "name": { - "name": { - "originalName": "cursor", - "camelCase": { - "unsafeName": "cursor", - "safeName": "cursor" - }, - "snakeCase": { - "unsafeName": "cursor", - "safeName": "cursor" - }, - "screamingSnakeCase": { - "unsafeName": "CURSOR", - "safeName": "CURSOR" - }, - "pascalCase": { - "unsafeName": "Cursor", - "safeName": "Cursor" - } - }, - "wireValue": "cursor" - }, - "valueType": { - "_type": "container", - "container": { - "_type": "optional", - "optional": { - "_type": "primitive", - "primitive": "UUID" - } - } - }, - "allowMultiple": false, - "availability": null, - "docs": null - } - ], - "headers": [], - "requestBody": null, - "sdkRequest": { - "shape": { - "type": "wrapper", - "wrapperName": { - "originalName": "ListUsersExtendedRequest", - "camelCase": { - "unsafeName": "listUsersExtendedRequest", - "safeName": "listUsersExtendedRequest" - }, - "snakeCase": { - "unsafeName": "list_users_extended_request", - "safeName": "list_users_extended_request" - }, - "screamingSnakeCase": { - "unsafeName": "LIST_USERS_EXTENDED_REQUEST", - "safeName": "LIST_USERS_EXTENDED_REQUEST" - }, - "pascalCase": { - "unsafeName": "ListUsersExtendedRequest", - "safeName": "ListUsersExtendedRequest" - } - }, - "bodyKey": { - "originalName": "body", - "camelCase": { - "unsafeName": "body", - "safeName": "body" - }, - "snakeCase": { - "unsafeName": "body", - "safeName": "body" - }, - "screamingSnakeCase": { - "unsafeName": "BODY", - "safeName": "BODY" - }, - "pascalCase": { - "unsafeName": "Body", - "safeName": "Body" - } - } - }, - "requestParameterName": { - "originalName": "request", - "camelCase": { - "unsafeName": "request", - "safeName": "request" - }, - "snakeCase": { - "unsafeName": "request", - "safeName": "request" - }, - "screamingSnakeCase": { - "unsafeName": "REQUEST", - "safeName": "REQUEST" - }, - "pascalCase": { - "unsafeName": "Request", - "safeName": "Request" - } - } - }, - "response": { - "body": { - "type": "json", - "value": { - "type": "response", - "responseBodyType": { - "_type": "named", - "name": { - "originalName": "ListUsersExtendedResponse", - "camelCase": { - "unsafeName": "listUsersExtendedResponse", - "safeName": "listUsersExtendedResponse" - }, - "snakeCase": { - "unsafeName": "list_users_extended_response", - "safeName": "list_users_extended_response" - }, - "screamingSnakeCase": { - "unsafeName": "LIST_USERS_EXTENDED_RESPONSE", - "safeName": "LIST_USERS_EXTENDED_RESPONSE" - }, - "pascalCase": { - "unsafeName": "ListUsersExtendedResponse", - "safeName": "ListUsersExtendedResponse" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:ListUsersExtendedResponse" - }, - "docs": null - } - }, - "status-code": null - }, - "errors": [], - "examples": [ - { - "exampleType": "generated", - "url": "", - "rootPathParameters": [], - "servicePathParameters": [], - "endpointPathParameters": [], - "serviceHeaders": [], - "endpointHeaders": [], - "queryParameters": [ - { - "name": { - "name": { - "originalName": "cursor", - "camelCase": { - "unsafeName": "cursor", - "safeName": "cursor" - }, - "snakeCase": { - "unsafeName": "cursor", - "safeName": "cursor" - }, - "screamingSnakeCase": { - "unsafeName": "CURSOR", - "safeName": "CURSOR" - }, - "pascalCase": { - "unsafeName": "Cursor", - "safeName": "Cursor" - } - }, - "wireValue": "cursor" - }, - "value": { - "shape": { - "type": "primitive", - "primitive": { - "type": "uuid", - "uuid": "d5e9c84f-c2b2-4bf4-b4b0-7ffd7a9ffc32" - } - }, - "jsonExample": "d5e9c84f-c2b2-4bf4-b4b0-7ffd7a9ffc32" - } - } - ], - "request": null, - "name": null, - "codeSamples": null, - "response": { - "type": "ok", - "body": { - "shape": { - "type": "named", - "typeName": { - "name": { - "originalName": "ListUsersExtendedResponse", - "camelCase": { - "unsafeName": "listUsersExtendedResponse", - "safeName": "listUsersExtendedResponse" - }, - "snakeCase": { - "unsafeName": "list_users_extended_response", - "safeName": "list_users_extended_response" - }, - "screamingSnakeCase": { - "unsafeName": "LIST_USERS_EXTENDED_RESPONSE", - "safeName": "LIST_USERS_EXTENDED_RESPONSE" - }, - "pascalCase": { - "unsafeName": "ListUsersExtendedResponse", - "safeName": "ListUsersExtendedResponse" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:ListUsersExtendedResponse" - }, - "shape": { - "type": "object", - "properties": [ - { - "name": { - "name": { - "originalName": "total_count", - "camelCase": { - "unsafeName": "totalCount", - "safeName": "totalCount" - }, - "snakeCase": { - "unsafeName": "total_count", - "safeName": "total_count" - }, - "screamingSnakeCase": { - "unsafeName": "TOTAL_COUNT", - "safeName": "TOTAL_COUNT" - }, - "pascalCase": { - "unsafeName": "TotalCount", - "safeName": "TotalCount" - } - }, - "wireValue": "total_count" - }, - "value": { - "shape": { - "type": "primitive", - "primitive": { - "type": "integer", - "integer": 1 - } - }, - "jsonExample": 1 - }, - "originalTypeDeclaration": { - "name": { - "originalName": "ListUsersExtendedResponse", - "camelCase": { - "unsafeName": "listUsersExtendedResponse", - "safeName": "listUsersExtendedResponse" - }, - "snakeCase": { - "unsafeName": "list_users_extended_response", - "safeName": "list_users_extended_response" - }, - "screamingSnakeCase": { - "unsafeName": "LIST_USERS_EXTENDED_RESPONSE", - "safeName": "LIST_USERS_EXTENDED_RESPONSE" - }, - "pascalCase": { - "unsafeName": "ListUsersExtendedResponse", - "safeName": "ListUsersExtendedResponse" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:ListUsersExtendedResponse" - } - }, - { - "name": { - "name": { - "originalName": "data", - "camelCase": { - "unsafeName": "data", - "safeName": "data" - }, - "snakeCase": { - "unsafeName": "data", - "safeName": "data" - }, - "screamingSnakeCase": { - "unsafeName": "DATA", - "safeName": "DATA" - }, - "pascalCase": { - "unsafeName": "Data", - "safeName": "Data" - } - }, - "wireValue": "data" - }, - "value": { - "shape": { - "type": "named", - "typeName": { - "name": { - "originalName": "UserListContainer", - "camelCase": { - "unsafeName": "userListContainer", - "safeName": "userListContainer" - }, - "snakeCase": { - "unsafeName": "user_list_container", - "safeName": "user_list_container" - }, - "screamingSnakeCase": { - "unsafeName": "USER_LIST_CONTAINER", - "safeName": "USER_LIST_CONTAINER" - }, - "pascalCase": { - "unsafeName": "UserListContainer", - "safeName": "UserListContainer" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:UserListContainer" - }, - "shape": { - "type": "object", - "properties": [ - { - "name": { - "name": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - }, - "wireValue": "users" - }, - "value": { - "shape": { - "type": "container", - "container": { - "type": "list", - "list": [ - { - "shape": { - "type": "named", - "typeName": { - "name": { - "originalName": "User", - "camelCase": { - "unsafeName": "user", - "safeName": "user" - }, - "snakeCase": { - "unsafeName": "user", - "safeName": "user" - }, - "screamingSnakeCase": { - "unsafeName": "USER", - "safeName": "USER" - }, - "pascalCase": { - "unsafeName": "User", - "safeName": "User" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:User" - }, - "shape": { - "type": "object", - "properties": [] - } - }, - "jsonExample": {} - } - ] - } - }, - "jsonExample": [ - {} - ] - }, - "originalTypeDeclaration": { - "name": { - "originalName": "UserListContainer", - "camelCase": { - "unsafeName": "userListContainer", - "safeName": "userListContainer" - }, - "snakeCase": { - "unsafeName": "user_list_container", - "safeName": "user_list_container" - }, - "screamingSnakeCase": { - "unsafeName": "USER_LIST_CONTAINER", - "safeName": "USER_LIST_CONTAINER" - }, - "pascalCase": { - "unsafeName": "UserListContainer", - "safeName": "UserListContainer" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:UserListContainer" - } - } - ] - } - }, - "jsonExample": { - "users": [ - {} - ] - } - }, - "originalTypeDeclaration": { - "name": { - "originalName": "UserPage", - "camelCase": { - "unsafeName": "userPage", - "safeName": "userPage" - }, - "snakeCase": { - "unsafeName": "user_page", - "safeName": "user_page" - }, - "screamingSnakeCase": { - "unsafeName": "USER_PAGE", - "safeName": "USER_PAGE" - }, - "pascalCase": { - "unsafeName": "UserPage", - "safeName": "UserPage" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:UserPage" - } - }, - { - "name": { - "name": { - "originalName": "next", - "camelCase": { - "unsafeName": "next", - "safeName": "next" - }, - "snakeCase": { - "unsafeName": "next", - "safeName": "next" - }, - "screamingSnakeCase": { - "unsafeName": "NEXT", - "safeName": "NEXT" - }, - "pascalCase": { - "unsafeName": "Next", - "safeName": "Next" - } - }, - "wireValue": "next" - }, - "value": { - "shape": { - "type": "primitive", - "primitive": { - "type": "uuid", - "uuid": "d5e9c84f-c2b2-4bf4-b4b0-7ffd7a9ffc32" - } - }, - "jsonExample": "d5e9c84f-c2b2-4bf4-b4b0-7ffd7a9ffc32" - }, - "originalTypeDeclaration": { - "name": { - "originalName": "UserPage", - "camelCase": { - "unsafeName": "userPage", - "safeName": "userPage" - }, - "snakeCase": { - "unsafeName": "user_page", - "safeName": "user_page" - }, - "screamingSnakeCase": { - "unsafeName": "USER_PAGE", - "safeName": "USER_PAGE" - }, - "pascalCase": { - "unsafeName": "UserPage", - "safeName": "UserPage" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:UserPage" - } - } - ] - } - }, - "jsonExample": { - "total_count": 1, - "data": { - "users": [ - {} - ] - }, - "next": "d5e9c84f-c2b2-4bf4-b4b0-7ffd7a9ffc32" - } - } - }, - "docs": null - } - ], - "pagination": { - "type": "cursor", - "page": { - "name": { - "name": { - "originalName": "cursor", - "camelCase": { - "unsafeName": "cursor", - "safeName": "cursor" - }, - "snakeCase": { - "unsafeName": "cursor", - "safeName": "cursor" - }, - "screamingSnakeCase": { - "unsafeName": "CURSOR", - "safeName": "CURSOR" - }, - "pascalCase": { - "unsafeName": "Cursor", - "safeName": "Cursor" - } - }, - "wireValue": "cursor" - }, - "valueType": { - "_type": "container", - "container": { - "_type": "optional", - "optional": { - "_type": "primitive", - "primitive": "UUID" - } - } - }, - "allowMultiple": false, - "availability": null, - "docs": null - }, - "next": { - "propertyPath": [], - "property": { - "name": { - "name": { - "originalName": "next", - "camelCase": { - "unsafeName": "next", - "safeName": "next" - }, - "snakeCase": { - "unsafeName": "next", - "safeName": "next" - }, - "screamingSnakeCase": { - "unsafeName": "NEXT", - "safeName": "NEXT" - }, - "pascalCase": { - "unsafeName": "Next", - "safeName": "Next" - } - }, - "wireValue": "next" - }, - "valueType": { - "_type": "container", - "container": { - "_type": "optional", - "optional": { - "_type": "primitive", - "primitive": "UUID" - } - } - }, - "availability": null, - "docs": null - } - }, - "results": { - "propertyPath": [ - { - "originalName": "data", - "camelCase": { - "unsafeName": "data", - "safeName": "data" - }, - "snakeCase": { - "unsafeName": "data", - "safeName": "data" - }, - "screamingSnakeCase": { - "unsafeName": "DATA", - "safeName": "DATA" - }, - "pascalCase": { - "unsafeName": "Data", - "safeName": "Data" - } - } - ], - "property": { - "name": { - "name": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - }, - "wireValue": "users" - }, - "valueType": { - "_type": "container", - "container": { - "_type": "list", - "list": { - "_type": "named", - "name": { - "originalName": "User", - "camelCase": { - "unsafeName": "user", - "safeName": "user" - }, - "snakeCase": { - "unsafeName": "user", - "safeName": "user" - }, - "screamingSnakeCase": { - "unsafeName": "USER", - "safeName": "USER" - }, - "pascalCase": { - "unsafeName": "User", - "safeName": "User" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:User" - } - } - }, - "availability": null, - "docs": null - } - } - }, - "availability": null, - "docs": null - }, - { - "id": "endpoint_users.listUsernames", - "name": { - "originalName": "listUsernames", - "camelCase": { - "unsafeName": "listUsernames", - "safeName": "listUsernames" - }, - "snakeCase": { - "unsafeName": "list_usernames", - "safeName": "list_usernames" - }, - "screamingSnakeCase": { - "unsafeName": "LIST_USERNAMES", - "safeName": "LIST_USERNAMES" - }, - "pascalCase": { - "unsafeName": "ListUsernames", - "safeName": "ListUsernames" - } - }, - "displayName": null, - "auth": false, - "idempotent": false, - "baseUrl": null, - "method": "GET", - "path": { - "head": "", - "parts": [] - }, - "fullPath": { - "head": "/users", - "parts": [] - }, - "pathParameters": [], - "allPathParameters": [], - "queryParameters": [ - { - "name": { - "name": { - "originalName": "starting_after", - "camelCase": { - "unsafeName": "startingAfter", - "safeName": "startingAfter" - }, - "snakeCase": { - "unsafeName": "starting_after", - "safeName": "starting_after" - }, - "screamingSnakeCase": { - "unsafeName": "STARTING_AFTER", - "safeName": "STARTING_AFTER" - }, - "pascalCase": { - "unsafeName": "StartingAfter", - "safeName": "StartingAfter" - } - }, - "wireValue": "starting_after" - }, - "valueType": { - "_type": "container", - "container": { - "_type": "optional", - "optional": { - "_type": "primitive", - "primitive": "STRING" - } - } - }, - "allowMultiple": false, - "availability": null, - "docs": "The cursor used for pagination in order to fetch\nthe next page of results." - } - ], - "headers": [], - "requestBody": null, - "sdkRequest": { - "shape": { - "type": "wrapper", - "wrapperName": { - "originalName": "ListUsernamesRequest", - "camelCase": { - "unsafeName": "listUsernamesRequest", - "safeName": "listUsernamesRequest" - }, - "snakeCase": { - "unsafeName": "list_usernames_request", - "safeName": "list_usernames_request" - }, - "screamingSnakeCase": { - "unsafeName": "LIST_USERNAMES_REQUEST", - "safeName": "LIST_USERNAMES_REQUEST" - }, - "pascalCase": { - "unsafeName": "ListUsernamesRequest", - "safeName": "ListUsernamesRequest" - } - }, - "bodyKey": { - "originalName": "body", - "camelCase": { - "unsafeName": "body", - "safeName": "body" - }, - "snakeCase": { - "unsafeName": "body", - "safeName": "body" - }, - "screamingSnakeCase": { - "unsafeName": "BODY", - "safeName": "BODY" - }, - "pascalCase": { - "unsafeName": "Body", - "safeName": "Body" - } - } - }, - "requestParameterName": { - "originalName": "request", - "camelCase": { - "unsafeName": "request", - "safeName": "request" - }, - "snakeCase": { - "unsafeName": "request", - "safeName": "request" - }, - "screamingSnakeCase": { - "unsafeName": "REQUEST", - "safeName": "REQUEST" - }, - "pascalCase": { - "unsafeName": "Request", - "safeName": "Request" - } - } - }, - "response": { - "body": { - "type": "json", - "value": { - "type": "response", - "responseBodyType": { - "_type": "named", - "name": { - "originalName": "UsernameCursor", - "camelCase": { - "unsafeName": "usernameCursor", - "safeName": "usernameCursor" - }, - "snakeCase": { - "unsafeName": "username_cursor", - "safeName": "username_cursor" - }, - "screamingSnakeCase": { - "unsafeName": "USERNAME_CURSOR", - "safeName": "USERNAME_CURSOR" - }, - "pascalCase": { - "unsafeName": "UsernameCursor", - "safeName": "UsernameCursor" - } - }, - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "typeId": "type_:UsernameCursor" - }, - "docs": null - } - }, - "status-code": null - }, - "errors": [], - "examples": [ - { - "exampleType": "generated", - "url": "", - "rootPathParameters": [], - "servicePathParameters": [], - "endpointPathParameters": [], - "serviceHeaders": [], - "endpointHeaders": [], - "queryParameters": [ - { - "name": { - "name": { - "originalName": "starting_after", - "camelCase": { - "unsafeName": "startingAfter", - "safeName": "startingAfter" - }, - "snakeCase": { - "unsafeName": "starting_after", - "safeName": "starting_after" - }, - "screamingSnakeCase": { - "unsafeName": "STARTING_AFTER", - "safeName": "STARTING_AFTER" - }, - "pascalCase": { - "unsafeName": "StartingAfter", - "safeName": "StartingAfter" - } - }, - "wireValue": "starting_after" - }, - "value": { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "string" - } - } - }, - "jsonExample": "string" - } - } - ], - "request": null, - "name": null, - "codeSamples": null, - "response": { - "type": "ok", - "body": { - "shape": { - "type": "named", - "typeName": { - "name": { - "originalName": "UsernameCursor", - "camelCase": { - "unsafeName": "usernameCursor", - "safeName": "usernameCursor" - }, - "snakeCase": { - "unsafeName": "username_cursor", - "safeName": "username_cursor" - }, - "screamingSnakeCase": { - "unsafeName": "USERNAME_CURSOR", - "safeName": "USERNAME_CURSOR" - }, - "pascalCase": { - "unsafeName": "UsernameCursor", - "safeName": "UsernameCursor" - } - }, - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "typeId": "type_:UsernameCursor" - }, - "shape": { - "type": "object", - "properties": [ - { - "name": { - "name": { - "originalName": "cursor", - "camelCase": { - "unsafeName": "cursor", - "safeName": "cursor" - }, - "snakeCase": { - "unsafeName": "cursor", - "safeName": "cursor" - }, - "screamingSnakeCase": { - "unsafeName": "CURSOR", - "safeName": "CURSOR" - }, - "pascalCase": { - "unsafeName": "Cursor", - "safeName": "Cursor" - } - }, - "wireValue": "cursor" - }, - "value": { - "shape": { - "type": "named", - "typeName": { - "name": { - "originalName": "UsernamePage", - "camelCase": { - "unsafeName": "usernamePage", - "safeName": "usernamePage" - }, - "snakeCase": { - "unsafeName": "username_page", - "safeName": "username_page" - }, - "screamingSnakeCase": { - "unsafeName": "USERNAME_PAGE", - "safeName": "USERNAME_PAGE" - }, - "pascalCase": { - "unsafeName": "UsernamePage", - "safeName": "UsernamePage" - } - }, - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "typeId": "type_:UsernamePage" - }, - "shape": { - "type": "object", - "properties": [] - } - }, - "jsonExample": {} - }, - "originalTypeDeclaration": { - "name": { - "originalName": "UsernameCursor", - "camelCase": { - "unsafeName": "usernameCursor", - "safeName": "usernameCursor" - }, - "snakeCase": { - "unsafeName": "username_cursor", - "safeName": "username_cursor" - }, - "screamingSnakeCase": { - "unsafeName": "USERNAME_CURSOR", - "safeName": "USERNAME_CURSOR" - }, - "pascalCase": { - "unsafeName": "UsernameCursor", - "safeName": "UsernameCursor" - } - }, - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "typeId": "type_:UsernameCursor" - } - } - ] - } - }, - "jsonExample": { - "cursor": {} - } - } - }, - "docs": null - } - ], - "pagination": { - "type": "cursor", - "page": { - "name": { - "name": { - "originalName": "starting_after", - "camelCase": { - "unsafeName": "startingAfter", - "safeName": "startingAfter" - }, - "snakeCase": { - "unsafeName": "starting_after", - "safeName": "starting_after" - }, - "screamingSnakeCase": { - "unsafeName": "STARTING_AFTER", - "safeName": "STARTING_AFTER" - }, - "pascalCase": { - "unsafeName": "StartingAfter", - "safeName": "StartingAfter" - } - }, - "wireValue": "starting_after" - }, - "valueType": { - "_type": "container", - "container": { - "_type": "optional", - "optional": { - "_type": "primitive", - "primitive": "STRING" - } - } - }, - "allowMultiple": false, - "availability": null, - "docs": "The cursor used for pagination in order to fetch\nthe next page of results." - }, - "next": { - "propertyPath": [ - { - "originalName": "cursor", - "camelCase": { - "unsafeName": "cursor", - "safeName": "cursor" - }, - "snakeCase": { - "unsafeName": "cursor", - "safeName": "cursor" - }, - "screamingSnakeCase": { - "unsafeName": "CURSOR", - "safeName": "CURSOR" - }, - "pascalCase": { - "unsafeName": "Cursor", - "safeName": "Cursor" - } - } - ], - "property": { - "name": { - "name": { - "originalName": "after", - "camelCase": { - "unsafeName": "after", - "safeName": "after" - }, - "snakeCase": { - "unsafeName": "after", - "safeName": "after" - }, - "screamingSnakeCase": { - "unsafeName": "AFTER", - "safeName": "AFTER" - }, - "pascalCase": { - "unsafeName": "After", - "safeName": "After" - } - }, - "wireValue": "after" - }, - "valueType": { - "_type": "container", - "container": { - "_type": "optional", - "optional": { - "_type": "primitive", - "primitive": "STRING" - } - } - }, - "availability": null, - "docs": null - } - }, - "results": { - "propertyPath": [ - { - "originalName": "cursor", - "camelCase": { - "unsafeName": "cursor", - "safeName": "cursor" - }, - "snakeCase": { - "unsafeName": "cursor", - "safeName": "cursor" - }, - "screamingSnakeCase": { - "unsafeName": "CURSOR", - "safeName": "CURSOR" - }, - "pascalCase": { - "unsafeName": "Cursor", - "safeName": "Cursor" - } - } - ], - "property": { - "name": { - "name": { - "originalName": "data", - "camelCase": { - "unsafeName": "data", - "safeName": "data" - }, - "snakeCase": { - "unsafeName": "data", - "safeName": "data" - }, - "screamingSnakeCase": { - "unsafeName": "DATA", - "safeName": "DATA" - }, - "pascalCase": { - "unsafeName": "Data", - "safeName": "Data" - } - }, - "wireValue": "data" - }, - "valueType": { - "_type": "container", - "container": { - "_type": "list", - "list": { - "_type": "primitive", - "primitive": "STRING" - } - } - }, - "availability": null, - "docs": null - } - } - }, - "availability": null, - "docs": null - }, - { - "id": "endpoint_users.listWithGlobalConfig", - "name": { - "originalName": "listWithGlobalConfig", - "camelCase": { - "unsafeName": "listWithGlobalConfig", - "safeName": "listWithGlobalConfig" - }, - "snakeCase": { - "unsafeName": "list_with_global_config", - "safeName": "list_with_global_config" - }, - "screamingSnakeCase": { - "unsafeName": "LIST_WITH_GLOBAL_CONFIG", - "safeName": "LIST_WITH_GLOBAL_CONFIG" - }, - "pascalCase": { - "unsafeName": "ListWithGlobalConfig", - "safeName": "ListWithGlobalConfig" - } - }, - "displayName": null, - "auth": false, - "idempotent": false, - "baseUrl": null, - "method": "GET", - "path": { - "head": "", - "parts": [] - }, - "fullPath": { - "head": "/users", - "parts": [] - }, - "pathParameters": [], - "allPathParameters": [], - "queryParameters": [ - { - "name": { - "name": { - "originalName": "offset", - "camelCase": { - "unsafeName": "offset", - "safeName": "offset" - }, - "snakeCase": { - "unsafeName": "offset", - "safeName": "offset" - }, - "screamingSnakeCase": { - "unsafeName": "OFFSET", - "safeName": "OFFSET" - }, - "pascalCase": { - "unsafeName": "Offset", - "safeName": "Offset" - } - }, - "wireValue": "offset" - }, - "valueType": { - "_type": "container", - "container": { - "_type": "optional", - "optional": { - "_type": "primitive", - "primitive": "INTEGER" - } - } - }, - "allowMultiple": false, - "availability": null, - "docs": null - } - ], - "headers": [], - "requestBody": null, - "sdkRequest": { - "shape": { - "type": "wrapper", - "wrapperName": { - "originalName": "ListWithGlobalConfigRequest", - "camelCase": { - "unsafeName": "listWithGlobalConfigRequest", - "safeName": "listWithGlobalConfigRequest" - }, - "snakeCase": { - "unsafeName": "list_with_global_config_request", - "safeName": "list_with_global_config_request" - }, - "screamingSnakeCase": { - "unsafeName": "LIST_WITH_GLOBAL_CONFIG_REQUEST", - "safeName": "LIST_WITH_GLOBAL_CONFIG_REQUEST" - }, - "pascalCase": { - "unsafeName": "ListWithGlobalConfigRequest", - "safeName": "ListWithGlobalConfigRequest" - } - }, - "bodyKey": { - "originalName": "body", - "camelCase": { - "unsafeName": "body", - "safeName": "body" - }, - "snakeCase": { - "unsafeName": "body", - "safeName": "body" - }, - "screamingSnakeCase": { - "unsafeName": "BODY", - "safeName": "BODY" - }, - "pascalCase": { - "unsafeName": "Body", - "safeName": "Body" - } - } - }, - "requestParameterName": { - "originalName": "request", - "camelCase": { - "unsafeName": "request", - "safeName": "request" - }, - "snakeCase": { - "unsafeName": "request", - "safeName": "request" - }, - "screamingSnakeCase": { - "unsafeName": "REQUEST", - "safeName": "REQUEST" - }, - "pascalCase": { - "unsafeName": "Request", - "safeName": "Request" - } - } - }, - "response": { - "body": { - "type": "json", - "value": { - "type": "response", - "responseBodyType": { - "_type": "named", - "name": { - "originalName": "UsernameContainer", - "camelCase": { - "unsafeName": "usernameContainer", - "safeName": "usernameContainer" - }, - "snakeCase": { - "unsafeName": "username_container", - "safeName": "username_container" - }, - "screamingSnakeCase": { - "unsafeName": "USERNAME_CONTAINER", - "safeName": "USERNAME_CONTAINER" - }, - "pascalCase": { - "unsafeName": "UsernameContainer", - "safeName": "UsernameContainer" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:UsernameContainer" - }, - "docs": null - } - }, - "status-code": null - }, - "errors": [], - "examples": [ - { - "exampleType": "generated", - "url": "", - "rootPathParameters": [], - "servicePathParameters": [], - "endpointPathParameters": [], - "serviceHeaders": [], - "endpointHeaders": [], - "queryParameters": [ - { - "name": { - "name": { - "originalName": "offset", - "camelCase": { - "unsafeName": "offset", - "safeName": "offset" - }, - "snakeCase": { - "unsafeName": "offset", - "safeName": "offset" - }, - "screamingSnakeCase": { - "unsafeName": "OFFSET", - "safeName": "OFFSET" - }, - "pascalCase": { - "unsafeName": "Offset", - "safeName": "Offset" - } - }, - "wireValue": "offset" - }, - "value": { - "shape": { - "type": "primitive", - "primitive": { - "type": "integer", - "integer": 1 - } - }, - "jsonExample": 1 - } - } - ], - "request": null, - "name": null, - "codeSamples": null, - "response": { - "type": "ok", - "body": { - "shape": { - "type": "named", - "typeName": { - "name": { - "originalName": "UsernameContainer", - "camelCase": { - "unsafeName": "usernameContainer", - "safeName": "usernameContainer" - }, - "snakeCase": { - "unsafeName": "username_container", - "safeName": "username_container" - }, - "screamingSnakeCase": { - "unsafeName": "USERNAME_CONTAINER", - "safeName": "USERNAME_CONTAINER" - }, - "pascalCase": { - "unsafeName": "UsernameContainer", - "safeName": "UsernameContainer" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:UsernameContainer" - }, - "shape": { - "type": "object", - "properties": [ - { - "name": { - "name": { - "originalName": "results", - "camelCase": { - "unsafeName": "results", - "safeName": "results" - }, - "snakeCase": { - "unsafeName": "results", - "safeName": "results" - }, - "screamingSnakeCase": { - "unsafeName": "RESULTS", - "safeName": "RESULTS" - }, - "pascalCase": { - "unsafeName": "Results", - "safeName": "Results" - } - }, - "wireValue": "results" - }, - "value": { - "shape": { - "type": "container", - "container": { - "type": "list", - "list": [ - { - "shape": { - "type": "primitive", - "primitive": { - "type": "string", - "string": { - "original": "string" - } - } - }, - "jsonExample": "string" - } - ] - } - }, - "jsonExample": [ - "string" - ] - }, - "originalTypeDeclaration": { - "name": { - "originalName": "UsernameContainer", - "camelCase": { - "unsafeName": "usernameContainer", - "safeName": "usernameContainer" - }, - "snakeCase": { - "unsafeName": "username_container", - "safeName": "username_container" - }, - "screamingSnakeCase": { - "unsafeName": "USERNAME_CONTAINER", - "safeName": "USERNAME_CONTAINER" - }, - "pascalCase": { - "unsafeName": "UsernameContainer", - "safeName": "UsernameContainer" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "typeId": "type_users:UsernameContainer" - } - } - ] - } - }, - "jsonExample": { - "results": [ - "string" - ] - } - } - }, - "docs": null - } - ], - "pagination": { - "type": "offset", - "page": { - "name": { - "name": { - "originalName": "offset", - "camelCase": { - "unsafeName": "offset", - "safeName": "offset" - }, - "snakeCase": { - "unsafeName": "offset", - "safeName": "offset" - }, - "screamingSnakeCase": { - "unsafeName": "OFFSET", - "safeName": "OFFSET" - }, - "pascalCase": { - "unsafeName": "Offset", - "safeName": "Offset" - } - }, - "wireValue": "offset" - }, - "valueType": { - "_type": "container", - "container": { - "_type": "optional", - "optional": { - "_type": "primitive", - "primitive": "INTEGER" - } - } - }, - "allowMultiple": false, - "availability": null, - "docs": null - }, - "results": { - "propertyPath": [], - "property": { - "name": { - "name": { - "originalName": "results", - "camelCase": { - "unsafeName": "results", - "safeName": "results" - }, - "snakeCase": { - "unsafeName": "results", - "safeName": "results" - }, - "screamingSnakeCase": { - "unsafeName": "RESULTS", - "safeName": "RESULTS" - }, - "pascalCase": { - "unsafeName": "Results", - "safeName": "Results" - } - }, - "wireValue": "results" - }, - "valueType": { - "_type": "container", - "container": { - "_type": "list", - "list": { - "_type": "primitive", - "primitive": "STRING" - } - } - }, - "availability": null, - "docs": null - } - } - }, - "availability": null, - "docs": null - } - ] - } - }, - "constants": { - "errorInstanceIdKey": { - "name": { - "originalName": "errorInstanceId", - "camelCase": { - "unsafeName": "errorInstanceId", - "safeName": "errorInstanceId" - }, - "snakeCase": { - "unsafeName": "error_instance_id", - "safeName": "error_instance_id" - }, - "screamingSnakeCase": { - "unsafeName": "ERROR_INSTANCE_ID", - "safeName": "ERROR_INSTANCE_ID" - }, - "pascalCase": { - "unsafeName": "ErrorInstanceId", - "safeName": "ErrorInstanceId" - } - }, - "wireValue": "errorInstanceId" - } - }, - "environments": null, - "errorDiscriminationStrategy": { - "type": "statusCode" - }, - "basePath": null, - "pathParameters": [], - "variables": [], - "serviceTypeReferenceInfo": { - "typesReferencedOnlyByService": { - "service_users": [ - "type_:UsernameCursor", - "type_users:Order", - "type_users:UsernameContainer", - "type_users:ListUsersExtendedResponse", - "type_users:ListUsersPaginationResponse" - ] - }, - "sharedTypes": [ - "type_:UsernamePage", - "type_users:UserListContainer", - "type_users:UserPage", - "type_users:Page", - "type_users:NextPage", - "type_users:User" - ] - }, - "webhookGroups": {}, - "websocketChannels": {}, - "subpackages": { - "subpackage_users": { - "name": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - }, - "fernFilepath": { - "allParts": [ - { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - ], - "packagePath": [], - "file": { - "originalName": "users", - "camelCase": { - "unsafeName": "users", - "safeName": "users" - }, - "snakeCase": { - "unsafeName": "users", - "safeName": "users" - }, - "screamingSnakeCase": { - "unsafeName": "USERS", - "safeName": "USERS" - }, - "pascalCase": { - "unsafeName": "Users", - "safeName": "Users" - } - } - }, - "service": "service_users", - "types": [ - "type_users:Order", - "type_users:UserListContainer", - "type_users:UserPage", - "type_users:UsernameContainer", - "type_users:ListUsersExtendedResponse", - "type_users:ListUsersPaginationResponse", - "type_users:Page", - "type_users:NextPage", - "type_users:User" - ], - "errors": [], - "subpackages": [], - "navigationConfig": null, - "webhooks": null, - "websocket": null, - "hasEndpointsInTree": true, - "docs": null - } - }, - "rootPackage": { - "fernFilepath": { - "allParts": [], - "packagePath": [], - "file": null - }, - "websocket": null, - "service": null, - "types": [ - "type_:UsernameCursor", - "type_:UsernamePage" - ], - "errors": [], - "subpackages": [ - "subpackage_users" - ], - "webhooks": null, - "navigationConfig": null, - "hasEndpointsInTree": true, - "docs": null - }, - "sdkConfig": { - "isAuthMandatory": false, - "hasStreamingEndpoints": false, - "hasFileDownloadEndpoints": false, - "platformHeaders": { - "language": "X-Fern-Language", - "sdkName": "X-Fern-SDK-Name", - "sdkVersion": "X-Fern-SDK-Version" - } - } -} \ No newline at end of file diff --git a/seed/ruby-model/seed.yml b/seed/ruby-model/seed.yml index 8302c7d5345..d6408e443ed 100644 --- a/seed/ruby-model/seed.yml +++ b/seed/ruby-model/seed.yml @@ -6,4 +6,6 @@ generatorType: model defaultOutputMode: local_files fixtures: [] scripts: [] - +allowedFailures: + # TODO: Add support for recursive undiscriminated unions. + - grpc diff --git a/seed/ruby-model/unknown/lib/seed_unknown_as_any_client.rb b/seed/ruby-model/unknown/lib/seed_unknown_as_any_client.rb index 86394e506e8..4d537ecfaa5 100644 --- a/seed/ruby-model/unknown/lib/seed_unknown_as_any_client.rb +++ b/seed/ruby-model/unknown/lib/seed_unknown_as_any_client.rb @@ -1,43 +1,4 @@ # frozen_string_literal: true -require_relative "types_export" -require_relative "requests" -require_relative "seed_unknown_as_any_client/unknown/client" - -module SeedUnknownAsAnyClient - class Client - # @return [SeedUnknownAsAnyClient::UnknownClient] - attr_reader :unknown - - # @param base_url [String] - # @param max_retries [Long] The number of times to retry a failed request, defaults to 2. - # @param timeout_in_seconds [Long] - # @return [SeedUnknownAsAnyClient::Client] - def initialize(base_url: nil, max_retries: nil, timeout_in_seconds: nil) - @request_client = SeedUnknownAsAnyClient::RequestClient.new( - base_url: base_url, - max_retries: max_retries, - timeout_in_seconds: timeout_in_seconds - ) - @unknown = SeedUnknownAsAnyClient::UnknownClient.new(request_client: @request_client) - end - end - - class AsyncClient - # @return [SeedUnknownAsAnyClient::AsyncUnknownClient] - attr_reader :unknown - - # @param base_url [String] - # @param max_retries [Long] The number of times to retry a failed request, defaults to 2. - # @param timeout_in_seconds [Long] - # @return [SeedUnknownAsAnyClient::AsyncClient] - def initialize(base_url: nil, max_retries: nil, timeout_in_seconds: nil) - @async_request_client = SeedUnknownAsAnyClient::AsyncRequestClient.new( - base_url: base_url, - max_retries: max_retries, - timeout_in_seconds: timeout_in_seconds - ) - @unknown = SeedUnknownAsAnyClient::AsyncUnknownClient.new(request_client: @async_request_client) - end - end -end +require_relative "seed_unknown_as_any_client/unknown/types/my_alias" +require_relative "seed_unknown_as_any_client/unknown/types/my_object" diff --git a/seed/ruby-model/unknown/lib/seed_unknown_as_any_client/unknown/client.rb b/seed/ruby-model/unknown/lib/seed_unknown_as_any_client/unknown/client.rb deleted file mode 100644 index 0c3838dd4d3..00000000000 --- a/seed/ruby-model/unknown/lib/seed_unknown_as_any_client/unknown/client.rb +++ /dev/null @@ -1,130 +0,0 @@ -# frozen_string_literal: true - -require_relative "../../requests" -require "json" -require_relative "types/my_object" -require "async" - -module SeedUnknownAsAnyClient - class UnknownClient - # @return [SeedUnknownAsAnyClient::RequestClient] - attr_reader :request_client - - # @param request_client [SeedUnknownAsAnyClient::RequestClient] - # @return [SeedUnknownAsAnyClient::UnknownClient] - def initialize(request_client:) - @request_client = request_client - end - - # @param request [Object] - # @param request_options [SeedUnknownAsAnyClient::RequestOptions] - # @return [Array] - # @example - # unknown_as_any = SeedUnknownAsAnyClient::Client.new(base_url: "https://api.example.com") - # unknown_as_any.unknown.post(request: {"key":"value"}) - def post(request: nil, request_options: nil) - response = @request_client.conn.post do |req| - req.options.timeout = request_options.timeout_in_seconds unless request_options&.timeout_in_seconds.nil? - req.headers = { - **(req.headers || {}), - **@request_client.get_headers, - **(request_options&.additional_headers || {}) - }.compact - unless request_options.nil? || request_options&.additional_query_parameters.nil? - req.params = { **(request_options&.additional_query_parameters || {}) }.compact - end - req.body = { **(request || {}), **(request_options&.additional_body_parameters || {}) }.compact - req.url "#{@request_client.get_url(request_options: request_options)}/" - end - JSON.parse(response.body) - end - - # @param request [Hash] Request of type SeedUnknownAsAnyClient::Unknown::MyObject, as a Hash - # * :unknown (Object) - # @param request_options [SeedUnknownAsAnyClient::RequestOptions] - # @return [Array] - # @example - # unknown_as_any = SeedUnknownAsAnyClient::Client.new(base_url: "https://api.example.com") - # unknown_as_any.unknown.post_object(request: { }) - def post_object(request:, request_options: nil) - response = @request_client.conn.post do |req| - req.options.timeout = request_options.timeout_in_seconds unless request_options&.timeout_in_seconds.nil? - req.headers = { - **(req.headers || {}), - **@request_client.get_headers, - **(request_options&.additional_headers || {}) - }.compact - unless request_options.nil? || request_options&.additional_query_parameters.nil? - req.params = { **(request_options&.additional_query_parameters || {}) }.compact - end - req.body = { **(request || {}), **(request_options&.additional_body_parameters || {}) }.compact - req.url "#{@request_client.get_url(request_options: request_options)}/with-object" - end - JSON.parse(response.body) - end - end - - class AsyncUnknownClient - # @return [SeedUnknownAsAnyClient::AsyncRequestClient] - attr_reader :request_client - - # @param request_client [SeedUnknownAsAnyClient::AsyncRequestClient] - # @return [SeedUnknownAsAnyClient::AsyncUnknownClient] - def initialize(request_client:) - @request_client = request_client - end - - # @param request [Object] - # @param request_options [SeedUnknownAsAnyClient::RequestOptions] - # @return [Array] - # @example - # unknown_as_any = SeedUnknownAsAnyClient::Client.new(base_url: "https://api.example.com") - # unknown_as_any.unknown.post(request: {"key":"value"}) - def post(request: nil, request_options: nil) - Async do - response = @request_client.conn.post do |req| - req.options.timeout = request_options.timeout_in_seconds unless request_options&.timeout_in_seconds.nil? - req.headers = { - **(req.headers || {}), - **@request_client.get_headers, - **(request_options&.additional_headers || {}) - }.compact - unless request_options.nil? || request_options&.additional_query_parameters.nil? - req.params = { **(request_options&.additional_query_parameters || {}) }.compact - end - req.body = { **(request || {}), **(request_options&.additional_body_parameters || {}) }.compact - req.url "#{@request_client.get_url(request_options: request_options)}/" - end - parsed_json = JSON.parse(response.body) - parsed_json - end - end - - # @param request [Hash] Request of type SeedUnknownAsAnyClient::Unknown::MyObject, as a Hash - # * :unknown (Object) - # @param request_options [SeedUnknownAsAnyClient::RequestOptions] - # @return [Array] - # @example - # unknown_as_any = SeedUnknownAsAnyClient::Client.new(base_url: "https://api.example.com") - # unknown_as_any.unknown.post_object(request: { }) - def post_object(request:, request_options: nil) - Async do - response = @request_client.conn.post do |req| - req.options.timeout = request_options.timeout_in_seconds unless request_options&.timeout_in_seconds.nil? - req.headers = { - **(req.headers || {}), - **@request_client.get_headers, - **(request_options&.additional_headers || {}) - }.compact - unless request_options.nil? || request_options&.additional_query_parameters.nil? - req.params = { **(request_options&.additional_query_parameters || {}) }.compact - end - req.body = { **(request || {}), **(request_options&.additional_body_parameters || {}) }.compact - req.url "#{@request_client.get_url(request_options: request_options)}/with-object" - end - parsed_json = JSON.parse(response.body) - parsed_json - end - end - end -end diff --git a/seed/ruby-model/unknown/lib/types_export.rb b/seed/ruby-model/unknown/lib/types_export.rb deleted file mode 100644 index 4d537ecfaa5..00000000000 --- a/seed/ruby-model/unknown/lib/types_export.rb +++ /dev/null @@ -1,4 +0,0 @@ -# frozen_string_literal: true - -require_relative "seed_unknown_as_any_client/unknown/types/my_alias" -require_relative "seed_unknown_as_any_client/unknown/types/my_object" diff --git a/seed/ruby-model/unknown/seed_unknown_as_any_client.gemspec b/seed/ruby-model/unknown/seed_unknown_as_any_client.gemspec index 06724d38d01..a57a2d5d058 100644 --- a/seed/ruby-model/unknown/seed_unknown_as_any_client.gemspec +++ b/seed/ruby-model/unknown/seed_unknown_as_any_client.gemspec @@ -18,8 +18,4 @@ Gem::Specification.new do |spec| spec.bindir = "exe" spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] - spec.add_dependency "async-http-faraday", ">= 0.0", "< 1.0" - spec.add_dependency "faraday", ">= 1.10", "< 3.0" - spec.add_dependency "faraday-net_http", ">= 1.0", "< 4.0" - spec.add_dependency "faraday-retry", ">= 1.0", "< 3.0" end diff --git a/seed/ruby-sdk/grpc-proto/.github/workflows/publish.yml b/seed/ruby-sdk/grpc-proto/.github/workflows/publish.yml new file mode 100644 index 00000000000..78474dabd54 --- /dev/null +++ b/seed/ruby-sdk/grpc-proto/.github/workflows/publish.yml @@ -0,0 +1,26 @@ +name: Publish + +on: [push] +jobs: + publish: + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.7 + bundler-cache: true + + - name: Test gem + run: bundle install && bundle exec rake test + + - name: Build and Push Gem + env: + GEM_HOST_API_KEY: ${{ secrets. }} + run: | + gem build fern_grpc_proto.gemspec + + gem push fern_grpc_proto-*.gem --host diff --git a/seed/ruby-sdk/grpc-proto/.gitignore b/seed/ruby-sdk/grpc-proto/.gitignore new file mode 100644 index 00000000000..a97c182a2e1 --- /dev/null +++ b/seed/ruby-sdk/grpc-proto/.gitignore @@ -0,0 +1,10 @@ +/.bundle/ +/.yardoc +/_yardoc/ +/coverage/ +/doc/ +/pkg/ +/spec/reports/ +/tmp/ +*.gem +.env diff --git a/seed/ruby-sdk/grpc-proto/.mock/fern.config.json b/seed/ruby-sdk/grpc-proto/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/ruby-sdk/grpc-proto/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/ruby-sdk/grpc-proto/.mock/generators.yml b/seed/ruby-sdk/grpc-proto/.mock/generators.yml new file mode 100644 index 00000000000..902c4f50168 --- /dev/null +++ b/seed/ruby-sdk/grpc-proto/.mock/generators.yml @@ -0,0 +1,5 @@ +api: + - proto: + root: proto + target: proto/user/v1/user.proto + local-generation: true \ No newline at end of file diff --git a/seed/ruby-sdk/grpc-proto/.mock/proto/google/api/annotations.proto b/seed/ruby-sdk/grpc-proto/.mock/proto/google/api/annotations.proto new file mode 100644 index 00000000000..8ff42098404 --- /dev/null +++ b/seed/ruby-sdk/grpc-proto/.mock/proto/google/api/annotations.proto @@ -0,0 +1,31 @@ +// Copyright 2015 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/api/http.proto"; +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "AnnotationsProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // See `HttpRule`. + HttpRule http = 72295728; +} \ No newline at end of file diff --git a/seed/ruby-sdk/grpc-proto/.mock/proto/google/api/field_behavior.proto b/seed/ruby-sdk/grpc-proto/.mock/proto/google/api/field_behavior.proto new file mode 100644 index 00000000000..128799c558d --- /dev/null +++ b/seed/ruby-sdk/grpc-proto/.mock/proto/google/api/field_behavior.proto @@ -0,0 +1,104 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "FieldBehaviorProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.FieldOptions { + // A designation of a specific field behavior (required, output only, etc.) + // in protobuf messages. + // + // Examples: + // + // string name = 1 [(google.api.field_behavior) = REQUIRED]; + // State state = 1 [(google.api.field_behavior) = OUTPUT_ONLY]; + // google.protobuf.Duration ttl = 1 + // [(google.api.field_behavior) = INPUT_ONLY]; + // google.protobuf.Timestamp expire_time = 1 + // [(google.api.field_behavior) = OUTPUT_ONLY, + // (google.api.field_behavior) = IMMUTABLE]; + repeated google.api.FieldBehavior field_behavior = 1052; +} + +// An indicator of the behavior of a given field (for example, that a field +// is required in requests, or given as output but ignored as input). +// This **does not** change the behavior in protocol buffers itself; it only +// denotes the behavior and may affect how API tooling handles the field. +// +// Note: This enum **may** receive new values in the future. +enum FieldBehavior { + // Conventional default for enums. Do not use this. + FIELD_BEHAVIOR_UNSPECIFIED = 0; + + // Specifically denotes a field as optional. + // While all fields in protocol buffers are optional, this may be specified + // for emphasis if appropriate. + OPTIONAL = 1; + + // Denotes a field as required. + // This indicates that the field **must** be provided as part of the request, + // and failure to do so will cause an error (usually `INVALID_ARGUMENT`). + REQUIRED = 2; + + // Denotes a field as output only. + // This indicates that the field is provided in responses, but including the + // field in a request does nothing (the server *must* ignore it and + // *must not* throw an error as a result of the field's presence). + OUTPUT_ONLY = 3; + + // Denotes a field as input only. + // This indicates that the field is provided in requests, and the + // corresponding field is not included in output. + INPUT_ONLY = 4; + + // Denotes a field as immutable. + // This indicates that the field may be set once in a request to create a + // resource, but may not be changed thereafter. + IMMUTABLE = 5; + + // Denotes that a (repeated) field is an unordered list. + // This indicates that the service may provide the elements of the list + // in any arbitrary order, rather than the order the user originally + // provided. Additionally, the list's order may or may not be stable. + UNORDERED_LIST = 6; + + // Denotes that this field returns a non-empty default value if not set. + // This indicates that if the user provides the empty value in a request, + // a non-empty value will be returned. The user will not be aware of what + // non-empty value to expect. + NON_EMPTY_DEFAULT = 7; + + // Denotes that the field in a resource (a message annotated with + // google.api.resource) is used in the resource name to uniquely identify the + // resource. For AIP-compliant APIs, this should only be applied to the + // `name` field on the resource. + // + // This behavior should not be applied to references to other resources within + // the message. + // + // The identifier field of resources often have different field behavior + // depending on the request it is embedded in (e.g. for Create methods name + // is optional and unused, while for Update methods it is required). Instead + // of method-specific annotations, only `IDENTIFIER` is required. + IDENTIFIER = 8; +} \ No newline at end of file diff --git a/seed/ruby-sdk/grpc-proto/.mock/proto/google/api/http.proto b/seed/ruby-sdk/grpc-proto/.mock/proto/google/api/http.proto new file mode 100644 index 00000000000..c8392381eb9 --- /dev/null +++ b/seed/ruby-sdk/grpc-proto/.mock/proto/google/api/http.proto @@ -0,0 +1,379 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "HttpProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Defines the HTTP configuration for an API service. It contains a list of +// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method +// to one or more HTTP REST API methods. +message Http { + // A list of HTTP configuration rules that apply to individual API methods. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated HttpRule rules = 1; + + // When set to true, URL path parameters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // + // The default behavior is to not decode RFC 6570 reserved characters in multi + // segment matches. + bool fully_decode_reserved_expansion = 2; +} + +// # gRPC Transcoding +// +// gRPC Transcoding is a feature for mapping between a gRPC method and one or +// more HTTP REST endpoints. It allows developers to build a single API service +// that supports both gRPC APIs and REST APIs. Many systems, including [Google +// APIs](https://github.com/googleapis/googleapis), +// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC +// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), +// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature +// and use it for large scale production services. +// +// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies +// how different portions of the gRPC request message are mapped to the URL +// path, URL query parameters, and HTTP request body. It also controls how the +// gRPC response message is mapped to the HTTP response body. `HttpRule` is +// typically specified as an `google.api.http` annotation on the gRPC method. +// +// Each mapping specifies a URL path template and an HTTP method. The path +// template may refer to one or more fields in the gRPC request message, as long +// as each field is a non-repeated field with a primitive (non-message) type. +// The path template controls how fields of the request message are mapped to +// the URL path. +// +// Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/{name=messages/*}" +// }; +// } +// } +// message GetMessageRequest { +// string name = 1; // Mapped to URL path. +// } +// message Message { +// string text = 1; // The resource content. +// } +// +// This enables an HTTP REST to gRPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` +// +// Any fields in the request message which are not bound by the path template +// automatically become HTTP query parameters if there is no HTTP request body. +// For example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get:"/v1/messages/{message_id}" +// }; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // Mapped to URL path. +// int64 revision = 2; // Mapped to URL query parameter `revision`. +// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. +// } +// +// This enables a HTTP JSON to RPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | +// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: +// "foo"))` +// +// Note that fields which are mapped to URL query parameters must have a +// primitive type or a repeated primitive type or a non-repeated message type. +// In the case of a repeated type, the parameter can be repeated in the URL +// as `...?param=A¶m=B`. In the case of a message type, each field of the +// message is mapped to a separate parameter, such as +// `...?foo.a=A&foo.b=B&foo.c=C`. +// +// For HTTP methods that allow a request body, the `body` field +// specifies the mapping. Consider a REST update method on the +// message resource collection: +// +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } +// +// The following HTTP JSON to RPC mapping is enabled, where the +// representation of the JSON in the request body is determined by +// protos JSON encoding: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" message { text: "Hi!" })` +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. This enables the following alternative definition of +// the update method: +// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } +// +// +// The following HTTP JSON to RPC mapping is enabled: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" text: "Hi!")` +// +// Note that when using `*` in the body mapping, it is not possible to +// have HTTP parameters, as all fields not bound by the path end in +// the body. This makes this option more rarely used in practice when +// defining REST APIs. The common usage of `*` is in custom methods +// which don't use the URL at all for transferring data. +// +// It is possible to define multiple HTTP methods for one RPC by using +// the `additional_bindings` option. Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } +// +// This enables the following two alternative HTTP JSON to RPC mappings: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` +// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: +// "123456")` +// +// ## Rules for HTTP mapping +// +// 1. Leaf request fields (recursive expansion nested messages in the request +// message) are classified into three categories: +// - Fields referred by the path template. They are passed via the URL path. +// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They +// are passed via the HTTP +// request body. +// - All other fields are passed via the URL query parameters, and the +// parameter name is the field path in the request message. A repeated +// field can be represented as multiple query parameters under the same +// name. +// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL +// query parameter, all fields +// are passed via URL path and HTTP request body. +// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP +// request body, all +// fields are passed via URL path and URL query parameters. +// +// ### Path template syntax +// +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +// +// The syntax `*` matches a single URL path segment. The syntax `**` matches +// zero or more URL path segments, which must be the last part of the URL path +// except the `Verb`. +// +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable +// matches a single path segment, its template may be omitted, e.g. `{var}` +// is equivalent to `{var=*}`. +// +// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` +// contains any reserved character, such characters should be percent-encoded +// before the matching. +// +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path on the client +// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The +// server side does the reverse decoding. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{var}`. +// +// If a variable contains multiple path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path on the +// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. +// The server side does the reverse decoding, except "%2F" and "%2f" are left +// unchanged. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{+var}`. +// +// ## Using gRPC API Service Configuration +// +// gRPC API Service Configuration (service config) is a configuration language +// for configuring a gRPC service to become a user-facing product. The +// service config is simply the YAML representation of the `google.api.Service` +// proto message. +// +// As an alternative to annotating your proto file, you can configure gRPC +// transcoding in your service config YAML files. You do this by specifying a +// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same +// effect as the proto annotation. This can be particularly useful if you +// have a proto that is reused in multiple services. Note that any transcoding +// specified in the service config will override any matching transcoding +// configuration in the proto. +// +// Example: +// +// http: +// rules: +// # Selects a gRPC method and applies HttpRule to it. +// - selector: example.v1.Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} +// +// ## Special notes +// +// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the +// proto to JSON conversion must follow the [proto3 +// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). +// +// While the single segment variable follows the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String +// Expansion, the multi segment variable **does not** follow RFC 6570 Section +// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding +// for multi segment variables. +// +// The path variables **must not** refer to any repeated or mapped field, +// because client libraries are not capable of handling such variable expansion. +// +// The path variables **must not** capture the leading "/" character. The reason +// is that the most common use case "{var}" does not capture the leading "/" +// character. For consistency, all path variables must share the same behavior. +// +// Repeated message fields must not be mapped to URL query parameters, because +// no client library can support such complicated mapping. +// +// If an API needs to use a JSON array for request or response body, it can map +// the request or response body to a repeated field. However, some gRPC +// Transcoding implementations may not support this feature. +message HttpRule { + // Selects a method to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax + // details. + string selector = 1; + + // Determines the URL pattern is matched by this rules. This pattern can be + // used with any of the {get|put|post|delete|patch} methods. A custom method + // can be defined using the 'custom' field. + oneof pattern { + // Maps to HTTP GET. Used for listing and getting information about + // resources. + string get = 2; + + // Maps to HTTP PUT. Used for replacing a resource. + string put = 3; + + // Maps to HTTP POST. Used for creating a resource or performing an action. + string post = 4; + + // Maps to HTTP DELETE. Used for deleting a resource. + string delete = 5; + + // Maps to HTTP PATCH. Used for updating a resource. + string patch = 6; + + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. + CustomHttpPattern custom = 8; + } + + // The name of the request field whose value is mapped to the HTTP request + // body, or `*` for mapping all request fields not captured by the path + // pattern to the HTTP body, or omitted for not having any HTTP request body. + // + // NOTE: the referred field must be present at the top-level of the request + // message type. + string body = 7; + + // Optional. The name of the response field whose value is mapped to the HTTP + // response body. When omitted, the entire response message will be used + // as the HTTP response body. + // + // NOTE: The referred field must be present at the top-level of the response + // message type. + string response_body = 12; + + // Additional HTTP bindings for the selector. Nested bindings must + // not contain an `additional_bindings` field themselves (that is, + // the nesting may only be one level deep). + repeated HttpRule additional_bindings = 11; +} + +// A custom pattern is used for defining custom HTTP verb. +message CustomHttpPattern { + // The name of this custom HTTP verb. + string kind = 1; + + // The path matched by this custom verb. + string path = 2; +} \ No newline at end of file diff --git a/seed/ruby-sdk/grpc-proto/.mock/proto/user/v1/user.proto b/seed/ruby-sdk/grpc-proto/.mock/proto/user/v1/user.proto new file mode 100644 index 00000000000..a3ad0195bd4 --- /dev/null +++ b/seed/ruby-sdk/grpc-proto/.mock/proto/user/v1/user.proto @@ -0,0 +1,39 @@ +syntax = "proto3"; + +package user.v1; + +import "google/api/annotations.proto"; +import "google/api/field_behavior.proto"; +import "google/protobuf/struct.proto"; + +option go_package = "user/v1"; +option csharp_namespace = "User.V1"; + +message UserModel { + string username = 1; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateRequest { + string username = 1; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateResponse { + UserModel user = 1; +} + +service User { + rpc Create(CreateRequest) returns (CreateResponse) { + option (google.api.http) = { + post: "/users" + body: "*" + }; + } +} \ No newline at end of file diff --git a/seed/ruby-sdk/grpc-proto/.rubocop.yml b/seed/ruby-sdk/grpc-proto/.rubocop.yml new file mode 100644 index 00000000000..c1d2344d6e6 --- /dev/null +++ b/seed/ruby-sdk/grpc-proto/.rubocop.yml @@ -0,0 +1,36 @@ +AllCops: + TargetRubyVersion: 2.7 + +Style/StringLiterals: + Enabled: true + EnforcedStyle: double_quotes + +Style/StringLiteralsInInterpolation: + Enabled: true + EnforcedStyle: double_quotes + +Layout/FirstHashElementLineBreak: + Enabled: true + +Layout/MultilineHashKeyLineBreaks: + Enabled: true + +# Generated files may be more complex than standard, disable +# these rules for now as a known limitation. +Metrics/ParameterLists: + Enabled: false + +Metrics/MethodLength: + Enabled: false + +Metrics/AbcSize: + Enabled: false + +Metrics/ClassLength: + Enabled: false + +Metrics/CyclomaticComplexity: + Enabled: false + +Metrics/PerceivedComplexity: + Enabled: false diff --git a/seed/ruby-sdk/grpc-proto/Gemfile b/seed/ruby-sdk/grpc-proto/Gemfile new file mode 100644 index 00000000000..49bd9cd0173 --- /dev/null +++ b/seed/ruby-sdk/grpc-proto/Gemfile @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +gemspec + +gem "minitest", "~> 5.0" +gem "rake", "~> 13.0" +gem "rubocop", "~> 1.21" diff --git a/seed/ruby-sdk/grpc-proto/README.md b/seed/ruby-sdk/grpc-proto/README.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/ruby-sdk/grpc-proto/Rakefile b/seed/ruby-sdk/grpc-proto/Rakefile new file mode 100644 index 00000000000..2bdbce0cf2c --- /dev/null +++ b/seed/ruby-sdk/grpc-proto/Rakefile @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require "rake/testtask" +require "rubocop/rake_task" + +task default: %i[test rubocop] + +Rake::TestTask.new do |t| + t.pattern = "./test/**/test_*.rb" +end + +RuboCop::RakeTask.new diff --git a/seed/ruby-sdk/grpc-proto/fern_grpc_proto.gemspec b/seed/ruby-sdk/grpc-proto/fern_grpc_proto.gemspec new file mode 100644 index 00000000000..ff47bc524a8 --- /dev/null +++ b/seed/ruby-sdk/grpc-proto/fern_grpc_proto.gemspec @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require_relative "lib/gemconfig" + +Gem::Specification.new do |spec| + spec.name = "fern_grpc_proto" + spec.version = "0.0.1" + spec.authors = SeedApiClient::Gemconfig::AUTHORS + spec.email = SeedApiClient::Gemconfig::EMAIL + spec.summary = SeedApiClient::Gemconfig::SUMMARY + spec.description = SeedApiClient::Gemconfig::DESCRIPTION + spec.homepage = SeedApiClient::Gemconfig::HOMEPAGE + spec.required_ruby_version = ">= 2.7.0" + spec.metadata["homepage_uri"] = spec.homepage + spec.metadata["source_code_uri"] = SeedApiClient::Gemconfig::SOURCE_CODE_URI + spec.metadata["changelog_uri"] = SeedApiClient::Gemconfig::CHANGELOG_URI + spec.files = Dir.glob("lib/**/*") + spec.bindir = "exe" + spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } + spec.require_paths = ["lib"] + spec.add_dependency "async-http-faraday", ">= 0.0", "< 1.0" + spec.add_dependency "faraday", ">= 1.10", "< 3.0" + spec.add_dependency "faraday-net_http", ">= 1.0", "< 4.0" + spec.add_dependency "faraday-retry", ">= 1.0", "< 3.0" +end diff --git a/seed/ruby-sdk/grpc-proto/lib/fern_grpc_proto.rb b/seed/ruby-sdk/grpc-proto/lib/fern_grpc_proto.rb new file mode 100644 index 00000000000..cb60fed868b --- /dev/null +++ b/seed/ruby-sdk/grpc-proto/lib/fern_grpc_proto.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require_relative "types_export" +require_relative "requests" +require_relative "fern_grpc_proto/user/client" + +module SeedApiClient + class Client + # @return [SeedApiClient::UserClient] + attr_reader :user + + # @param base_url [String] + # @param max_retries [Long] The number of times to retry a failed request, defaults to 2. + # @param timeout_in_seconds [Long] + # @return [SeedApiClient::Client] + def initialize(base_url: nil, max_retries: nil, timeout_in_seconds: nil) + @request_client = SeedApiClient::RequestClient.new( + base_url: base_url, + max_retries: max_retries, + timeout_in_seconds: timeout_in_seconds + ) + @user = SeedApiClient::UserClient.new(request_client: @request_client) + end + end + + class AsyncClient + # @return [SeedApiClient::AsyncUserClient] + attr_reader :user + + # @param base_url [String] + # @param max_retries [Long] The number of times to retry a failed request, defaults to 2. + # @param timeout_in_seconds [Long] + # @return [SeedApiClient::AsyncClient] + def initialize(base_url: nil, max_retries: nil, timeout_in_seconds: nil) + @async_request_client = SeedApiClient::AsyncRequestClient.new( + base_url: base_url, + max_retries: max_retries, + timeout_in_seconds: timeout_in_seconds + ) + @user = SeedApiClient::AsyncUserClient.new(request_client: @async_request_client) + end + end +end diff --git a/seed/ruby-sdk/grpc-proto/lib/fern_grpc_proto/types/create_response.rb b/seed/ruby-sdk/grpc-proto/lib/fern_grpc_proto/types/create_response.rb new file mode 100644 index 00000000000..00c5e0f6f63 --- /dev/null +++ b/seed/ruby-sdk/grpc-proto/lib/fern_grpc_proto/types/create_response.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require_relative "user_model" +require "ostruct" +require "json" + +module SeedApiClient + class CreateResponse + # @return [SeedApiClient::UserModel] + attr_reader :user + # @return [OpenStruct] Additional properties unmapped to the current class definition + attr_reader :additional_properties + # @return [Object] + attr_reader :_field_set + protected :_field_set + + OMIT = Object.new + + # @param user [SeedApiClient::UserModel] + # @param additional_properties [OpenStruct] Additional properties unmapped to the current class definition + # @return [SeedApiClient::CreateResponse] + def initialize(user: OMIT, additional_properties: nil) + @user = user if user != OMIT + @additional_properties = additional_properties + @_field_set = { "user": user }.reject do |_k, v| + v == OMIT + end + end + + # Deserialize a JSON object to an instance of CreateResponse + # + # @param json_object [String] + # @return [SeedApiClient::CreateResponse] + def self.from_json(json_object:) + struct = JSON.parse(json_object, object_class: OpenStruct) + parsed_json = JSON.parse(json_object) + if parsed_json["user"].nil? + user = nil + else + user = parsed_json["user"].to_json + user = SeedApiClient::UserModel.from_json(json_object: user) + end + new(user: user, additional_properties: struct) + end + + # Serialize an instance of CreateResponse to a JSON object + # + # @return [String] + def to_json(*_args) + @_field_set&.to_json + end + + # Leveraged for Union-type generation, validate_raw attempts to parse the given + # hash and check each fields type against the current object's property + # definitions. + # + # @param obj [Object] + # @return [Void] + def self.validate_raw(obj:) + obj.user.nil? || SeedApiClient::UserModel.validate_raw(obj: obj.user) + end + end +end diff --git a/seed/ruby-sdk/grpc-proto/lib/fern_grpc_proto/types/user_model.rb b/seed/ruby-sdk/grpc-proto/lib/fern_grpc_proto/types/user_model.rb new file mode 100644 index 00000000000..157004d3be3 --- /dev/null +++ b/seed/ruby-sdk/grpc-proto/lib/fern_grpc_proto/types/user_model.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +require "ostruct" +require "json" + +module SeedApiClient + class UserModel + # @return [String] + attr_reader :username + # @return [String] + attr_reader :email + # @return [Integer] + attr_reader :age + # @return [Float] + attr_reader :weight + # @return [Hash{String => Object}] + attr_reader :metadata + # @return [OpenStruct] Additional properties unmapped to the current class definition + attr_reader :additional_properties + # @return [Object] + attr_reader :_field_set + protected :_field_set + + OMIT = Object.new + + # @param username [String] + # @param email [String] + # @param age [Integer] + # @param weight [Float] + # @param metadata [Hash{String => Object}] + # @param additional_properties [OpenStruct] Additional properties unmapped to the current class definition + # @return [SeedApiClient::UserModel] + def initialize(username: OMIT, email: OMIT, age: OMIT, weight: OMIT, metadata: OMIT, additional_properties: nil) + @username = username if username != OMIT + @email = email if email != OMIT + @age = age if age != OMIT + @weight = weight if weight != OMIT + @metadata = metadata if metadata != OMIT + @additional_properties = additional_properties + @_field_set = { + "username": username, + "email": email, + "age": age, + "weight": weight, + "metadata": metadata + }.reject do |_k, v| + v == OMIT + end + end + + # Deserialize a JSON object to an instance of UserModel + # + # @param json_object [String] + # @return [SeedApiClient::UserModel] + def self.from_json(json_object:) + struct = JSON.parse(json_object, object_class: OpenStruct) + parsed_json = JSON.parse(json_object) + username = parsed_json["username"] + email = parsed_json["email"] + age = parsed_json["age"] + weight = parsed_json["weight"] + metadata = parsed_json["metadata"] + new( + username: username, + email: email, + age: age, + weight: weight, + metadata: metadata, + additional_properties: struct + ) + end + + # Serialize an instance of UserModel to a JSON object + # + # @return [String] + def to_json(*_args) + @_field_set&.to_json + end + + # Leveraged for Union-type generation, validate_raw attempts to parse the given + # hash and check each fields type against the current object's property + # definitions. + # + # @param obj [Object] + # @return [Void] + def self.validate_raw(obj:) + obj.username&.is_a?(String) != false || raise("Passed value for field obj.username is not the expected type, validation failed.") + obj.email&.is_a?(String) != false || raise("Passed value for field obj.email is not the expected type, validation failed.") + obj.age&.is_a?(Integer) != false || raise("Passed value for field obj.age is not the expected type, validation failed.") + obj.weight&.is_a?(Float) != false || raise("Passed value for field obj.weight is not the expected type, validation failed.") + obj.metadata&.is_a?(Hash) != false || raise("Passed value for field obj.metadata is not the expected type, validation failed.") + end + end +end diff --git a/seed/ruby-sdk/grpc-proto/lib/fern_grpc_proto/user/client.rb b/seed/ruby-sdk/grpc-proto/lib/fern_grpc_proto/user/client.rb new file mode 100644 index 00000000000..e7bf56acbdd --- /dev/null +++ b/seed/ruby-sdk/grpc-proto/lib/fern_grpc_proto/user/client.rb @@ -0,0 +1,99 @@ +# frozen_string_literal: true + +require_relative "../../requests" +require_relative "../types/create_response" +require "async" + +module SeedApiClient + class UserClient + # @return [SeedApiClient::RequestClient] + attr_reader :request_client + + # @param request_client [SeedApiClient::RequestClient] + # @return [SeedApiClient::UserClient] + def initialize(request_client:) + @request_client = request_client + end + + # @param username [String] + # @param email [String] + # @param age [Integer] + # @param weight [Float] + # @param metadata [Hash{String => Object}] + # @param request_options [SeedApiClient::RequestOptions] + # @return [SeedApiClient::CreateResponse] + # @example + # api = SeedApiClient::Client.new(base_url: "https://api.example.com") + # api.user.create + def create(username: nil, email: nil, age: nil, weight: nil, metadata: nil, request_options: nil) + response = @request_client.conn.post do |req| + req.options.timeout = request_options.timeout_in_seconds unless request_options&.timeout_in_seconds.nil? + req.headers = { + **(req.headers || {}), + **@request_client.get_headers, + **(request_options&.additional_headers || {}) + }.compact + unless request_options.nil? || request_options&.additional_query_parameters.nil? + req.params = { **(request_options&.additional_query_parameters || {}) }.compact + end + req.body = { + **(request_options&.additional_body_parameters || {}), + username: username, + email: email, + age: age, + weight: weight, + metadata: metadata + }.compact + req.url "#{@request_client.get_url(request_options: request_options)}/users" + end + SeedApiClient::CreateResponse.from_json(json_object: response.body) + end + end + + class AsyncUserClient + # @return [SeedApiClient::AsyncRequestClient] + attr_reader :request_client + + # @param request_client [SeedApiClient::AsyncRequestClient] + # @return [SeedApiClient::AsyncUserClient] + def initialize(request_client:) + @request_client = request_client + end + + # @param username [String] + # @param email [String] + # @param age [Integer] + # @param weight [Float] + # @param metadata [Hash{String => Object}] + # @param request_options [SeedApiClient::RequestOptions] + # @return [SeedApiClient::CreateResponse] + # @example + # api = SeedApiClient::Client.new(base_url: "https://api.example.com") + # api.user.create + def create(username: nil, email: nil, age: nil, weight: nil, metadata: nil, request_options: nil) + Async do + response = @request_client.conn.post do |req| + req.options.timeout = request_options.timeout_in_seconds unless request_options&.timeout_in_seconds.nil? + req.headers = { + **(req.headers || {}), + **@request_client.get_headers, + **(request_options&.additional_headers || {}) + }.compact + unless request_options.nil? || request_options&.additional_query_parameters.nil? + req.params = { **(request_options&.additional_query_parameters || {}) }.compact + end + req.body = { + **(request_options&.additional_body_parameters || {}), + username: username, + email: email, + age: age, + weight: weight, + metadata: metadata + }.compact + req.url "#{@request_client.get_url(request_options: request_options)}/users" + end + SeedApiClient::CreateResponse.from_json(json_object: response.body) + end + end + end +end diff --git a/seed/ruby-sdk/grpc-proto/lib/gemconfig.rb b/seed/ruby-sdk/grpc-proto/lib/gemconfig.rb new file mode 100644 index 00000000000..158aea9b7b7 --- /dev/null +++ b/seed/ruby-sdk/grpc-proto/lib/gemconfig.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module SeedApiClient + module Gemconfig + VERSION = "" + AUTHORS = [""].freeze + EMAIL = "" + SUMMARY = "" + DESCRIPTION = "" + HOMEPAGE = "https://github.com/grpc-proto/fern" + SOURCE_CODE_URI = "https://github.com/grpc-proto/fern" + CHANGELOG_URI = "https://github.com/grpc-proto/fern/blob/master/CHANGELOG.md" + end +end diff --git a/seed/ruby-model/unknown/lib/requests.rb b/seed/ruby-sdk/grpc-proto/lib/requests.rb similarity index 88% rename from seed/ruby-model/unknown/lib/requests.rb rename to seed/ruby-sdk/grpc-proto/lib/requests.rb index b2e8e53ee4e..636fce43fb6 100644 --- a/seed/ruby-model/unknown/lib/requests.rb +++ b/seed/ruby-sdk/grpc-proto/lib/requests.rb @@ -4,7 +4,7 @@ require "faraday/retry" require "async/http/faraday" -module SeedUnknownAsAnyClient +module SeedApiClient class RequestClient # @return [Faraday] attr_reader :conn @@ -14,7 +14,7 @@ class RequestClient # @param base_url [String] # @param max_retries [Long] The number of times to retry a failed request, defaults to 2. # @param timeout_in_seconds [Long] - # @return [SeedUnknownAsAnyClient::RequestClient] + # @return [SeedApiClient::RequestClient] def initialize(base_url: nil, max_retries: nil, timeout_in_seconds: nil) @base_url = base_url @conn = Faraday.new do |faraday| @@ -25,7 +25,7 @@ def initialize(base_url: nil, max_retries: nil, timeout_in_seconds: nil) end end - # @param request_options [SeedUnknownAsAnyClient::RequestOptions] + # @param request_options [SeedApiClient::RequestOptions] # @return [String] def get_url(request_options: nil) request_options&.base_url || @base_url @@ -33,7 +33,7 @@ def get_url(request_options: nil) # @return [Hash{String => String}] def get_headers - { "X-Fern-Language": "Ruby", "X-Fern-SDK-Name": "seed_unknown_as_any_client" } + { "X-Fern-Language": "Ruby", "X-Fern-SDK-Name": "fern_grpc_proto", "X-Fern-SDK-Version": "0.0.1" } end end @@ -46,7 +46,7 @@ class AsyncRequestClient # @param base_url [String] # @param max_retries [Long] The number of times to retry a failed request, defaults to 2. # @param timeout_in_seconds [Long] - # @return [SeedUnknownAsAnyClient::AsyncRequestClient] + # @return [SeedApiClient::AsyncRequestClient] def initialize(base_url: nil, max_retries: nil, timeout_in_seconds: nil) @base_url = base_url @conn = Faraday.new do |faraday| @@ -58,7 +58,7 @@ def initialize(base_url: nil, max_retries: nil, timeout_in_seconds: nil) end end - # @param request_options [SeedUnknownAsAnyClient::RequestOptions] + # @param request_options [SeedApiClient::RequestOptions] # @return [String] def get_url(request_options: nil) request_options&.base_url || @base_url @@ -66,7 +66,7 @@ def get_url(request_options: nil) # @return [Hash{String => String}] def get_headers - { "X-Fern-Language": "Ruby", "X-Fern-SDK-Name": "seed_unknown_as_any_client" } + { "X-Fern-Language": "Ruby", "X-Fern-SDK-Name": "fern_grpc_proto", "X-Fern-SDK-Version": "0.0.1" } end end @@ -89,7 +89,7 @@ class RequestOptions # @param additional_query_parameters [Hash{String => Object}] # @param additional_body_parameters [Hash{String => Object}] # @param timeout_in_seconds [Long] - # @return [SeedUnknownAsAnyClient::RequestOptions] + # @return [SeedApiClient::RequestOptions] def initialize(base_url: nil, additional_headers: nil, additional_query_parameters: nil, additional_body_parameters: nil, timeout_in_seconds: nil) @base_url = base_url @@ -119,7 +119,7 @@ class IdempotencyRequestOptions # @param additional_query_parameters [Hash{String => Object}] # @param additional_body_parameters [Hash{String => Object}] # @param timeout_in_seconds [Long] - # @return [SeedUnknownAsAnyClient::IdempotencyRequestOptions] + # @return [SeedApiClient::IdempotencyRequestOptions] def initialize(base_url: nil, additional_headers: nil, additional_query_parameters: nil, additional_body_parameters: nil, timeout_in_seconds: nil) @base_url = base_url diff --git a/seed/ruby-sdk/grpc-proto/lib/types_export.rb b/seed/ruby-sdk/grpc-proto/lib/types_export.rb new file mode 100644 index 00000000000..187f2dc3a52 --- /dev/null +++ b/seed/ruby-sdk/grpc-proto/lib/types_export.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +require_relative "fern_grpc_proto/types/create_response" +require_relative "fern_grpc_proto/types/user_model" diff --git a/seed/ruby-sdk/grpc-proto/snippet-templates.json b/seed/ruby-sdk/grpc-proto/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/ruby-sdk/grpc-proto/snippet.json b/seed/ruby-sdk/grpc-proto/snippet.json new file mode 100644 index 00000000000..dd3eca59c59 --- /dev/null +++ b/seed/ruby-sdk/grpc-proto/snippet.json @@ -0,0 +1,27 @@ +{ + "endpoints": [ + { + "id": { + "path": "/users", + "method": "POST", + "identifierOverride": "endpoint_user.create" + }, + "snippet": { + "client": "require \"fern_grpc_proto\"\n\napi = SeedApiClient::Client.new(base_url: \"https://api.example.com\")\napi.user.create", + "type": "ruby" + } + }, + { + "id": { + "path": "/users", + "method": "POST", + "identifierOverride": "endpoint_user.create" + }, + "snippet": { + "client": "require \"fern_grpc_proto\"\n\napi = SeedApiClient::Client.new(base_url: \"https://api.example.com\")\napi.user.create", + "type": "ruby" + } + } + ], + "types": {} +} \ No newline at end of file diff --git a/seed/ruby-sdk/grpc-proto/test/test_fern_grpc_proto.rb b/seed/ruby-sdk/grpc-proto/test/test_fern_grpc_proto.rb new file mode 100644 index 00000000000..d3dc83a417e --- /dev/null +++ b/seed/ruby-sdk/grpc-proto/test/test_fern_grpc_proto.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require_relative "test_helper" +require "fern_grpc_proto" + +# Basic SeedApiClient tests +class TestSeedApiClient < Minitest::Test + def test_function + # SeedApiClient::Client.new + end +end diff --git a/seed/ruby-sdk/grpc-proto/test/test_helper.rb b/seed/ruby-sdk/grpc-proto/test/test_helper.rb new file mode 100644 index 00000000000..895e723421f --- /dev/null +++ b/seed/ruby-sdk/grpc-proto/test/test_helper.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +$LOAD_PATH.unshift File.expand_path("../lib", __dir__) + +require "minitest/autorun" +require "fern_grpc_proto" diff --git a/seed/ruby-sdk/seed.yml b/seed/ruby-sdk/seed.yml index a1425af292a..6faceeb29b9 100644 --- a/seed/ruby-sdk/seed.yml +++ b/seed/ruby-sdk/seed.yml @@ -32,6 +32,8 @@ fixtures: allowedFailures: - streaming - objects-with-imports + # TODO: Add support for recursive undiscriminated unions. + - grpc features: requestOptions: true idempotency: false diff --git a/seed/ts-express/grpc-proto/.mock/fern.config.json b/seed/ts-express/grpc-proto/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/ts-express/grpc-proto/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/ts-express/grpc-proto/.mock/generators.yml b/seed/ts-express/grpc-proto/.mock/generators.yml new file mode 100644 index 00000000000..902c4f50168 --- /dev/null +++ b/seed/ts-express/grpc-proto/.mock/generators.yml @@ -0,0 +1,5 @@ +api: + - proto: + root: proto + target: proto/user/v1/user.proto + local-generation: true \ No newline at end of file diff --git a/seed/ts-express/grpc-proto/.mock/proto/google/api/annotations.proto b/seed/ts-express/grpc-proto/.mock/proto/google/api/annotations.proto new file mode 100644 index 00000000000..8ff42098404 --- /dev/null +++ b/seed/ts-express/grpc-proto/.mock/proto/google/api/annotations.proto @@ -0,0 +1,31 @@ +// Copyright 2015 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/api/http.proto"; +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "AnnotationsProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // See `HttpRule`. + HttpRule http = 72295728; +} \ No newline at end of file diff --git a/seed/ts-express/grpc-proto/.mock/proto/google/api/field_behavior.proto b/seed/ts-express/grpc-proto/.mock/proto/google/api/field_behavior.proto new file mode 100644 index 00000000000..128799c558d --- /dev/null +++ b/seed/ts-express/grpc-proto/.mock/proto/google/api/field_behavior.proto @@ -0,0 +1,104 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "FieldBehaviorProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.FieldOptions { + // A designation of a specific field behavior (required, output only, etc.) + // in protobuf messages. + // + // Examples: + // + // string name = 1 [(google.api.field_behavior) = REQUIRED]; + // State state = 1 [(google.api.field_behavior) = OUTPUT_ONLY]; + // google.protobuf.Duration ttl = 1 + // [(google.api.field_behavior) = INPUT_ONLY]; + // google.protobuf.Timestamp expire_time = 1 + // [(google.api.field_behavior) = OUTPUT_ONLY, + // (google.api.field_behavior) = IMMUTABLE]; + repeated google.api.FieldBehavior field_behavior = 1052; +} + +// An indicator of the behavior of a given field (for example, that a field +// is required in requests, or given as output but ignored as input). +// This **does not** change the behavior in protocol buffers itself; it only +// denotes the behavior and may affect how API tooling handles the field. +// +// Note: This enum **may** receive new values in the future. +enum FieldBehavior { + // Conventional default for enums. Do not use this. + FIELD_BEHAVIOR_UNSPECIFIED = 0; + + // Specifically denotes a field as optional. + // While all fields in protocol buffers are optional, this may be specified + // for emphasis if appropriate. + OPTIONAL = 1; + + // Denotes a field as required. + // This indicates that the field **must** be provided as part of the request, + // and failure to do so will cause an error (usually `INVALID_ARGUMENT`). + REQUIRED = 2; + + // Denotes a field as output only. + // This indicates that the field is provided in responses, but including the + // field in a request does nothing (the server *must* ignore it and + // *must not* throw an error as a result of the field's presence). + OUTPUT_ONLY = 3; + + // Denotes a field as input only. + // This indicates that the field is provided in requests, and the + // corresponding field is not included in output. + INPUT_ONLY = 4; + + // Denotes a field as immutable. + // This indicates that the field may be set once in a request to create a + // resource, but may not be changed thereafter. + IMMUTABLE = 5; + + // Denotes that a (repeated) field is an unordered list. + // This indicates that the service may provide the elements of the list + // in any arbitrary order, rather than the order the user originally + // provided. Additionally, the list's order may or may not be stable. + UNORDERED_LIST = 6; + + // Denotes that this field returns a non-empty default value if not set. + // This indicates that if the user provides the empty value in a request, + // a non-empty value will be returned. The user will not be aware of what + // non-empty value to expect. + NON_EMPTY_DEFAULT = 7; + + // Denotes that the field in a resource (a message annotated with + // google.api.resource) is used in the resource name to uniquely identify the + // resource. For AIP-compliant APIs, this should only be applied to the + // `name` field on the resource. + // + // This behavior should not be applied to references to other resources within + // the message. + // + // The identifier field of resources often have different field behavior + // depending on the request it is embedded in (e.g. for Create methods name + // is optional and unused, while for Update methods it is required). Instead + // of method-specific annotations, only `IDENTIFIER` is required. + IDENTIFIER = 8; +} \ No newline at end of file diff --git a/seed/ts-express/grpc-proto/.mock/proto/google/api/http.proto b/seed/ts-express/grpc-proto/.mock/proto/google/api/http.proto new file mode 100644 index 00000000000..c8392381eb9 --- /dev/null +++ b/seed/ts-express/grpc-proto/.mock/proto/google/api/http.proto @@ -0,0 +1,379 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "HttpProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Defines the HTTP configuration for an API service. It contains a list of +// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method +// to one or more HTTP REST API methods. +message Http { + // A list of HTTP configuration rules that apply to individual API methods. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated HttpRule rules = 1; + + // When set to true, URL path parameters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // + // The default behavior is to not decode RFC 6570 reserved characters in multi + // segment matches. + bool fully_decode_reserved_expansion = 2; +} + +// # gRPC Transcoding +// +// gRPC Transcoding is a feature for mapping between a gRPC method and one or +// more HTTP REST endpoints. It allows developers to build a single API service +// that supports both gRPC APIs and REST APIs. Many systems, including [Google +// APIs](https://github.com/googleapis/googleapis), +// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC +// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), +// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature +// and use it for large scale production services. +// +// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies +// how different portions of the gRPC request message are mapped to the URL +// path, URL query parameters, and HTTP request body. It also controls how the +// gRPC response message is mapped to the HTTP response body. `HttpRule` is +// typically specified as an `google.api.http` annotation on the gRPC method. +// +// Each mapping specifies a URL path template and an HTTP method. The path +// template may refer to one or more fields in the gRPC request message, as long +// as each field is a non-repeated field with a primitive (non-message) type. +// The path template controls how fields of the request message are mapped to +// the URL path. +// +// Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/{name=messages/*}" +// }; +// } +// } +// message GetMessageRequest { +// string name = 1; // Mapped to URL path. +// } +// message Message { +// string text = 1; // The resource content. +// } +// +// This enables an HTTP REST to gRPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` +// +// Any fields in the request message which are not bound by the path template +// automatically become HTTP query parameters if there is no HTTP request body. +// For example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get:"/v1/messages/{message_id}" +// }; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // Mapped to URL path. +// int64 revision = 2; // Mapped to URL query parameter `revision`. +// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. +// } +// +// This enables a HTTP JSON to RPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | +// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: +// "foo"))` +// +// Note that fields which are mapped to URL query parameters must have a +// primitive type or a repeated primitive type or a non-repeated message type. +// In the case of a repeated type, the parameter can be repeated in the URL +// as `...?param=A¶m=B`. In the case of a message type, each field of the +// message is mapped to a separate parameter, such as +// `...?foo.a=A&foo.b=B&foo.c=C`. +// +// For HTTP methods that allow a request body, the `body` field +// specifies the mapping. Consider a REST update method on the +// message resource collection: +// +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } +// +// The following HTTP JSON to RPC mapping is enabled, where the +// representation of the JSON in the request body is determined by +// protos JSON encoding: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" message { text: "Hi!" })` +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. This enables the following alternative definition of +// the update method: +// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } +// +// +// The following HTTP JSON to RPC mapping is enabled: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" text: "Hi!")` +// +// Note that when using `*` in the body mapping, it is not possible to +// have HTTP parameters, as all fields not bound by the path end in +// the body. This makes this option more rarely used in practice when +// defining REST APIs. The common usage of `*` is in custom methods +// which don't use the URL at all for transferring data. +// +// It is possible to define multiple HTTP methods for one RPC by using +// the `additional_bindings` option. Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } +// +// This enables the following two alternative HTTP JSON to RPC mappings: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` +// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: +// "123456")` +// +// ## Rules for HTTP mapping +// +// 1. Leaf request fields (recursive expansion nested messages in the request +// message) are classified into three categories: +// - Fields referred by the path template. They are passed via the URL path. +// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They +// are passed via the HTTP +// request body. +// - All other fields are passed via the URL query parameters, and the +// parameter name is the field path in the request message. A repeated +// field can be represented as multiple query parameters under the same +// name. +// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL +// query parameter, all fields +// are passed via URL path and HTTP request body. +// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP +// request body, all +// fields are passed via URL path and URL query parameters. +// +// ### Path template syntax +// +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +// +// The syntax `*` matches a single URL path segment. The syntax `**` matches +// zero or more URL path segments, which must be the last part of the URL path +// except the `Verb`. +// +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable +// matches a single path segment, its template may be omitted, e.g. `{var}` +// is equivalent to `{var=*}`. +// +// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` +// contains any reserved character, such characters should be percent-encoded +// before the matching. +// +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path on the client +// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The +// server side does the reverse decoding. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{var}`. +// +// If a variable contains multiple path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path on the +// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. +// The server side does the reverse decoding, except "%2F" and "%2f" are left +// unchanged. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{+var}`. +// +// ## Using gRPC API Service Configuration +// +// gRPC API Service Configuration (service config) is a configuration language +// for configuring a gRPC service to become a user-facing product. The +// service config is simply the YAML representation of the `google.api.Service` +// proto message. +// +// As an alternative to annotating your proto file, you can configure gRPC +// transcoding in your service config YAML files. You do this by specifying a +// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same +// effect as the proto annotation. This can be particularly useful if you +// have a proto that is reused in multiple services. Note that any transcoding +// specified in the service config will override any matching transcoding +// configuration in the proto. +// +// Example: +// +// http: +// rules: +// # Selects a gRPC method and applies HttpRule to it. +// - selector: example.v1.Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} +// +// ## Special notes +// +// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the +// proto to JSON conversion must follow the [proto3 +// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). +// +// While the single segment variable follows the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String +// Expansion, the multi segment variable **does not** follow RFC 6570 Section +// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding +// for multi segment variables. +// +// The path variables **must not** refer to any repeated or mapped field, +// because client libraries are not capable of handling such variable expansion. +// +// The path variables **must not** capture the leading "/" character. The reason +// is that the most common use case "{var}" does not capture the leading "/" +// character. For consistency, all path variables must share the same behavior. +// +// Repeated message fields must not be mapped to URL query parameters, because +// no client library can support such complicated mapping. +// +// If an API needs to use a JSON array for request or response body, it can map +// the request or response body to a repeated field. However, some gRPC +// Transcoding implementations may not support this feature. +message HttpRule { + // Selects a method to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax + // details. + string selector = 1; + + // Determines the URL pattern is matched by this rules. This pattern can be + // used with any of the {get|put|post|delete|patch} methods. A custom method + // can be defined using the 'custom' field. + oneof pattern { + // Maps to HTTP GET. Used for listing and getting information about + // resources. + string get = 2; + + // Maps to HTTP PUT. Used for replacing a resource. + string put = 3; + + // Maps to HTTP POST. Used for creating a resource or performing an action. + string post = 4; + + // Maps to HTTP DELETE. Used for deleting a resource. + string delete = 5; + + // Maps to HTTP PATCH. Used for updating a resource. + string patch = 6; + + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. + CustomHttpPattern custom = 8; + } + + // The name of the request field whose value is mapped to the HTTP request + // body, or `*` for mapping all request fields not captured by the path + // pattern to the HTTP body, or omitted for not having any HTTP request body. + // + // NOTE: the referred field must be present at the top-level of the request + // message type. + string body = 7; + + // Optional. The name of the response field whose value is mapped to the HTTP + // response body. When omitted, the entire response message will be used + // as the HTTP response body. + // + // NOTE: The referred field must be present at the top-level of the response + // message type. + string response_body = 12; + + // Additional HTTP bindings for the selector. Nested bindings must + // not contain an `additional_bindings` field themselves (that is, + // the nesting may only be one level deep). + repeated HttpRule additional_bindings = 11; +} + +// A custom pattern is used for defining custom HTTP verb. +message CustomHttpPattern { + // The name of this custom HTTP verb. + string kind = 1; + + // The path matched by this custom verb. + string path = 2; +} \ No newline at end of file diff --git a/seed/ts-express/grpc-proto/.mock/proto/user/v1/user.proto b/seed/ts-express/grpc-proto/.mock/proto/user/v1/user.proto new file mode 100644 index 00000000000..a3ad0195bd4 --- /dev/null +++ b/seed/ts-express/grpc-proto/.mock/proto/user/v1/user.proto @@ -0,0 +1,39 @@ +syntax = "proto3"; + +package user.v1; + +import "google/api/annotations.proto"; +import "google/api/field_behavior.proto"; +import "google/protobuf/struct.proto"; + +option go_package = "user/v1"; +option csharp_namespace = "User.V1"; + +message UserModel { + string username = 1; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateRequest { + string username = 1; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateResponse { + UserModel user = 1; +} + +service User { + rpc Create(CreateRequest) returns (CreateResponse) { + option (google.api.http) = { + post: "/users" + body: "*" + }; + } +} \ No newline at end of file diff --git a/seed/ts-express/grpc-proto/api/index.ts b/seed/ts-express/grpc-proto/api/index.ts new file mode 100644 index 00000000000..3ce0a3e38e8 --- /dev/null +++ b/seed/ts-express/grpc-proto/api/index.ts @@ -0,0 +1,2 @@ +export * from "./types"; +export * from "./resources"; diff --git a/seed/ts-express/grpc-proto/api/resources/index.ts b/seed/ts-express/grpc-proto/api/resources/index.ts new file mode 100644 index 00000000000..8b2f3a0b823 --- /dev/null +++ b/seed/ts-express/grpc-proto/api/resources/index.ts @@ -0,0 +1,2 @@ +export * as user from "./user"; +export * from "./user/service/requests"; diff --git a/seed/ts-express/grpc-proto/api/resources/user/index.ts b/seed/ts-express/grpc-proto/api/resources/user/index.ts new file mode 100644 index 00000000000..6261f896364 --- /dev/null +++ b/seed/ts-express/grpc-proto/api/resources/user/index.ts @@ -0,0 +1 @@ +export * from "./service"; diff --git a/seed/ts-express/grpc-proto/api/resources/user/service/UserService.ts b/seed/ts-express/grpc-proto/api/resources/user/service/UserService.ts new file mode 100644 index 00000000000..6b68c34ec3d --- /dev/null +++ b/seed/ts-express/grpc-proto/api/resources/user/service/UserService.ts @@ -0,0 +1,85 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as SeedApi from "../../../index"; +import express from "express"; +import * as serializers from "../../../../serialization/index"; +import * as errors from "../../../../errors/index"; + +export interface UserServiceMethods { + create( + req: express.Request, + res: { + send: (responseBody: SeedApi.CreateResponse) => Promise; + cookie: (cookie: string, value: string, options?: express.CookieOptions) => void; + locals: any; + }, + next: express.NextFunction + ): void | Promise; +} + +export class UserService { + private router; + + constructor(private readonly methods: UserServiceMethods, middleware: express.RequestHandler[] = []) { + this.router = express.Router({ mergeParams: true }).use( + express.json({ + strict: false, + }), + ...middleware + ); + } + + public addMiddleware(handler: express.RequestHandler): this { + this.router.use(handler); + return this; + } + + public toRouter(): express.Router { + this.router.post("/users", async (req, res, next) => { + const request = serializers.CreateRequest.parse(req.body); + if (request.ok) { + req.body = request.value; + try { + await this.methods.create( + req as any, + { + send: async (responseBody) => { + res.json( + serializers.CreateResponse.jsonOrThrow(responseBody, { + unrecognizedObjectKeys: "strip", + }) + ); + }, + cookie: res.cookie.bind(res), + locals: res.locals, + }, + next + ); + next(); + } catch (error) { + if (error instanceof errors.SeedApiError) { + console.warn( + `Endpoint 'create' unexpectedly threw ${error.constructor.name}.` + + ` If this was intentional, please add ${error.constructor.name} to` + + " the endpoint's errors list in your Fern Definition." + ); + await error.send(res); + } else { + res.status(500).json("Internal Server Error"); + } + next(error); + } + } else { + res.status(422).json({ + errors: request.errors.map( + (error) => ["request", ...error.path].join(" -> ") + ": " + error.message + ), + }); + next(request.errors); + } + }); + return this.router; + } +} diff --git a/seed/ts-express/grpc-proto/api/resources/user/service/index.ts b/seed/ts-express/grpc-proto/api/resources/user/service/index.ts new file mode 100644 index 00000000000..415726b7fea --- /dev/null +++ b/seed/ts-express/grpc-proto/api/resources/user/service/index.ts @@ -0,0 +1 @@ +export * from "./requests"; diff --git a/seed/ts-express/grpc-proto/api/resources/user/service/requests/CreateRequest.ts b/seed/ts-express/grpc-proto/api/resources/user/service/requests/CreateRequest.ts new file mode 100644 index 00000000000..d3df6e71461 --- /dev/null +++ b/seed/ts-express/grpc-proto/api/resources/user/service/requests/CreateRequest.ts @@ -0,0 +1,11 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +export interface CreateRequest { + username?: string; + email?: string; + age?: number; + weight?: number; + metadata?: Record; +} diff --git a/seed/ts-express/grpc-proto/api/resources/user/service/requests/index.ts b/seed/ts-express/grpc-proto/api/resources/user/service/requests/index.ts new file mode 100644 index 00000000000..4f8adcb4e83 --- /dev/null +++ b/seed/ts-express/grpc-proto/api/resources/user/service/requests/index.ts @@ -0,0 +1 @@ +export { CreateRequest } from "./CreateRequest"; diff --git a/seed/ts-express/grpc-proto/api/types/CreateResponse.ts b/seed/ts-express/grpc-proto/api/types/CreateResponse.ts new file mode 100644 index 00000000000..03aa8d0606d --- /dev/null +++ b/seed/ts-express/grpc-proto/api/types/CreateResponse.ts @@ -0,0 +1,9 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as SeedApi from "../index"; + +export interface CreateResponse { + user?: SeedApi.UserModel; +} diff --git a/seed/ts-express/grpc-proto/api/types/UserModel.ts b/seed/ts-express/grpc-proto/api/types/UserModel.ts new file mode 100644 index 00000000000..591c0c6bb88 --- /dev/null +++ b/seed/ts-express/grpc-proto/api/types/UserModel.ts @@ -0,0 +1,11 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +export interface UserModel { + username?: string; + email?: string; + age?: number; + weight?: number; + metadata?: Record; +} diff --git a/seed/ts-express/grpc-proto/api/types/index.ts b/seed/ts-express/grpc-proto/api/types/index.ts new file mode 100644 index 00000000000..8685532c76b --- /dev/null +++ b/seed/ts-express/grpc-proto/api/types/index.ts @@ -0,0 +1,2 @@ +export * from "./CreateResponse"; +export * from "./UserModel"; diff --git a/seed/ts-express/grpc-proto/core/index.ts b/seed/ts-express/grpc-proto/core/index.ts new file mode 100644 index 00000000000..3ae53c06d38 --- /dev/null +++ b/seed/ts-express/grpc-proto/core/index.ts @@ -0,0 +1 @@ +export * as serialization from "./schemas"; diff --git a/seed/ts-express/grpc-proto/core/schemas/Schema.ts b/seed/ts-express/grpc-proto/core/schemas/Schema.ts new file mode 100644 index 00000000000..19acc5dc44b --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/Schema.ts @@ -0,0 +1,98 @@ +import { SchemaUtils } from "./builders"; + +export type Schema = BaseSchema & SchemaUtils; + +export type inferRaw = S extends Schema ? Raw : never; +export type inferParsed = S extends Schema ? Parsed : never; + +export interface BaseSchema { + parse: (raw: unknown, opts?: SchemaOptions) => MaybeValid; + json: (parsed: unknown, opts?: SchemaOptions) => MaybeValid; + getType: () => SchemaType | SchemaType; +} + +export const SchemaType = { + DATE: "date", + ENUM: "enum", + LIST: "list", + STRING_LITERAL: "stringLiteral", + BOOLEAN_LITERAL: "booleanLiteral", + OBJECT: "object", + ANY: "any", + BOOLEAN: "boolean", + NUMBER: "number", + STRING: "string", + UNKNOWN: "unknown", + RECORD: "record", + SET: "set", + UNION: "union", + UNDISCRIMINATED_UNION: "undiscriminatedUnion", + OPTIONAL: "optional", +} as const; +export type SchemaType = typeof SchemaType[keyof typeof SchemaType]; + +export type MaybeValid = Valid | Invalid; + +export interface Valid { + ok: true; + value: T; +} + +export interface Invalid { + ok: false; + errors: ValidationError[]; +} + +export interface ValidationError { + path: string[]; + message: string; +} + +export interface SchemaOptions { + /** + * how to handle unrecognized keys in objects + * + * @default "fail" + */ + unrecognizedObjectKeys?: "fail" | "passthrough" | "strip"; + + /** + * whether to fail when an unrecognized discriminant value is + * encountered in a union + * + * @default false + */ + allowUnrecognizedUnionMembers?: boolean; + + /** + * whether to fail when an unrecognized enum value is encountered + * + * @default false + */ + allowUnrecognizedEnumValues?: boolean; + + /** + * whether to allow data that doesn't conform to the schema. + * invalid data is passed through without transformation. + * + * when this is enabled, .parse() and .json() will always + * return `ok: true`. `.parseOrThrow()` and `.jsonOrThrow()` + * will never fail. + * + * @default false + */ + skipValidation?: boolean; + + /** + * each validation failure contains a "path" property, which is + * the breadcrumbs to the offending node in the JSON. you can supply + * a prefix that is prepended to all the errors' paths. this can be + * helpful for zurg's internal debug logging. + */ + breadcrumbsPrefix?: string[]; + + /** + * whether to send 'null' for optional properties explicitly set to 'undefined'. + */ + omitUndefined?: boolean; +} diff --git a/seed/ts-express/grpc-proto/core/schemas/builders/date/date.ts b/seed/ts-express/grpc-proto/core/schemas/builders/date/date.ts new file mode 100644 index 00000000000..b70f24b045a --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/builders/date/date.ts @@ -0,0 +1,65 @@ +import { BaseSchema, Schema, SchemaType } from "../../Schema"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { getSchemaUtils } from "../schema-utils"; + +// https://stackoverflow.com/questions/12756159/regex-and-iso8601-formatted-datetime +const ISO_8601_REGEX = + /^([+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24:?00)([.,]\d+(?!:))?)?(\17[0-5]\d([.,]\d+)?)?([zZ]|([+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/; + +export function date(): Schema { + const baseSchema: BaseSchema = { + parse: (raw, { breadcrumbsPrefix = [] } = {}) => { + if (typeof raw !== "string") { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(raw, "string"), + }, + ], + }; + } + if (!ISO_8601_REGEX.test(raw)) { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(raw, "ISO 8601 date string"), + }, + ], + }; + } + return { + ok: true, + value: new Date(raw), + }; + }, + json: (date, { breadcrumbsPrefix = [] } = {}) => { + if (date instanceof Date) { + return { + ok: true, + value: date.toISOString(), + }; + } else { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(date, "Date object"), + }, + ], + }; + } + }, + getType: () => SchemaType.DATE, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + }; +} diff --git a/seed/ts-express/grpc-proto/core/schemas/builders/date/index.ts b/seed/ts-express/grpc-proto/core/schemas/builders/date/index.ts new file mode 100644 index 00000000000..187b29040f6 --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/builders/date/index.ts @@ -0,0 +1 @@ +export { date } from "./date"; diff --git a/seed/ts-express/grpc-proto/core/schemas/builders/enum/enum.ts b/seed/ts-express/grpc-proto/core/schemas/builders/enum/enum.ts new file mode 100644 index 00000000000..c1e24d69dec --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/builders/enum/enum.ts @@ -0,0 +1,43 @@ +import { Schema, SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; + +export function enum_(values: E): Schema { + const validValues = new Set(values); + + const schemaCreator = createIdentitySchemaCreator( + SchemaType.ENUM, + (value, { allowUnrecognizedEnumValues, breadcrumbsPrefix = [] } = {}) => { + if (typeof value !== "string") { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "string"), + }, + ], + }; + } + + if (!validValues.has(value) && !allowUnrecognizedEnumValues) { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "enum"), + }, + ], + }; + } + + return { + ok: true, + value: value as U, + }; + } + ); + + return schemaCreator(); +} diff --git a/seed/ts-express/grpc-proto/core/schemas/builders/enum/index.ts b/seed/ts-express/grpc-proto/core/schemas/builders/enum/index.ts new file mode 100644 index 00000000000..fe6faed93e3 --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/builders/enum/index.ts @@ -0,0 +1 @@ +export { enum_ } from "./enum"; diff --git a/seed/ts-express/grpc-proto/core/schemas/builders/index.ts b/seed/ts-express/grpc-proto/core/schemas/builders/index.ts new file mode 100644 index 00000000000..050cd2c4efb --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/builders/index.ts @@ -0,0 +1,13 @@ +export * from "./date"; +export * from "./enum"; +export * from "./lazy"; +export * from "./list"; +export * from "./literals"; +export * from "./object"; +export * from "./object-like"; +export * from "./primitives"; +export * from "./record"; +export * from "./schema-utils"; +export * from "./set"; +export * from "./undiscriminated-union"; +export * from "./union"; diff --git a/seed/ts-express/grpc-proto/core/schemas/builders/lazy/index.ts b/seed/ts-express/grpc-proto/core/schemas/builders/lazy/index.ts new file mode 100644 index 00000000000..77420fb031c --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/builders/lazy/index.ts @@ -0,0 +1,3 @@ +export { lazy } from "./lazy"; +export type { SchemaGetter } from "./lazy"; +export { lazyObject } from "./lazyObject"; diff --git a/seed/ts-express/grpc-proto/core/schemas/builders/lazy/lazy.ts b/seed/ts-express/grpc-proto/core/schemas/builders/lazy/lazy.ts new file mode 100644 index 00000000000..835c61f8a56 --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/builders/lazy/lazy.ts @@ -0,0 +1,32 @@ +import { BaseSchema, Schema } from "../../Schema"; +import { getSchemaUtils } from "../schema-utils"; + +export type SchemaGetter> = () => SchemaType; + +export function lazy(getter: SchemaGetter>): Schema { + const baseSchema = constructLazyBaseSchema(getter); + return { + ...baseSchema, + ...getSchemaUtils(baseSchema), + }; +} + +export function constructLazyBaseSchema( + getter: SchemaGetter> +): BaseSchema { + return { + parse: (raw, opts) => getMemoizedSchema(getter).parse(raw, opts), + json: (parsed, opts) => getMemoizedSchema(getter).json(parsed, opts), + getType: () => getMemoizedSchema(getter).getType(), + }; +} + +type MemoizedGetter> = SchemaGetter & { __zurg_memoized?: SchemaType }; + +export function getMemoizedSchema>(getter: SchemaGetter): SchemaType { + const castedGetter = getter as MemoizedGetter; + if (castedGetter.__zurg_memoized == null) { + castedGetter.__zurg_memoized = getter(); + } + return castedGetter.__zurg_memoized; +} diff --git a/seed/ts-express/grpc-proto/core/schemas/builders/lazy/lazyObject.ts b/seed/ts-express/grpc-proto/core/schemas/builders/lazy/lazyObject.ts new file mode 100644 index 00000000000..38c9e28404b --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/builders/lazy/lazyObject.ts @@ -0,0 +1,20 @@ +import { getObjectUtils } from "../object"; +import { getObjectLikeUtils } from "../object-like"; +import { BaseObjectSchema, ObjectSchema } from "../object/types"; +import { getSchemaUtils } from "../schema-utils"; +import { constructLazyBaseSchema, getMemoizedSchema, SchemaGetter } from "./lazy"; + +export function lazyObject(getter: SchemaGetter>): ObjectSchema { + const baseSchema: BaseObjectSchema = { + ...constructLazyBaseSchema(getter), + _getRawProperties: () => getMemoizedSchema(getter)._getRawProperties(), + _getParsedProperties: () => getMemoizedSchema(getter)._getParsedProperties(), + }; + + return { + ...baseSchema, + ...getSchemaUtils(baseSchema), + ...getObjectLikeUtils(baseSchema), + ...getObjectUtils(baseSchema), + }; +} diff --git a/seed/ts-express/grpc-proto/core/schemas/builders/list/index.ts b/seed/ts-express/grpc-proto/core/schemas/builders/list/index.ts new file mode 100644 index 00000000000..25f4bcc1737 --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/builders/list/index.ts @@ -0,0 +1 @@ +export { list } from "./list"; diff --git a/seed/ts-express/grpc-proto/core/schemas/builders/list/list.ts b/seed/ts-express/grpc-proto/core/schemas/builders/list/list.ts new file mode 100644 index 00000000000..e4c5c4a4a99 --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/builders/list/list.ts @@ -0,0 +1,73 @@ +import { BaseSchema, MaybeValid, Schema, SchemaType, ValidationError } from "../../Schema"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { getSchemaUtils } from "../schema-utils"; + +export function list(schema: Schema): Schema { + const baseSchema: BaseSchema = { + parse: (raw, opts) => + validateAndTransformArray(raw, (item, index) => + schema.parse(item, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `[${index}]`], + }) + ), + json: (parsed, opts) => + validateAndTransformArray(parsed, (item, index) => + schema.json(item, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `[${index}]`], + }) + ), + getType: () => SchemaType.LIST, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + }; +} + +function validateAndTransformArray( + value: unknown, + transformItem: (item: Raw, index: number) => MaybeValid +): MaybeValid { + if (!Array.isArray(value)) { + return { + ok: false, + errors: [ + { + message: getErrorMessageForIncorrectType(value, "list"), + path: [], + }, + ], + }; + } + + const maybeValidItems = value.map((item, index) => transformItem(item, index)); + + return maybeValidItems.reduce>( + (acc, item) => { + if (acc.ok && item.ok) { + return { + ok: true, + value: [...acc.value, item.value], + }; + } + + const errors: ValidationError[] = []; + if (!acc.ok) { + errors.push(...acc.errors); + } + if (!item.ok) { + errors.push(...item.errors); + } + + return { + ok: false, + errors, + }; + }, + { ok: true, value: [] } + ); +} diff --git a/seed/ts-express/grpc-proto/core/schemas/builders/literals/booleanLiteral.ts b/seed/ts-express/grpc-proto/core/schemas/builders/literals/booleanLiteral.ts new file mode 100644 index 00000000000..a83d22cd48a --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/builders/literals/booleanLiteral.ts @@ -0,0 +1,29 @@ +import { Schema, SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; + +export function booleanLiteral(literal: V): Schema { + const schemaCreator = createIdentitySchemaCreator( + SchemaType.BOOLEAN_LITERAL, + (value, { breadcrumbsPrefix = [] } = {}) => { + if (value === literal) { + return { + ok: true, + value: literal, + }; + } else { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, `${literal.toString()}`), + }, + ], + }; + } + } + ); + + return schemaCreator(); +} diff --git a/seed/ts-express/grpc-proto/core/schemas/builders/literals/index.ts b/seed/ts-express/grpc-proto/core/schemas/builders/literals/index.ts new file mode 100644 index 00000000000..d2bf08fc6ca --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/builders/literals/index.ts @@ -0,0 +1,2 @@ +export { stringLiteral } from "./stringLiteral"; +export { booleanLiteral } from "./booleanLiteral"; diff --git a/seed/ts-express/grpc-proto/core/schemas/builders/literals/stringLiteral.ts b/seed/ts-express/grpc-proto/core/schemas/builders/literals/stringLiteral.ts new file mode 100644 index 00000000000..3939b76b48d --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/builders/literals/stringLiteral.ts @@ -0,0 +1,29 @@ +import { Schema, SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; + +export function stringLiteral(literal: V): Schema { + const schemaCreator = createIdentitySchemaCreator( + SchemaType.STRING_LITERAL, + (value, { breadcrumbsPrefix = [] } = {}) => { + if (value === literal) { + return { + ok: true, + value: literal, + }; + } else { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, `"${literal}"`), + }, + ], + }; + } + } + ); + + return schemaCreator(); +} diff --git a/seed/ts-express/grpc-proto/core/schemas/builders/object-like/getObjectLikeUtils.ts b/seed/ts-express/grpc-proto/core/schemas/builders/object-like/getObjectLikeUtils.ts new file mode 100644 index 00000000000..8331d08da89 --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/builders/object-like/getObjectLikeUtils.ts @@ -0,0 +1,79 @@ +import { BaseSchema } from "../../Schema"; +import { filterObject } from "../../utils/filterObject"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { isPlainObject } from "../../utils/isPlainObject"; +import { getSchemaUtils } from "../schema-utils"; +import { ObjectLikeSchema, ObjectLikeUtils } from "./types"; + +export function getObjectLikeUtils(schema: BaseSchema): ObjectLikeUtils { + return { + withParsedProperties: (properties) => withParsedProperties(schema, properties), + }; +} + +/** + * object-like utils are defined in one file to resolve issues with circular imports + */ + +export function withParsedProperties( + objectLike: BaseSchema, + properties: { [K in keyof Properties]: Properties[K] | ((parsed: ParsedObjectShape) => Properties[K]) } +): ObjectLikeSchema { + const objectSchema: BaseSchema = { + parse: (raw, opts) => { + const parsedObject = objectLike.parse(raw, opts); + if (!parsedObject.ok) { + return parsedObject; + } + + const additionalProperties = Object.entries(properties).reduce>( + (processed, [key, value]) => { + return { + ...processed, + [key]: typeof value === "function" ? value(parsedObject.value) : value, + }; + }, + {} + ); + + return { + ok: true, + value: { + ...parsedObject.value, + ...(additionalProperties as Properties), + }, + }; + }, + + json: (parsed, opts) => { + if (!isPlainObject(parsed)) { + return { + ok: false, + errors: [ + { + path: opts?.breadcrumbsPrefix ?? [], + message: getErrorMessageForIncorrectType(parsed, "object"), + }, + ], + }; + } + + // strip out added properties + const addedPropertyKeys = new Set(Object.keys(properties)); + const parsedWithoutAddedProperties = filterObject( + parsed, + Object.keys(parsed).filter((key) => !addedPropertyKeys.has(key)) + ); + + return objectLike.json(parsedWithoutAddedProperties as ParsedObjectShape, opts); + }, + + getType: () => objectLike.getType(), + }; + + return { + ...objectSchema, + ...getSchemaUtils(objectSchema), + ...getObjectLikeUtils(objectSchema), + }; +} diff --git a/seed/ts-express/grpc-proto/core/schemas/builders/object-like/index.ts b/seed/ts-express/grpc-proto/core/schemas/builders/object-like/index.ts new file mode 100644 index 00000000000..c342e72cf9d --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/builders/object-like/index.ts @@ -0,0 +1,2 @@ +export { getObjectLikeUtils, withParsedProperties } from "./getObjectLikeUtils"; +export type { ObjectLikeSchema, ObjectLikeUtils } from "./types"; diff --git a/seed/ts-express/grpc-proto/core/schemas/builders/object-like/types.ts b/seed/ts-express/grpc-proto/core/schemas/builders/object-like/types.ts new file mode 100644 index 00000000000..75b3698729c --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/builders/object-like/types.ts @@ -0,0 +1,11 @@ +import { BaseSchema, Schema } from "../../Schema"; + +export type ObjectLikeSchema = Schema & + BaseSchema & + ObjectLikeUtils; + +export interface ObjectLikeUtils { + withParsedProperties: >(properties: { + [K in keyof T]: T[K] | ((parsed: Parsed) => T[K]); + }) => ObjectLikeSchema; +} diff --git a/seed/ts-express/grpc-proto/core/schemas/builders/object/index.ts b/seed/ts-express/grpc-proto/core/schemas/builders/object/index.ts new file mode 100644 index 00000000000..e3f4388db28 --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/builders/object/index.ts @@ -0,0 +1,22 @@ +export { getObjectUtils, object } from "./object"; +export { objectWithoutOptionalProperties } from "./objectWithoutOptionalProperties"; +export type { + inferObjectWithoutOptionalPropertiesSchemaFromPropertySchemas, + inferParsedObjectWithoutOptionalPropertiesFromPropertySchemas, +} from "./objectWithoutOptionalProperties"; +export { isProperty, property } from "./property"; +export type { Property } from "./property"; +export type { + BaseObjectSchema, + inferObjectSchemaFromPropertySchemas, + inferParsedObject, + inferParsedObjectFromPropertySchemas, + inferParsedPropertySchema, + inferRawKey, + inferRawObject, + inferRawObjectFromPropertySchemas, + inferRawPropertySchema, + ObjectSchema, + ObjectUtils, + PropertySchemas, +} from "./types"; diff --git a/seed/ts-express/grpc-proto/core/schemas/builders/object/object.ts b/seed/ts-express/grpc-proto/core/schemas/builders/object/object.ts new file mode 100644 index 00000000000..e00136d72fc --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/builders/object/object.ts @@ -0,0 +1,324 @@ +import { MaybeValid, Schema, SchemaType, ValidationError } from "../../Schema"; +import { entries } from "../../utils/entries"; +import { filterObject } from "../../utils/filterObject"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { isPlainObject } from "../../utils/isPlainObject"; +import { keys } from "../../utils/keys"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { partition } from "../../utils/partition"; +import { getObjectLikeUtils } from "../object-like"; +import { getSchemaUtils } from "../schema-utils"; +import { isProperty } from "./property"; +import { + BaseObjectSchema, + inferObjectSchemaFromPropertySchemas, + inferParsedObjectFromPropertySchemas, + inferRawObjectFromPropertySchemas, + ObjectSchema, + ObjectUtils, + PropertySchemas, +} from "./types"; + +interface ObjectPropertyWithRawKey { + rawKey: string; + parsedKey: string; + valueSchema: Schema; +} + +export function object>( + schemas: T +): inferObjectSchemaFromPropertySchemas { + const baseSchema: BaseObjectSchema< + inferRawObjectFromPropertySchemas, + inferParsedObjectFromPropertySchemas + > = { + _getRawProperties: () => + Object.entries(schemas).map(([parsedKey, propertySchema]) => + isProperty(propertySchema) ? propertySchema.rawKey : parsedKey + ) as unknown as (keyof inferRawObjectFromPropertySchemas)[], + _getParsedProperties: () => keys(schemas) as unknown as (keyof inferParsedObjectFromPropertySchemas)[], + + parse: (raw, opts) => { + const rawKeyToProperty: Record = {}; + const requiredKeys: string[] = []; + + for (const [parsedKey, schemaOrObjectProperty] of entries(schemas)) { + const rawKey = isProperty(schemaOrObjectProperty) ? schemaOrObjectProperty.rawKey : parsedKey; + const valueSchema: Schema = isProperty(schemaOrObjectProperty) + ? schemaOrObjectProperty.valueSchema + : schemaOrObjectProperty; + + const property: ObjectPropertyWithRawKey = { + rawKey, + parsedKey: parsedKey as string, + valueSchema, + }; + + rawKeyToProperty[rawKey] = property; + + if (isSchemaRequired(valueSchema)) { + requiredKeys.push(rawKey); + } + } + + return validateAndTransformObject({ + value: raw, + requiredKeys, + getProperty: (rawKey) => { + const property = rawKeyToProperty[rawKey]; + if (property == null) { + return undefined; + } + return { + transformedKey: property.parsedKey, + transform: (propertyValue) => + property.valueSchema.parse(propertyValue, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), rawKey], + }), + }; + }, + unrecognizedObjectKeys: opts?.unrecognizedObjectKeys, + skipValidation: opts?.skipValidation, + breadcrumbsPrefix: opts?.breadcrumbsPrefix, + omitUndefined: opts?.omitUndefined, + }); + }, + + json: (parsed, opts) => { + const requiredKeys: string[] = []; + + for (const [parsedKey, schemaOrObjectProperty] of entries(schemas)) { + const valueSchema: Schema = isProperty(schemaOrObjectProperty) + ? schemaOrObjectProperty.valueSchema + : schemaOrObjectProperty; + + if (isSchemaRequired(valueSchema)) { + requiredKeys.push(parsedKey as string); + } + } + + return validateAndTransformObject({ + value: parsed, + requiredKeys, + getProperty: ( + parsedKey + ): { transformedKey: string; transform: (propertyValue: unknown) => MaybeValid } | undefined => { + const property = schemas[parsedKey as keyof T]; + + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (property == null) { + return undefined; + } + + if (isProperty(property)) { + return { + transformedKey: property.rawKey, + transform: (propertyValue) => + property.valueSchema.json(propertyValue, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), parsedKey], + }), + }; + } else { + return { + transformedKey: parsedKey, + transform: (propertyValue) => + property.json(propertyValue, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), parsedKey], + }), + }; + } + }, + unrecognizedObjectKeys: opts?.unrecognizedObjectKeys, + skipValidation: opts?.skipValidation, + breadcrumbsPrefix: opts?.breadcrumbsPrefix, + omitUndefined: opts?.omitUndefined, + }); + }, + + getType: () => SchemaType.OBJECT, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + ...getObjectLikeUtils(baseSchema), + ...getObjectUtils(baseSchema), + }; +} + +function validateAndTransformObject({ + value, + requiredKeys, + getProperty, + unrecognizedObjectKeys = "fail", + skipValidation = false, + breadcrumbsPrefix = [], +}: { + value: unknown; + requiredKeys: string[]; + getProperty: ( + preTransformedKey: string + ) => { transformedKey: string; transform: (propertyValue: unknown) => MaybeValid } | undefined; + unrecognizedObjectKeys: "fail" | "passthrough" | "strip" | undefined; + skipValidation: boolean | undefined; + breadcrumbsPrefix: string[] | undefined; + omitUndefined: boolean | undefined; +}): MaybeValid { + if (!isPlainObject(value)) { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "object"), + }, + ], + }; + } + + const missingRequiredKeys = new Set(requiredKeys); + const errors: ValidationError[] = []; + const transformed: Record = {}; + + for (const [preTransformedKey, preTransformedItemValue] of Object.entries(value)) { + const property = getProperty(preTransformedKey); + + if (property != null) { + missingRequiredKeys.delete(preTransformedKey); + + const value = property.transform(preTransformedItemValue); + if (value.ok) { + transformed[property.transformedKey] = value.value; + } else { + transformed[preTransformedKey] = preTransformedItemValue; + errors.push(...value.errors); + } + } else { + switch (unrecognizedObjectKeys) { + case "fail": + errors.push({ + path: [...breadcrumbsPrefix, preTransformedKey], + message: `Unexpected key "${preTransformedKey}"`, + }); + break; + case "strip": + break; + case "passthrough": + transformed[preTransformedKey] = preTransformedItemValue; + break; + } + } + } + + errors.push( + ...requiredKeys + .filter((key) => missingRequiredKeys.has(key)) + .map((key) => ({ + path: breadcrumbsPrefix, + message: `Missing required key "${key}"`, + })) + ); + + if (errors.length === 0 || skipValidation) { + return { + ok: true, + value: transformed as Transformed, + }; + } else { + return { + ok: false, + errors, + }; + } +} + +export function getObjectUtils(schema: BaseObjectSchema): ObjectUtils { + return { + extend: (extension: ObjectSchema) => { + const baseSchema: BaseObjectSchema = { + _getParsedProperties: () => [...schema._getParsedProperties(), ...extension._getParsedProperties()], + _getRawProperties: () => [...schema._getRawProperties(), ...extension._getRawProperties()], + parse: (raw, opts) => { + return validateAndTransformExtendedObject({ + extensionKeys: extension._getRawProperties(), + value: raw, + transformBase: (rawBase) => schema.parse(rawBase, opts), + transformExtension: (rawExtension) => extension.parse(rawExtension, opts), + }); + }, + json: (parsed, opts) => { + return validateAndTransformExtendedObject({ + extensionKeys: extension._getParsedProperties(), + value: parsed, + transformBase: (parsedBase) => schema.json(parsedBase, opts), + transformExtension: (parsedExtension) => extension.json(parsedExtension, opts), + }); + }, + getType: () => SchemaType.OBJECT, + }; + + return { + ...baseSchema, + ...getSchemaUtils(baseSchema), + ...getObjectLikeUtils(baseSchema), + ...getObjectUtils(baseSchema), + }; + }, + }; +} + +function validateAndTransformExtendedObject({ + extensionKeys, + value, + transformBase, + transformExtension, +}: { + extensionKeys: (keyof PreTransformedExtension)[]; + value: unknown; + transformBase: (value: unknown) => MaybeValid; + transformExtension: (value: unknown) => MaybeValid; +}): MaybeValid { + const extensionPropertiesSet = new Set(extensionKeys); + const [extensionProperties, baseProperties] = partition(keys(value), (key) => + extensionPropertiesSet.has(key as keyof PreTransformedExtension) + ); + + const transformedBase = transformBase(filterObject(value, baseProperties)); + const transformedExtension = transformExtension(filterObject(value, extensionProperties)); + + if (transformedBase.ok && transformedExtension.ok) { + return { + ok: true, + value: { + ...transformedBase.value, + ...transformedExtension.value, + }, + }; + } else { + return { + ok: false, + errors: [ + ...(transformedBase.ok ? [] : transformedBase.errors), + ...(transformedExtension.ok ? [] : transformedExtension.errors), + ], + }; + } +} + +function isSchemaRequired(schema: Schema): boolean { + return !isSchemaOptional(schema); +} + +function isSchemaOptional(schema: Schema): boolean { + switch (schema.getType()) { + case SchemaType.ANY: + case SchemaType.UNKNOWN: + case SchemaType.OPTIONAL: + return true; + default: + return false; + } +} diff --git a/seed/ts-express/grpc-proto/core/schemas/builders/object/objectWithoutOptionalProperties.ts b/seed/ts-express/grpc-proto/core/schemas/builders/object/objectWithoutOptionalProperties.ts new file mode 100644 index 00000000000..a0951f48efc --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/builders/object/objectWithoutOptionalProperties.ts @@ -0,0 +1,18 @@ +import { object } from "./object"; +import { inferParsedPropertySchema, inferRawObjectFromPropertySchemas, ObjectSchema, PropertySchemas } from "./types"; + +export function objectWithoutOptionalProperties>( + schemas: T +): inferObjectWithoutOptionalPropertiesSchemaFromPropertySchemas { + return object(schemas) as unknown as inferObjectWithoutOptionalPropertiesSchemaFromPropertySchemas; +} + +export type inferObjectWithoutOptionalPropertiesSchemaFromPropertySchemas> = + ObjectSchema< + inferRawObjectFromPropertySchemas, + inferParsedObjectWithoutOptionalPropertiesFromPropertySchemas + >; + +export type inferParsedObjectWithoutOptionalPropertiesFromPropertySchemas> = { + [K in keyof T]: inferParsedPropertySchema; +}; diff --git a/seed/ts-express/grpc-proto/core/schemas/builders/object/property.ts b/seed/ts-express/grpc-proto/core/schemas/builders/object/property.ts new file mode 100644 index 00000000000..d245c4b193a --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/builders/object/property.ts @@ -0,0 +1,23 @@ +import { Schema } from "../../Schema"; + +export function property( + rawKey: RawKey, + valueSchema: Schema +): Property { + return { + rawKey, + valueSchema, + isProperty: true, + }; +} + +export interface Property { + rawKey: RawKey; + valueSchema: Schema; + isProperty: true; +} + +export function isProperty>(maybeProperty: unknown): maybeProperty is O { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + return (maybeProperty as O).isProperty; +} diff --git a/seed/ts-express/grpc-proto/core/schemas/builders/object/types.ts b/seed/ts-express/grpc-proto/core/schemas/builders/object/types.ts new file mode 100644 index 00000000000..de9bb4074e5 --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/builders/object/types.ts @@ -0,0 +1,72 @@ +import { BaseSchema, inferParsed, inferRaw, Schema } from "../../Schema"; +import { addQuestionMarksToNullableProperties } from "../../utils/addQuestionMarksToNullableProperties"; +import { ObjectLikeUtils } from "../object-like"; +import { SchemaUtils } from "../schema-utils"; +import { Property } from "./property"; + +export type ObjectSchema = BaseObjectSchema & + ObjectLikeUtils & + ObjectUtils & + SchemaUtils; + +export interface BaseObjectSchema extends BaseSchema { + _getRawProperties: () => (keyof Raw)[]; + _getParsedProperties: () => (keyof Parsed)[]; +} + +export interface ObjectUtils { + extend: ( + schemas: ObjectSchema + ) => ObjectSchema; +} + +export type inferRawObject> = O extends ObjectSchema ? Raw : never; + +export type inferParsedObject> = O extends ObjectSchema + ? Parsed + : never; + +export type inferObjectSchemaFromPropertySchemas> = ObjectSchema< + inferRawObjectFromPropertySchemas, + inferParsedObjectFromPropertySchemas +>; + +export type inferRawObjectFromPropertySchemas> = + addQuestionMarksToNullableProperties<{ + [ParsedKey in keyof T as inferRawKey]: inferRawPropertySchema; + }>; + +export type inferParsedObjectFromPropertySchemas> = + addQuestionMarksToNullableProperties<{ + [K in keyof T]: inferParsedPropertySchema; + }>; + +export type PropertySchemas = Record< + ParsedKeys, + Property | Schema +>; + +export type inferRawPropertySchema

| Schema> = P extends Property< + any, + infer Raw, + any +> + ? Raw + : P extends Schema + ? inferRaw

+ : never; + +export type inferParsedPropertySchema

| Schema> = P extends Property< + any, + any, + infer Parsed +> + ? Parsed + : P extends Schema + ? inferParsed

+ : never; + +export type inferRawKey< + ParsedKey extends string | number | symbol, + P extends Property | Schema +> = P extends Property ? Raw : ParsedKey; diff --git a/seed/ts-express/grpc-proto/core/schemas/builders/primitives/any.ts b/seed/ts-express/grpc-proto/core/schemas/builders/primitives/any.ts new file mode 100644 index 00000000000..fcaeb04255a --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/builders/primitives/any.ts @@ -0,0 +1,4 @@ +import { SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; + +export const any = createIdentitySchemaCreator(SchemaType.ANY, (value) => ({ ok: true, value })); diff --git a/seed/ts-express/grpc-proto/core/schemas/builders/primitives/boolean.ts b/seed/ts-express/grpc-proto/core/schemas/builders/primitives/boolean.ts new file mode 100644 index 00000000000..fad60562120 --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/builders/primitives/boolean.ts @@ -0,0 +1,25 @@ +import { SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; + +export const boolean = createIdentitySchemaCreator( + SchemaType.BOOLEAN, + (value, { breadcrumbsPrefix = [] } = {}) => { + if (typeof value === "boolean") { + return { + ok: true, + value, + }; + } else { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "boolean"), + }, + ], + }; + } + } +); diff --git a/seed/ts-express/grpc-proto/core/schemas/builders/primitives/index.ts b/seed/ts-express/grpc-proto/core/schemas/builders/primitives/index.ts new file mode 100644 index 00000000000..788f9416bfe --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/builders/primitives/index.ts @@ -0,0 +1,5 @@ +export { any } from "./any"; +export { boolean } from "./boolean"; +export { number } from "./number"; +export { string } from "./string"; +export { unknown } from "./unknown"; diff --git a/seed/ts-express/grpc-proto/core/schemas/builders/primitives/number.ts b/seed/ts-express/grpc-proto/core/schemas/builders/primitives/number.ts new file mode 100644 index 00000000000..c2689456936 --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/builders/primitives/number.ts @@ -0,0 +1,25 @@ +import { SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; + +export const number = createIdentitySchemaCreator( + SchemaType.NUMBER, + (value, { breadcrumbsPrefix = [] } = {}) => { + if (typeof value === "number") { + return { + ok: true, + value, + }; + } else { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "number"), + }, + ], + }; + } + } +); diff --git a/seed/ts-express/grpc-proto/core/schemas/builders/primitives/string.ts b/seed/ts-express/grpc-proto/core/schemas/builders/primitives/string.ts new file mode 100644 index 00000000000..949f1f2a630 --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/builders/primitives/string.ts @@ -0,0 +1,25 @@ +import { SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; + +export const string = createIdentitySchemaCreator( + SchemaType.STRING, + (value, { breadcrumbsPrefix = [] } = {}) => { + if (typeof value === "string") { + return { + ok: true, + value, + }; + } else { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "string"), + }, + ], + }; + } + } +); diff --git a/seed/ts-express/grpc-proto/core/schemas/builders/primitives/unknown.ts b/seed/ts-express/grpc-proto/core/schemas/builders/primitives/unknown.ts new file mode 100644 index 00000000000..4d5249571f5 --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/builders/primitives/unknown.ts @@ -0,0 +1,4 @@ +import { SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; + +export const unknown = createIdentitySchemaCreator(SchemaType.UNKNOWN, (value) => ({ ok: true, value })); diff --git a/seed/ts-express/grpc-proto/core/schemas/builders/record/index.ts b/seed/ts-express/grpc-proto/core/schemas/builders/record/index.ts new file mode 100644 index 00000000000..82e25c5c2af --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/builders/record/index.ts @@ -0,0 +1,2 @@ +export { record } from "./record"; +export type { BaseRecordSchema, RecordSchema } from "./types"; diff --git a/seed/ts-express/grpc-proto/core/schemas/builders/record/record.ts b/seed/ts-express/grpc-proto/core/schemas/builders/record/record.ts new file mode 100644 index 00000000000..6683ac3609f --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/builders/record/record.ts @@ -0,0 +1,130 @@ +import { MaybeValid, Schema, SchemaType, ValidationError } from "../../Schema"; +import { entries } from "../../utils/entries"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { isPlainObject } from "../../utils/isPlainObject"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { getSchemaUtils } from "../schema-utils"; +import { BaseRecordSchema, RecordSchema } from "./types"; + +export function record( + keySchema: Schema, + valueSchema: Schema +): RecordSchema { + const baseSchema: BaseRecordSchema = { + parse: (raw, opts) => { + return validateAndTransformRecord({ + value: raw, + isKeyNumeric: keySchema.getType() === SchemaType.NUMBER, + transformKey: (key) => + keySchema.parse(key, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `${key} (key)`], + }), + transformValue: (value, key) => + valueSchema.parse(value, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `${key}`], + }), + breadcrumbsPrefix: opts?.breadcrumbsPrefix, + }); + }, + json: (parsed, opts) => { + return validateAndTransformRecord({ + value: parsed, + isKeyNumeric: keySchema.getType() === SchemaType.NUMBER, + transformKey: (key) => + keySchema.json(key, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `${key} (key)`], + }), + transformValue: (value, key) => + valueSchema.json(value, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `${key}`], + }), + breadcrumbsPrefix: opts?.breadcrumbsPrefix, + }); + }, + getType: () => SchemaType.RECORD, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + }; +} + +function validateAndTransformRecord({ + value, + isKeyNumeric, + transformKey, + transformValue, + breadcrumbsPrefix = [], +}: { + value: unknown; + isKeyNumeric: boolean; + transformKey: (key: string | number) => MaybeValid; + transformValue: (value: unknown, key: string | number) => MaybeValid; + breadcrumbsPrefix: string[] | undefined; +}): MaybeValid> { + if (!isPlainObject(value)) { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "object"), + }, + ], + }; + } + + return entries(value).reduce>>( + (accPromise, [stringKey, value]) => { + // skip nullish keys + if (value == null) { + return accPromise; + } + + const acc = accPromise; + + let key: string | number = stringKey; + if (isKeyNumeric) { + const numberKey = stringKey.length > 0 ? Number(stringKey) : NaN; + if (!isNaN(numberKey)) { + key = numberKey; + } + } + const transformedKey = transformKey(key); + + const transformedValue = transformValue(value, key); + + if (acc.ok && transformedKey.ok && transformedValue.ok) { + return { + ok: true, + value: { + ...acc.value, + [transformedKey.value]: transformedValue.value, + }, + }; + } + + const errors: ValidationError[] = []; + if (!acc.ok) { + errors.push(...acc.errors); + } + if (!transformedKey.ok) { + errors.push(...transformedKey.errors); + } + if (!transformedValue.ok) { + errors.push(...transformedValue.errors); + } + + return { + ok: false, + errors, + }; + }, + { ok: true, value: {} as Record } + ); +} diff --git a/seed/ts-express/grpc-proto/core/schemas/builders/record/types.ts b/seed/ts-express/grpc-proto/core/schemas/builders/record/types.ts new file mode 100644 index 00000000000..eb82cc7f65c --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/builders/record/types.ts @@ -0,0 +1,17 @@ +import { BaseSchema } from "../../Schema"; +import { SchemaUtils } from "../schema-utils"; + +export type RecordSchema< + RawKey extends string | number, + RawValue, + ParsedKey extends string | number, + ParsedValue +> = BaseRecordSchema & + SchemaUtils, Record>; + +export type BaseRecordSchema< + RawKey extends string | number, + RawValue, + ParsedKey extends string | number, + ParsedValue +> = BaseSchema, Record>; diff --git a/seed/ts-express/grpc-proto/core/schemas/builders/schema-utils/JsonError.ts b/seed/ts-express/grpc-proto/core/schemas/builders/schema-utils/JsonError.ts new file mode 100644 index 00000000000..2b89ca0e7ad --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/builders/schema-utils/JsonError.ts @@ -0,0 +1,9 @@ +import { ValidationError } from "../../Schema"; +import { stringifyValidationError } from "./stringifyValidationErrors"; + +export class JsonError extends Error { + constructor(public readonly errors: ValidationError[]) { + super(errors.map(stringifyValidationError).join("; ")); + Object.setPrototypeOf(this, JsonError.prototype); + } +} diff --git a/seed/ts-express/grpc-proto/core/schemas/builders/schema-utils/ParseError.ts b/seed/ts-express/grpc-proto/core/schemas/builders/schema-utils/ParseError.ts new file mode 100644 index 00000000000..d056eb45cf7 --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/builders/schema-utils/ParseError.ts @@ -0,0 +1,9 @@ +import { ValidationError } from "../../Schema"; +import { stringifyValidationError } from "./stringifyValidationErrors"; + +export class ParseError extends Error { + constructor(public readonly errors: ValidationError[]) { + super(errors.map(stringifyValidationError).join("; ")); + Object.setPrototypeOf(this, ParseError.prototype); + } +} diff --git a/seed/ts-express/grpc-proto/core/schemas/builders/schema-utils/getSchemaUtils.ts b/seed/ts-express/grpc-proto/core/schemas/builders/schema-utils/getSchemaUtils.ts new file mode 100644 index 00000000000..79ecad92132 --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/builders/schema-utils/getSchemaUtils.ts @@ -0,0 +1,105 @@ +import { BaseSchema, Schema, SchemaOptions, SchemaType } from "../../Schema"; +import { JsonError } from "./JsonError"; +import { ParseError } from "./ParseError"; + +export interface SchemaUtils { + optional: () => Schema; + transform: (transformer: SchemaTransformer) => Schema; + parseOrThrow: (raw: unknown, opts?: SchemaOptions) => Parsed; + jsonOrThrow: (raw: unknown, opts?: SchemaOptions) => Raw; +} + +export interface SchemaTransformer { + transform: (parsed: Parsed) => Transformed; + untransform: (transformed: any) => Parsed; +} + +export function getSchemaUtils(schema: BaseSchema): SchemaUtils { + return { + optional: () => optional(schema), + transform: (transformer) => transform(schema, transformer), + parseOrThrow: (raw, opts) => { + const parsed = schema.parse(raw, opts); + if (parsed.ok) { + return parsed.value; + } + throw new ParseError(parsed.errors); + }, + jsonOrThrow: (parsed, opts) => { + const raw = schema.json(parsed, opts); + if (raw.ok) { + return raw.value; + } + throw new JsonError(raw.errors); + }, + }; +} + +/** + * schema utils are defined in one file to resolve issues with circular imports + */ + +export function optional( + schema: BaseSchema +): Schema { + const baseSchema: BaseSchema = { + parse: (raw, opts) => { + if (raw == null) { + return { + ok: true, + value: undefined, + }; + } + return schema.parse(raw, opts); + }, + json: (parsed, opts) => { + if (opts?.omitUndefined && parsed === undefined) { + return { + ok: true, + value: undefined, + }; + } + if (parsed == null) { + return { + ok: true, + value: null, + }; + } + return schema.json(parsed, opts); + }, + getType: () => SchemaType.OPTIONAL, + }; + + return { + ...baseSchema, + ...getSchemaUtils(baseSchema), + }; +} + +export function transform( + schema: BaseSchema, + transformer: SchemaTransformer +): Schema { + const baseSchema: BaseSchema = { + parse: (raw, opts) => { + const parsed = schema.parse(raw, opts); + if (!parsed.ok) { + return parsed; + } + return { + ok: true, + value: transformer.transform(parsed.value), + }; + }, + json: (transformed, opts) => { + const parsed = transformer.untransform(transformed); + return schema.json(parsed, opts); + }, + getType: () => schema.getType(), + }; + + return { + ...baseSchema, + ...getSchemaUtils(baseSchema), + }; +} diff --git a/seed/ts-express/grpc-proto/core/schemas/builders/schema-utils/index.ts b/seed/ts-express/grpc-proto/core/schemas/builders/schema-utils/index.ts new file mode 100644 index 00000000000..aa04e051dfa --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/builders/schema-utils/index.ts @@ -0,0 +1,4 @@ +export { getSchemaUtils, optional, transform } from "./getSchemaUtils"; +export type { SchemaUtils } from "./getSchemaUtils"; +export { JsonError } from "./JsonError"; +export { ParseError } from "./ParseError"; diff --git a/seed/ts-express/grpc-proto/core/schemas/builders/schema-utils/stringifyValidationErrors.ts b/seed/ts-express/grpc-proto/core/schemas/builders/schema-utils/stringifyValidationErrors.ts new file mode 100644 index 00000000000..4160f0a2617 --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/builders/schema-utils/stringifyValidationErrors.ts @@ -0,0 +1,8 @@ +import { ValidationError } from "../../Schema"; + +export function stringifyValidationError(error: ValidationError): string { + if (error.path.length === 0) { + return error.message; + } + return `${error.path.join(" -> ")}: ${error.message}`; +} diff --git a/seed/ts-express/grpc-proto/core/schemas/builders/set/index.ts b/seed/ts-express/grpc-proto/core/schemas/builders/set/index.ts new file mode 100644 index 00000000000..f3310e8bdad --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/builders/set/index.ts @@ -0,0 +1 @@ +export { set } from "./set"; diff --git a/seed/ts-express/grpc-proto/core/schemas/builders/set/set.ts b/seed/ts-express/grpc-proto/core/schemas/builders/set/set.ts new file mode 100644 index 00000000000..e9e6bb7e539 --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/builders/set/set.ts @@ -0,0 +1,43 @@ +import { BaseSchema, Schema, SchemaType } from "../../Schema"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { list } from "../list"; +import { getSchemaUtils } from "../schema-utils"; + +export function set(schema: Schema): Schema> { + const listSchema = list(schema); + const baseSchema: BaseSchema> = { + parse: (raw, opts) => { + const parsedList = listSchema.parse(raw, opts); + if (parsedList.ok) { + return { + ok: true, + value: new Set(parsedList.value), + }; + } else { + return parsedList; + } + }, + json: (parsed, opts) => { + if (!(parsed instanceof Set)) { + return { + ok: false, + errors: [ + { + path: opts?.breadcrumbsPrefix ?? [], + message: getErrorMessageForIncorrectType(parsed, "Set"), + }, + ], + }; + } + const jsonList = listSchema.json([...parsed], opts); + return jsonList; + }, + getType: () => SchemaType.SET, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + }; +} diff --git a/seed/ts-express/grpc-proto/core/schemas/builders/undiscriminated-union/index.ts b/seed/ts-express/grpc-proto/core/schemas/builders/undiscriminated-union/index.ts new file mode 100644 index 00000000000..75b71cb3565 --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/builders/undiscriminated-union/index.ts @@ -0,0 +1,6 @@ +export type { + inferParsedUnidiscriminatedUnionSchema, + inferRawUnidiscriminatedUnionSchema, + UndiscriminatedUnionSchema, +} from "./types"; +export { undiscriminatedUnion } from "./undiscriminatedUnion"; diff --git a/seed/ts-express/grpc-proto/core/schemas/builders/undiscriminated-union/types.ts b/seed/ts-express/grpc-proto/core/schemas/builders/undiscriminated-union/types.ts new file mode 100644 index 00000000000..43e7108a060 --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/builders/undiscriminated-union/types.ts @@ -0,0 +1,10 @@ +import { inferParsed, inferRaw, Schema } from "../../Schema"; + +export type UndiscriminatedUnionSchema = Schema< + inferRawUnidiscriminatedUnionSchema, + inferParsedUnidiscriminatedUnionSchema +>; + +export type inferRawUnidiscriminatedUnionSchema = inferRaw; + +export type inferParsedUnidiscriminatedUnionSchema = inferParsed; diff --git a/seed/ts-express/grpc-proto/core/schemas/builders/undiscriminated-union/undiscriminatedUnion.ts b/seed/ts-express/grpc-proto/core/schemas/builders/undiscriminated-union/undiscriminatedUnion.ts new file mode 100644 index 00000000000..21ed3df0f40 --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/builders/undiscriminated-union/undiscriminatedUnion.ts @@ -0,0 +1,60 @@ +import { BaseSchema, MaybeValid, Schema, SchemaOptions, SchemaType, ValidationError } from "../../Schema"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { getSchemaUtils } from "../schema-utils"; +import { inferParsedUnidiscriminatedUnionSchema, inferRawUnidiscriminatedUnionSchema } from "./types"; + +export function undiscriminatedUnion, ...Schema[]]>( + schemas: Schemas +): Schema, inferParsedUnidiscriminatedUnionSchema> { + const baseSchema: BaseSchema< + inferRawUnidiscriminatedUnionSchema, + inferParsedUnidiscriminatedUnionSchema + > = { + parse: (raw, opts) => { + return validateAndTransformUndiscriminatedUnion>( + (schema, opts) => schema.parse(raw, opts), + schemas, + opts + ); + }, + json: (parsed, opts) => { + return validateAndTransformUndiscriminatedUnion>( + (schema, opts) => schema.json(parsed, opts), + schemas, + opts + ); + }, + getType: () => SchemaType.UNDISCRIMINATED_UNION, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + }; +} + +function validateAndTransformUndiscriminatedUnion( + transform: (schema: Schema, opts: SchemaOptions) => MaybeValid, + schemas: Schema[], + opts: SchemaOptions | undefined +): MaybeValid { + const errors: ValidationError[] = []; + for (const [index, schema] of schemas.entries()) { + const transformed = transform(schema, { ...opts, skipValidation: false }); + if (transformed.ok) { + return transformed; + } else { + for (const error of transformed.errors) { + errors.push({ + path: error.path, + message: `[Variant ${index}] ${error.message}`, + }); + } + } + } + + return { + ok: false, + errors, + }; +} diff --git a/seed/ts-express/grpc-proto/core/schemas/builders/union/discriminant.ts b/seed/ts-express/grpc-proto/core/schemas/builders/union/discriminant.ts new file mode 100644 index 00000000000..55065bc8946 --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/builders/union/discriminant.ts @@ -0,0 +1,14 @@ +export function discriminant( + parsedDiscriminant: ParsedDiscriminant, + rawDiscriminant: RawDiscriminant +): Discriminant { + return { + parsedDiscriminant, + rawDiscriminant, + }; +} + +export interface Discriminant { + parsedDiscriminant: ParsedDiscriminant; + rawDiscriminant: RawDiscriminant; +} diff --git a/seed/ts-express/grpc-proto/core/schemas/builders/union/index.ts b/seed/ts-express/grpc-proto/core/schemas/builders/union/index.ts new file mode 100644 index 00000000000..85fc008a2d8 --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/builders/union/index.ts @@ -0,0 +1,10 @@ +export { discriminant } from "./discriminant"; +export type { Discriminant } from "./discriminant"; +export type { + inferParsedDiscriminant, + inferParsedUnion, + inferRawDiscriminant, + inferRawUnion, + UnionSubtypes, +} from "./types"; +export { union } from "./union"; diff --git a/seed/ts-express/grpc-proto/core/schemas/builders/union/types.ts b/seed/ts-express/grpc-proto/core/schemas/builders/union/types.ts new file mode 100644 index 00000000000..6f82c868b2d --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/builders/union/types.ts @@ -0,0 +1,26 @@ +import { inferParsedObject, inferRawObject, ObjectSchema } from "../object"; +import { Discriminant } from "./discriminant"; + +export type UnionSubtypes = { + [K in DiscriminantValues]: ObjectSchema; +}; + +export type inferRawUnion, U extends UnionSubtypes> = { + [K in keyof U]: Record, K> & inferRawObject; +}[keyof U]; + +export type inferParsedUnion, U extends UnionSubtypes> = { + [K in keyof U]: Record, K> & inferParsedObject; +}[keyof U]; + +export type inferRawDiscriminant> = D extends string + ? D + : D extends Discriminant + ? Raw + : never; + +export type inferParsedDiscriminant> = D extends string + ? D + : D extends Discriminant + ? Parsed + : never; diff --git a/seed/ts-express/grpc-proto/core/schemas/builders/union/union.ts b/seed/ts-express/grpc-proto/core/schemas/builders/union/union.ts new file mode 100644 index 00000000000..ab61475a572 --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/builders/union/union.ts @@ -0,0 +1,170 @@ +import { BaseSchema, MaybeValid, SchemaType } from "../../Schema"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { isPlainObject } from "../../utils/isPlainObject"; +import { keys } from "../../utils/keys"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { enum_ } from "../enum"; +import { ObjectSchema } from "../object"; +import { getObjectLikeUtils, ObjectLikeSchema } from "../object-like"; +import { getSchemaUtils } from "../schema-utils"; +import { Discriminant } from "./discriminant"; +import { inferParsedDiscriminant, inferParsedUnion, inferRawDiscriminant, inferRawUnion, UnionSubtypes } from "./types"; + +export function union, U extends UnionSubtypes>( + discriminant: D, + union: U +): ObjectLikeSchema, inferParsedUnion> { + const rawDiscriminant = + typeof discriminant === "string" ? discriminant : (discriminant.rawDiscriminant as inferRawDiscriminant); + const parsedDiscriminant = + typeof discriminant === "string" + ? discriminant + : (discriminant.parsedDiscriminant as inferParsedDiscriminant); + + const discriminantValueSchema = enum_(keys(union) as string[]); + + const baseSchema: BaseSchema, inferParsedUnion> = { + parse: (raw, opts) => { + return transformAndValidateUnion({ + value: raw, + discriminant: rawDiscriminant, + transformedDiscriminant: parsedDiscriminant, + transformDiscriminantValue: (discriminantValue) => + discriminantValueSchema.parse(discriminantValue, { + allowUnrecognizedEnumValues: opts?.allowUnrecognizedUnionMembers, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), rawDiscriminant], + }), + getAdditionalPropertiesSchema: (discriminantValue) => union[discriminantValue], + allowUnrecognizedUnionMembers: opts?.allowUnrecognizedUnionMembers, + transformAdditionalProperties: (additionalProperties, additionalPropertiesSchema) => + additionalPropertiesSchema.parse(additionalProperties, opts), + breadcrumbsPrefix: opts?.breadcrumbsPrefix, + }); + }, + json: (parsed, opts) => { + return transformAndValidateUnion({ + value: parsed, + discriminant: parsedDiscriminant, + transformedDiscriminant: rawDiscriminant, + transformDiscriminantValue: (discriminantValue) => + discriminantValueSchema.json(discriminantValue, { + allowUnrecognizedEnumValues: opts?.allowUnrecognizedUnionMembers, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), parsedDiscriminant], + }), + getAdditionalPropertiesSchema: (discriminantValue) => union[discriminantValue], + allowUnrecognizedUnionMembers: opts?.allowUnrecognizedUnionMembers, + transformAdditionalProperties: (additionalProperties, additionalPropertiesSchema) => + additionalPropertiesSchema.json(additionalProperties, opts), + breadcrumbsPrefix: opts?.breadcrumbsPrefix, + }); + }, + getType: () => SchemaType.UNION, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + ...getObjectLikeUtils(baseSchema), + }; +} + +function transformAndValidateUnion< + TransformedDiscriminant extends string, + TransformedDiscriminantValue extends string, + TransformedAdditionalProperties +>({ + value, + discriminant, + transformedDiscriminant, + transformDiscriminantValue, + getAdditionalPropertiesSchema, + allowUnrecognizedUnionMembers = false, + transformAdditionalProperties, + breadcrumbsPrefix = [], +}: { + value: unknown; + discriminant: string; + transformedDiscriminant: TransformedDiscriminant; + transformDiscriminantValue: (discriminantValue: unknown) => MaybeValid; + getAdditionalPropertiesSchema: (discriminantValue: string) => ObjectSchema | undefined; + allowUnrecognizedUnionMembers: boolean | undefined; + transformAdditionalProperties: ( + additionalProperties: unknown, + additionalPropertiesSchema: ObjectSchema + ) => MaybeValid; + breadcrumbsPrefix: string[] | undefined; +}): MaybeValid & TransformedAdditionalProperties> { + if (!isPlainObject(value)) { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "object"), + }, + ], + }; + } + + const { [discriminant]: discriminantValue, ...additionalProperties } = value; + + if (discriminantValue == null) { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: `Missing discriminant ("${discriminant}")`, + }, + ], + }; + } + + const transformedDiscriminantValue = transformDiscriminantValue(discriminantValue); + if (!transformedDiscriminantValue.ok) { + return { + ok: false, + errors: transformedDiscriminantValue.errors, + }; + } + + const additionalPropertiesSchema = getAdditionalPropertiesSchema(transformedDiscriminantValue.value); + + if (additionalPropertiesSchema == null) { + if (allowUnrecognizedUnionMembers) { + return { + ok: true, + value: { + [transformedDiscriminant]: transformedDiscriminantValue.value, + ...additionalProperties, + } as Record & TransformedAdditionalProperties, + }; + } else { + return { + ok: false, + errors: [ + { + path: [...breadcrumbsPrefix, discriminant], + message: "Unexpected discriminant value", + }, + ], + }; + } + } + + const transformedAdditionalProperties = transformAdditionalProperties( + additionalProperties, + additionalPropertiesSchema + ); + if (!transformedAdditionalProperties.ok) { + return transformedAdditionalProperties; + } + + return { + ok: true, + value: { + [transformedDiscriminant]: discriminantValue, + ...transformedAdditionalProperties.value, + } as Record & TransformedAdditionalProperties, + }; +} diff --git a/seed/ts-express/grpc-proto/core/schemas/index.ts b/seed/ts-express/grpc-proto/core/schemas/index.ts new file mode 100644 index 00000000000..5429d8b43eb --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/index.ts @@ -0,0 +1,2 @@ +export * from "./builders"; +export type { inferParsed, inferRaw, Schema, SchemaOptions } from "./Schema"; diff --git a/seed/ts-express/grpc-proto/core/schemas/utils/MaybePromise.ts b/seed/ts-express/grpc-proto/core/schemas/utils/MaybePromise.ts new file mode 100644 index 00000000000..9cd354b3418 --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/utils/MaybePromise.ts @@ -0,0 +1 @@ +export type MaybePromise = T | Promise; diff --git a/seed/ts-express/grpc-proto/core/schemas/utils/addQuestionMarksToNullableProperties.ts b/seed/ts-express/grpc-proto/core/schemas/utils/addQuestionMarksToNullableProperties.ts new file mode 100644 index 00000000000..4111d703cd0 --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/utils/addQuestionMarksToNullableProperties.ts @@ -0,0 +1,15 @@ +export type addQuestionMarksToNullableProperties = { + [K in OptionalKeys]?: T[K]; +} & Pick>; + +export type OptionalKeys = { + [K in keyof T]-?: undefined extends T[K] + ? K + : null extends T[K] + ? K + : 1 extends (any extends T[K] ? 0 : 1) + ? never + : K; +}[keyof T]; + +export type RequiredKeys = Exclude>; diff --git a/seed/ts-express/grpc-proto/core/schemas/utils/createIdentitySchemaCreator.ts b/seed/ts-express/grpc-proto/core/schemas/utils/createIdentitySchemaCreator.ts new file mode 100644 index 00000000000..de107cf5ee1 --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/utils/createIdentitySchemaCreator.ts @@ -0,0 +1,21 @@ +import { getSchemaUtils } from "../builders/schema-utils"; +import { BaseSchema, MaybeValid, Schema, SchemaOptions, SchemaType } from "../Schema"; +import { maybeSkipValidation } from "./maybeSkipValidation"; + +export function createIdentitySchemaCreator( + schemaType: SchemaType, + validate: (value: unknown, opts?: SchemaOptions) => MaybeValid +): () => Schema { + return () => { + const baseSchema: BaseSchema = { + parse: validate, + json: validate, + getType: () => schemaType, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + }; + }; +} diff --git a/seed/ts-express/grpc-proto/core/schemas/utils/entries.ts b/seed/ts-express/grpc-proto/core/schemas/utils/entries.ts new file mode 100644 index 00000000000..e122952137d --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/utils/entries.ts @@ -0,0 +1,3 @@ +export function entries(object: T): [keyof T, T[keyof T]][] { + return Object.entries(object) as [keyof T, T[keyof T]][]; +} diff --git a/seed/ts-express/grpc-proto/core/schemas/utils/filterObject.ts b/seed/ts-express/grpc-proto/core/schemas/utils/filterObject.ts new file mode 100644 index 00000000000..2c25a3455bc --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/utils/filterObject.ts @@ -0,0 +1,10 @@ +export function filterObject(obj: T, keysToInclude: K[]): Pick { + const keysToIncludeSet = new Set(keysToInclude); + return Object.entries(obj).reduce((acc, [key, value]) => { + if (keysToIncludeSet.has(key as K)) { + acc[key as K] = value; + } + return acc; + // eslint-disable-next-line @typescript-eslint/prefer-reduce-type-parameter + }, {} as Pick); +} diff --git a/seed/ts-express/grpc-proto/core/schemas/utils/getErrorMessageForIncorrectType.ts b/seed/ts-express/grpc-proto/core/schemas/utils/getErrorMessageForIncorrectType.ts new file mode 100644 index 00000000000..438012df418 --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/utils/getErrorMessageForIncorrectType.ts @@ -0,0 +1,21 @@ +export function getErrorMessageForIncorrectType(value: unknown, expectedType: string): string { + return `Expected ${expectedType}. Received ${getTypeAsString(value)}.`; +} + +function getTypeAsString(value: unknown): string { + if (Array.isArray(value)) { + return "list"; + } + if (value === null) { + return "null"; + } + switch (typeof value) { + case "string": + return `"${value}"`; + case "number": + case "boolean": + case "undefined": + return `${value}`; + } + return typeof value; +} diff --git a/seed/ts-express/grpc-proto/core/schemas/utils/isPlainObject.ts b/seed/ts-express/grpc-proto/core/schemas/utils/isPlainObject.ts new file mode 100644 index 00000000000..db82a722c35 --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/utils/isPlainObject.ts @@ -0,0 +1,17 @@ +// borrowed from https://github.com/lodash/lodash/blob/master/isPlainObject.js +export function isPlainObject(value: unknown): value is Record { + if (typeof value !== "object" || value === null) { + return false; + } + + if (Object.getPrototypeOf(value) === null) { + return true; + } + + let proto = value; + while (Object.getPrototypeOf(proto) !== null) { + proto = Object.getPrototypeOf(proto); + } + + return Object.getPrototypeOf(value) === proto; +} diff --git a/seed/ts-express/grpc-proto/core/schemas/utils/keys.ts b/seed/ts-express/grpc-proto/core/schemas/utils/keys.ts new file mode 100644 index 00000000000..01867098287 --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/utils/keys.ts @@ -0,0 +1,3 @@ +export function keys(object: T): (keyof T)[] { + return Object.keys(object) as (keyof T)[]; +} diff --git a/seed/ts-express/grpc-proto/core/schemas/utils/maybeSkipValidation.ts b/seed/ts-express/grpc-proto/core/schemas/utils/maybeSkipValidation.ts new file mode 100644 index 00000000000..86c07abf2b4 --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/utils/maybeSkipValidation.ts @@ -0,0 +1,38 @@ +import { BaseSchema, MaybeValid, SchemaOptions } from "../Schema"; + +export function maybeSkipValidation, Raw, Parsed>(schema: S): S { + return { + ...schema, + json: transformAndMaybeSkipValidation(schema.json), + parse: transformAndMaybeSkipValidation(schema.parse), + }; +} + +function transformAndMaybeSkipValidation( + transform: (value: unknown, opts?: SchemaOptions) => MaybeValid +): (value: unknown, opts?: SchemaOptions) => MaybeValid { + return (value, opts): MaybeValid => { + const transformed = transform(value, opts); + const { skipValidation = false } = opts ?? {}; + if (!transformed.ok && skipValidation) { + // eslint-disable-next-line no-console + console.warn( + [ + "Failed to validate.", + ...transformed.errors.map( + (error) => + " - " + + (error.path.length > 0 ? `${error.path.join(".")}: ${error.message}` : error.message) + ), + ].join("\n") + ); + + return { + ok: true, + value: value as T, + }; + } else { + return transformed; + } + }; +} diff --git a/seed/ts-express/grpc-proto/core/schemas/utils/partition.ts b/seed/ts-express/grpc-proto/core/schemas/utils/partition.ts new file mode 100644 index 00000000000..f58d6f3d35f --- /dev/null +++ b/seed/ts-express/grpc-proto/core/schemas/utils/partition.ts @@ -0,0 +1,12 @@ +export function partition(items: readonly T[], predicate: (item: T) => boolean): [T[], T[]] { + const trueItems: T[] = [], + falseItems: T[] = []; + for (const item of items) { + if (predicate(item)) { + trueItems.push(item); + } else { + falseItems.push(item); + } + } + return [trueItems, falseItems]; +} diff --git a/seed/ts-express/grpc-proto/errors/SeedApiError.ts b/seed/ts-express/grpc-proto/errors/SeedApiError.ts new file mode 100644 index 00000000000..2e63e9e1784 --- /dev/null +++ b/seed/ts-express/grpc-proto/errors/SeedApiError.ts @@ -0,0 +1,14 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import express from "express"; + +export abstract class SeedApiError extends Error { + constructor(public readonly errorName?: string) { + super(); + Object.setPrototypeOf(this, SeedApiError.prototype); + } + + public abstract send(res: express.Response): Promise; +} diff --git a/seed/ts-express/grpc-proto/errors/index.ts b/seed/ts-express/grpc-proto/errors/index.ts new file mode 100644 index 00000000000..845ebc97622 --- /dev/null +++ b/seed/ts-express/grpc-proto/errors/index.ts @@ -0,0 +1 @@ +export { SeedApiError } from "./SeedApiError"; diff --git a/seed/ts-express/grpc-proto/index.ts b/seed/ts-express/grpc-proto/index.ts new file mode 100644 index 00000000000..e63e1d32ac9 --- /dev/null +++ b/seed/ts-express/grpc-proto/index.ts @@ -0,0 +1,3 @@ +export * as SeedApi from "./api"; +export { register } from "./register"; +export { SeedApiError } from "./errors"; diff --git a/seed/ts-express/grpc-proto/register.ts b/seed/ts-express/grpc-proto/register.ts new file mode 100644 index 00000000000..82b18004813 --- /dev/null +++ b/seed/ts-express/grpc-proto/register.ts @@ -0,0 +1,15 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import express from "express"; +import { UserService } from "./api/resources/user/service/UserService"; + +export function register( + expressApp: express.Express | express.Router, + services: { + user: UserService; + } +): void { + (expressApp as any).use("", services.user.toRouter()); +} diff --git a/seed/ts-express/grpc-proto/serialization/index.ts b/seed/ts-express/grpc-proto/serialization/index.ts new file mode 100644 index 00000000000..3ce0a3e38e8 --- /dev/null +++ b/seed/ts-express/grpc-proto/serialization/index.ts @@ -0,0 +1,2 @@ +export * from "./types"; +export * from "./resources"; diff --git a/seed/ts-express/grpc-proto/serialization/resources/index.ts b/seed/ts-express/grpc-proto/serialization/resources/index.ts new file mode 100644 index 00000000000..8b2f3a0b823 --- /dev/null +++ b/seed/ts-express/grpc-proto/serialization/resources/index.ts @@ -0,0 +1,2 @@ +export * as user from "./user"; +export * from "./user/service/requests"; diff --git a/seed/ts-express/grpc-proto/serialization/resources/user/index.ts b/seed/ts-express/grpc-proto/serialization/resources/user/index.ts new file mode 100644 index 00000000000..6261f896364 --- /dev/null +++ b/seed/ts-express/grpc-proto/serialization/resources/user/index.ts @@ -0,0 +1 @@ +export * from "./service"; diff --git a/seed/ts-express/grpc-proto/serialization/resources/user/service/index.ts b/seed/ts-express/grpc-proto/serialization/resources/user/service/index.ts new file mode 100644 index 00000000000..415726b7fea --- /dev/null +++ b/seed/ts-express/grpc-proto/serialization/resources/user/service/index.ts @@ -0,0 +1 @@ +export * from "./requests"; diff --git a/seed/ts-express/grpc-proto/serialization/resources/user/service/requests/CreateRequest.ts b/seed/ts-express/grpc-proto/serialization/resources/user/service/requests/CreateRequest.ts new file mode 100644 index 00000000000..45950a76bed --- /dev/null +++ b/seed/ts-express/grpc-proto/serialization/resources/user/service/requests/CreateRequest.ts @@ -0,0 +1,26 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../../index"; +import * as SeedApi from "../../../../../api/index"; +import * as core from "../../../../../core"; + +export const CreateRequest: core.serialization.Schema = + core.serialization.object({ + username: core.serialization.string().optional(), + email: core.serialization.string().optional(), + age: core.serialization.number().optional(), + weight: core.serialization.number().optional(), + metadata: core.serialization.record(core.serialization.string(), core.serialization.unknown()).optional(), + }); + +export declare namespace CreateRequest { + interface Raw { + username?: string | null; + email?: string | null; + age?: number | null; + weight?: number | null; + metadata?: Record | null; + } +} diff --git a/seed/ts-express/grpc-proto/serialization/resources/user/service/requests/index.ts b/seed/ts-express/grpc-proto/serialization/resources/user/service/requests/index.ts new file mode 100644 index 00000000000..4f8adcb4e83 --- /dev/null +++ b/seed/ts-express/grpc-proto/serialization/resources/user/service/requests/index.ts @@ -0,0 +1 @@ +export { CreateRequest } from "./CreateRequest"; diff --git a/seed/ts-express/grpc-proto/serialization/types/CreateResponse.ts b/seed/ts-express/grpc-proto/serialization/types/CreateResponse.ts new file mode 100644 index 00000000000..33d2b9d1be5 --- /dev/null +++ b/seed/ts-express/grpc-proto/serialization/types/CreateResponse.ts @@ -0,0 +1,18 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../index"; +import * as SeedApi from "../../api/index"; +import * as core from "../../core"; + +export const CreateResponse: core.serialization.ObjectSchema = + core.serialization.object({ + user: core.serialization.lazyObject(() => serializers.UserModel).optional(), + }); + +export declare namespace CreateResponse { + interface Raw { + user?: serializers.UserModel.Raw | null; + } +} diff --git a/seed/ts-express/grpc-proto/serialization/types/UserModel.ts b/seed/ts-express/grpc-proto/serialization/types/UserModel.ts new file mode 100644 index 00000000000..d34237fabad --- /dev/null +++ b/seed/ts-express/grpc-proto/serialization/types/UserModel.ts @@ -0,0 +1,26 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../index"; +import * as SeedApi from "../../api/index"; +import * as core from "../../core"; + +export const UserModel: core.serialization.ObjectSchema = + core.serialization.object({ + username: core.serialization.string().optional(), + email: core.serialization.string().optional(), + age: core.serialization.number().optional(), + weight: core.serialization.number().optional(), + metadata: core.serialization.record(core.serialization.string(), core.serialization.unknown()).optional(), + }); + +export declare namespace UserModel { + interface Raw { + username?: string | null; + email?: string | null; + age?: number | null; + weight?: number | null; + metadata?: Record | null; + } +} diff --git a/seed/ts-express/grpc-proto/serialization/types/index.ts b/seed/ts-express/grpc-proto/serialization/types/index.ts new file mode 100644 index 00000000000..8685532c76b --- /dev/null +++ b/seed/ts-express/grpc-proto/serialization/types/index.ts @@ -0,0 +1,2 @@ +export * from "./CreateResponse"; +export * from "./UserModel"; diff --git a/seed/ts-express/grpc-proto/snippet-templates.json b/seed/ts-express/grpc-proto/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/ts-express/grpc-proto/snippet.json b/seed/ts-express/grpc-proto/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/ts-express/grpc/.mock/definition/api.yml b/seed/ts-express/grpc/.mock/definition/api.yml new file mode 100644 index 00000000000..15a9f823724 --- /dev/null +++ b/seed/ts-express/grpc/.mock/definition/api.yml @@ -0,0 +1,4 @@ +name: api + +error-discrimination: + strategy: status-code \ No newline at end of file diff --git a/seed/ts-express/grpc/.mock/definition/user.yml b/seed/ts-express/grpc/.mock/definition/user.yml new file mode 100644 index 00000000000..80223300afc --- /dev/null +++ b/seed/ts-express/grpc/.mock/definition/user.yml @@ -0,0 +1,61 @@ +types: + Metadata: + type: map> + encoding: + proto: + type: google.protobuf.Struct + + MetadataValue: + discriminated: false + union: + - double + - string + - boolean + - list + encoding: + proto: + type: google.protobuf.Value + + User: + properties: + id: string + username: string + email: optional + age: optional + weight: optional + metadata: optional + + CreateUserResponse: + properties: + user: User + +service: + auth: false + base-path: / + transport: + grpc: + service-name: UserService + endpoints: + createUser: + method: POST + path: /users + request: + name: CreateUserRequest + body: + properties: + username: string + email: optional + age: optional + weight: optional + response: CreateUserResponse + + getUser: + method: GET + path: /users + request: + name: GetUserRequest + query-parameters: + username: optional + age: optional + weight: optional + response: User diff --git a/seed/ts-express/grpc/.mock/fern.config.json b/seed/ts-express/grpc/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/ts-express/grpc/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/ts-express/grpc/.mock/generators.yml b/seed/ts-express/grpc/.mock/generators.yml new file mode 100644 index 00000000000..f62dfba5843 --- /dev/null +++ b/seed/ts-express/grpc/.mock/generators.yml @@ -0,0 +1,4 @@ +api: + - proto: + root: proto + target: proto/user/v1/user.proto \ No newline at end of file diff --git a/seed/ts-express/grpc/.mock/proto/google/api/annotations.proto b/seed/ts-express/grpc/.mock/proto/google/api/annotations.proto new file mode 100644 index 00000000000..efdab3db6ca --- /dev/null +++ b/seed/ts-express/grpc/.mock/proto/google/api/annotations.proto @@ -0,0 +1,31 @@ +// Copyright 2015 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/api/http.proto"; +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "AnnotationsProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // See `HttpRule`. + HttpRule http = 72295728; +} diff --git a/seed/ts-express/grpc/.mock/proto/google/api/field_behavior.proto b/seed/ts-express/grpc/.mock/proto/google/api/field_behavior.proto new file mode 100644 index 00000000000..344cb0b1fc2 --- /dev/null +++ b/seed/ts-express/grpc/.mock/proto/google/api/field_behavior.proto @@ -0,0 +1,104 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "FieldBehaviorProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.FieldOptions { + // A designation of a specific field behavior (required, output only, etc.) + // in protobuf messages. + // + // Examples: + // + // string name = 1 [(google.api.field_behavior) = REQUIRED]; + // State state = 1 [(google.api.field_behavior) = OUTPUT_ONLY]; + // google.protobuf.Duration ttl = 1 + // [(google.api.field_behavior) = INPUT_ONLY]; + // google.protobuf.Timestamp expire_time = 1 + // [(google.api.field_behavior) = OUTPUT_ONLY, + // (google.api.field_behavior) = IMMUTABLE]; + repeated google.api.FieldBehavior field_behavior = 1052; +} + +// An indicator of the behavior of a given field (for example, that a field +// is required in requests, or given as output but ignored as input). +// This **does not** change the behavior in protocol buffers itself; it only +// denotes the behavior and may affect how API tooling handles the field. +// +// Note: This enum **may** receive new values in the future. +enum FieldBehavior { + // Conventional default for enums. Do not use this. + FIELD_BEHAVIOR_UNSPECIFIED = 0; + + // Specifically denotes a field as optional. + // While all fields in protocol buffers are optional, this may be specified + // for emphasis if appropriate. + OPTIONAL = 1; + + // Denotes a field as required. + // This indicates that the field **must** be provided as part of the request, + // and failure to do so will cause an error (usually `INVALID_ARGUMENT`). + REQUIRED = 2; + + // Denotes a field as output only. + // This indicates that the field is provided in responses, but including the + // field in a request does nothing (the server *must* ignore it and + // *must not* throw an error as a result of the field's presence). + OUTPUT_ONLY = 3; + + // Denotes a field as input only. + // This indicates that the field is provided in requests, and the + // corresponding field is not included in output. + INPUT_ONLY = 4; + + // Denotes a field as immutable. + // This indicates that the field may be set once in a request to create a + // resource, but may not be changed thereafter. + IMMUTABLE = 5; + + // Denotes that a (repeated) field is an unordered list. + // This indicates that the service may provide the elements of the list + // in any arbitrary order, rather than the order the user originally + // provided. Additionally, the list's order may or may not be stable. + UNORDERED_LIST = 6; + + // Denotes that this field returns a non-empty default value if not set. + // This indicates that if the user provides the empty value in a request, + // a non-empty value will be returned. The user will not be aware of what + // non-empty value to expect. + NON_EMPTY_DEFAULT = 7; + + // Denotes that the field in a resource (a message annotated with + // google.api.resource) is used in the resource name to uniquely identify the + // resource. For AIP-compliant APIs, this should only be applied to the + // `name` field on the resource. + // + // This behavior should not be applied to references to other resources within + // the message. + // + // The identifier field of resources often have different field behavior + // depending on the request it is embedded in (e.g. for Create methods name + // is optional and unused, while for Update methods it is required). Instead + // of method-specific annotations, only `IDENTIFIER` is required. + IDENTIFIER = 8; +} diff --git a/seed/ts-express/grpc/.mock/proto/google/api/http.proto b/seed/ts-express/grpc/.mock/proto/google/api/http.proto new file mode 100644 index 00000000000..31d867a27d5 --- /dev/null +++ b/seed/ts-express/grpc/.mock/proto/google/api/http.proto @@ -0,0 +1,379 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "HttpProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Defines the HTTP configuration for an API service. It contains a list of +// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method +// to one or more HTTP REST API methods. +message Http { + // A list of HTTP configuration rules that apply to individual API methods. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated HttpRule rules = 1; + + // When set to true, URL path parameters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // + // The default behavior is to not decode RFC 6570 reserved characters in multi + // segment matches. + bool fully_decode_reserved_expansion = 2; +} + +// # gRPC Transcoding +// +// gRPC Transcoding is a feature for mapping between a gRPC method and one or +// more HTTP REST endpoints. It allows developers to build a single API service +// that supports both gRPC APIs and REST APIs. Many systems, including [Google +// APIs](https://github.com/googleapis/googleapis), +// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC +// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), +// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature +// and use it for large scale production services. +// +// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies +// how different portions of the gRPC request message are mapped to the URL +// path, URL query parameters, and HTTP request body. It also controls how the +// gRPC response message is mapped to the HTTP response body. `HttpRule` is +// typically specified as an `google.api.http` annotation on the gRPC method. +// +// Each mapping specifies a URL path template and an HTTP method. The path +// template may refer to one or more fields in the gRPC request message, as long +// as each field is a non-repeated field with a primitive (non-message) type. +// The path template controls how fields of the request message are mapped to +// the URL path. +// +// Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/{name=messages/*}" +// }; +// } +// } +// message GetMessageRequest { +// string name = 1; // Mapped to URL path. +// } +// message Message { +// string text = 1; // The resource content. +// } +// +// This enables an HTTP REST to gRPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` +// +// Any fields in the request message which are not bound by the path template +// automatically become HTTP query parameters if there is no HTTP request body. +// For example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get:"/v1/messages/{message_id}" +// }; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // Mapped to URL path. +// int64 revision = 2; // Mapped to URL query parameter `revision`. +// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. +// } +// +// This enables a HTTP JSON to RPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | +// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: +// "foo"))` +// +// Note that fields which are mapped to URL query parameters must have a +// primitive type or a repeated primitive type or a non-repeated message type. +// In the case of a repeated type, the parameter can be repeated in the URL +// as `...?param=A¶m=B`. In the case of a message type, each field of the +// message is mapped to a separate parameter, such as +// `...?foo.a=A&foo.b=B&foo.c=C`. +// +// For HTTP methods that allow a request body, the `body` field +// specifies the mapping. Consider a REST update method on the +// message resource collection: +// +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } +// +// The following HTTP JSON to RPC mapping is enabled, where the +// representation of the JSON in the request body is determined by +// protos JSON encoding: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" message { text: "Hi!" })` +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. This enables the following alternative definition of +// the update method: +// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } +// +// +// The following HTTP JSON to RPC mapping is enabled: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" text: "Hi!")` +// +// Note that when using `*` in the body mapping, it is not possible to +// have HTTP parameters, as all fields not bound by the path end in +// the body. This makes this option more rarely used in practice when +// defining REST APIs. The common usage of `*` is in custom methods +// which don't use the URL at all for transferring data. +// +// It is possible to define multiple HTTP methods for one RPC by using +// the `additional_bindings` option. Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } +// +// This enables the following two alternative HTTP JSON to RPC mappings: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` +// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: +// "123456")` +// +// ## Rules for HTTP mapping +// +// 1. Leaf request fields (recursive expansion nested messages in the request +// message) are classified into three categories: +// - Fields referred by the path template. They are passed via the URL path. +// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They +// are passed via the HTTP +// request body. +// - All other fields are passed via the URL query parameters, and the +// parameter name is the field path in the request message. A repeated +// field can be represented as multiple query parameters under the same +// name. +// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL +// query parameter, all fields +// are passed via URL path and HTTP request body. +// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP +// request body, all +// fields are passed via URL path and URL query parameters. +// +// ### Path template syntax +// +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +// +// The syntax `*` matches a single URL path segment. The syntax `**` matches +// zero or more URL path segments, which must be the last part of the URL path +// except the `Verb`. +// +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable +// matches a single path segment, its template may be omitted, e.g. `{var}` +// is equivalent to `{var=*}`. +// +// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` +// contains any reserved character, such characters should be percent-encoded +// before the matching. +// +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path on the client +// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The +// server side does the reverse decoding. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{var}`. +// +// If a variable contains multiple path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path on the +// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. +// The server side does the reverse decoding, except "%2F" and "%2f" are left +// unchanged. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{+var}`. +// +// ## Using gRPC API Service Configuration +// +// gRPC API Service Configuration (service config) is a configuration language +// for configuring a gRPC service to become a user-facing product. The +// service config is simply the YAML representation of the `google.api.Service` +// proto message. +// +// As an alternative to annotating your proto file, you can configure gRPC +// transcoding in your service config YAML files. You do this by specifying a +// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same +// effect as the proto annotation. This can be particularly useful if you +// have a proto that is reused in multiple services. Note that any transcoding +// specified in the service config will override any matching transcoding +// configuration in the proto. +// +// Example: +// +// http: +// rules: +// # Selects a gRPC method and applies HttpRule to it. +// - selector: example.v1.Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} +// +// ## Special notes +// +// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the +// proto to JSON conversion must follow the [proto3 +// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). +// +// While the single segment variable follows the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String +// Expansion, the multi segment variable **does not** follow RFC 6570 Section +// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding +// for multi segment variables. +// +// The path variables **must not** refer to any repeated or mapped field, +// because client libraries are not capable of handling such variable expansion. +// +// The path variables **must not** capture the leading "/" character. The reason +// is that the most common use case "{var}" does not capture the leading "/" +// character. For consistency, all path variables must share the same behavior. +// +// Repeated message fields must not be mapped to URL query parameters, because +// no client library can support such complicated mapping. +// +// If an API needs to use a JSON array for request or response body, it can map +// the request or response body to a repeated field. However, some gRPC +// Transcoding implementations may not support this feature. +message HttpRule { + // Selects a method to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax + // details. + string selector = 1; + + // Determines the URL pattern is matched by this rules. This pattern can be + // used with any of the {get|put|post|delete|patch} methods. A custom method + // can be defined using the 'custom' field. + oneof pattern { + // Maps to HTTP GET. Used for listing and getting information about + // resources. + string get = 2; + + // Maps to HTTP PUT. Used for replacing a resource. + string put = 3; + + // Maps to HTTP POST. Used for creating a resource or performing an action. + string post = 4; + + // Maps to HTTP DELETE. Used for deleting a resource. + string delete = 5; + + // Maps to HTTP PATCH. Used for updating a resource. + string patch = 6; + + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. + CustomHttpPattern custom = 8; + } + + // The name of the request field whose value is mapped to the HTTP request + // body, or `*` for mapping all request fields not captured by the path + // pattern to the HTTP body, or omitted for not having any HTTP request body. + // + // NOTE: the referred field must be present at the top-level of the request + // message type. + string body = 7; + + // Optional. The name of the response field whose value is mapped to the HTTP + // response body. When omitted, the entire response message will be used + // as the HTTP response body. + // + // NOTE: The referred field must be present at the top-level of the response + // message type. + string response_body = 12; + + // Additional HTTP bindings for the selector. Nested bindings must + // not contain an `additional_bindings` field themselves (that is, + // the nesting may only be one level deep). + repeated HttpRule additional_bindings = 11; +} + +// A custom pattern is used for defining custom HTTP verb. +message CustomHttpPattern { + // The name of this custom HTTP verb. + string kind = 1; + + // The path matched by this custom verb. + string path = 2; +} diff --git a/seed/ts-express/grpc/.mock/proto/user/v1/user.proto b/seed/ts-express/grpc/.mock/proto/user/v1/user.proto new file mode 100644 index 00000000000..adab9ceefb9 --- /dev/null +++ b/seed/ts-express/grpc/.mock/proto/user/v1/user.proto @@ -0,0 +1,55 @@ +syntax = "proto3"; + +package user.v1; + +import "google/api/annotations.proto"; +import "google/api/field_behavior.proto"; +import "google/protobuf/struct.proto"; + +option csharp_namespace = "User.V1"; + +message User { + string username = 1 [ + (google.api.field_behavior) = REQUIRED + ]; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateUserRequest { + string username = 1 [ + (google.api.field_behavior) = REQUIRED + ]; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateUserResponse { + User user = 1; +} + +message GetUserRequest { + string username = 1; + string email = 2; + uint32 age = 3; + float weight = 4; +} + +service UserService { + rpc CreateUser(CreateUserRequest) returns (CreateUserResponse) { + option (google.api.http) = { + post: "/users" + body: "*" + }; + } + + rpc GetUser(GetUserRequest) returns (User) { + option (google.api.http) = { + get: "/users" + }; + } +} diff --git a/seed/ts-express/grpc/api/index.ts b/seed/ts-express/grpc/api/index.ts new file mode 100644 index 00000000000..3e5335fe421 --- /dev/null +++ b/seed/ts-express/grpc/api/index.ts @@ -0,0 +1 @@ +export * from "./resources"; diff --git a/seed/ts-express/grpc/api/resources/index.ts b/seed/ts-express/grpc/api/resources/index.ts new file mode 100644 index 00000000000..ebc1d4fff94 --- /dev/null +++ b/seed/ts-express/grpc/api/resources/index.ts @@ -0,0 +1,3 @@ +export * as user from "./user"; +export * from "./user/types"; +export * from "./user/service/requests"; diff --git a/seed/ts-express/grpc/api/resources/user/index.ts b/seed/ts-express/grpc/api/resources/user/index.ts new file mode 100644 index 00000000000..fcc81debec4 --- /dev/null +++ b/seed/ts-express/grpc/api/resources/user/index.ts @@ -0,0 +1,2 @@ +export * from "./types"; +export * from "./service"; diff --git a/seed/ts-express/grpc/api/resources/user/service/UserService.ts b/seed/ts-express/grpc/api/resources/user/service/UserService.ts new file mode 100644 index 00000000000..89bf620e0e5 --- /dev/null +++ b/seed/ts-express/grpc/api/resources/user/service/UserService.ts @@ -0,0 +1,131 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as SeedApi from "../../../index"; +import express from "express"; +import * as serializers from "../../../../serialization/index"; +import * as errors from "../../../../errors/index"; + +export interface UserServiceMethods { + createUser( + req: express.Request, + res: { + send: (responseBody: SeedApi.CreateUserResponse) => Promise; + cookie: (cookie: string, value: string, options?: express.CookieOptions) => void; + locals: any; + }, + next: express.NextFunction + ): void | Promise; + getUser( + req: express.Request< + never, + SeedApi.User, + never, + { + username?: string; + age?: number; + weight?: number; + } + >, + res: { + send: (responseBody: SeedApi.User) => Promise; + cookie: (cookie: string, value: string, options?: express.CookieOptions) => void; + locals: any; + }, + next: express.NextFunction + ): void | Promise; +} + +export class UserService { + private router; + + constructor(private readonly methods: UserServiceMethods, middleware: express.RequestHandler[] = []) { + this.router = express.Router({ mergeParams: true }).use( + express.json({ + strict: false, + }), + ...middleware + ); + } + + public addMiddleware(handler: express.RequestHandler): this { + this.router.use(handler); + return this; + } + + public toRouter(): express.Router { + this.router.post("/users", async (req, res, next) => { + const request = serializers.CreateUserRequest.parse(req.body); + if (request.ok) { + req.body = request.value; + try { + await this.methods.createUser( + req as any, + { + send: async (responseBody) => { + res.json( + serializers.CreateUserResponse.jsonOrThrow(responseBody, { + unrecognizedObjectKeys: "strip", + }) + ); + }, + cookie: res.cookie.bind(res), + locals: res.locals, + }, + next + ); + next(); + } catch (error) { + if (error instanceof errors.SeedApiError) { + console.warn( + `Endpoint 'createUser' unexpectedly threw ${error.constructor.name}.` + + ` If this was intentional, please add ${error.constructor.name} to` + + " the endpoint's errors list in your Fern Definition." + ); + await error.send(res); + } else { + res.status(500).json("Internal Server Error"); + } + next(error); + } + } else { + res.status(422).json({ + errors: request.errors.map( + (error) => ["request", ...error.path].join(" -> ") + ": " + error.message + ), + }); + next(request.errors); + } + }); + this.router.get("/users", async (req, res, next) => { + try { + await this.methods.getUser( + req as any, + { + send: async (responseBody) => { + res.json(serializers.User.jsonOrThrow(responseBody, { unrecognizedObjectKeys: "strip" })); + }, + cookie: res.cookie.bind(res), + locals: res.locals, + }, + next + ); + next(); + } catch (error) { + if (error instanceof errors.SeedApiError) { + console.warn( + `Endpoint 'getUser' unexpectedly threw ${error.constructor.name}.` + + ` If this was intentional, please add ${error.constructor.name} to` + + " the endpoint's errors list in your Fern Definition." + ); + await error.send(res); + } else { + res.status(500).json("Internal Server Error"); + } + next(error); + } + }); + return this.router; + } +} diff --git a/seed/ts-express/grpc/api/resources/user/service/index.ts b/seed/ts-express/grpc/api/resources/user/service/index.ts new file mode 100644 index 00000000000..415726b7fea --- /dev/null +++ b/seed/ts-express/grpc/api/resources/user/service/index.ts @@ -0,0 +1 @@ +export * from "./requests"; diff --git a/seed/ts-express/grpc/api/resources/user/service/requests/CreateUserRequest.ts b/seed/ts-express/grpc/api/resources/user/service/requests/CreateUserRequest.ts new file mode 100644 index 00000000000..abf4a6f2fa4 --- /dev/null +++ b/seed/ts-express/grpc/api/resources/user/service/requests/CreateUserRequest.ts @@ -0,0 +1,10 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +export interface CreateUserRequest { + username: string; + email?: string; + age?: number; + weight?: number; +} diff --git a/seed/ts-express/grpc/api/resources/user/service/requests/index.ts b/seed/ts-express/grpc/api/resources/user/service/requests/index.ts new file mode 100644 index 00000000000..5632de83260 --- /dev/null +++ b/seed/ts-express/grpc/api/resources/user/service/requests/index.ts @@ -0,0 +1 @@ +export { CreateUserRequest } from "./CreateUserRequest"; diff --git a/seed/ts-express/grpc/api/resources/user/types/CreateUserResponse.ts b/seed/ts-express/grpc/api/resources/user/types/CreateUserResponse.ts new file mode 100644 index 00000000000..3e470a184f5 --- /dev/null +++ b/seed/ts-express/grpc/api/resources/user/types/CreateUserResponse.ts @@ -0,0 +1,9 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as SeedApi from "../../../index"; + +export interface CreateUserResponse { + user: SeedApi.User; +} diff --git a/seed/ts-express/grpc/api/resources/user/types/Metadata.ts b/seed/ts-express/grpc/api/resources/user/types/Metadata.ts new file mode 100644 index 00000000000..1b5e687c6f5 --- /dev/null +++ b/seed/ts-express/grpc/api/resources/user/types/Metadata.ts @@ -0,0 +1,7 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as SeedApi from "../../../index"; + +export type Metadata = Record; diff --git a/seed/ts-express/grpc/api/resources/user/types/MetadataValue.ts b/seed/ts-express/grpc/api/resources/user/types/MetadataValue.ts new file mode 100644 index 00000000000..bd7a484bfdc --- /dev/null +++ b/seed/ts-express/grpc/api/resources/user/types/MetadataValue.ts @@ -0,0 +1,7 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as SeedApi from "../../../index"; + +export type MetadataValue = number | string | boolean | SeedApi.MetadataValue[]; diff --git a/seed/ts-express/grpc/api/resources/user/types/User.ts b/seed/ts-express/grpc/api/resources/user/types/User.ts new file mode 100644 index 00000000000..0b9f9850fe2 --- /dev/null +++ b/seed/ts-express/grpc/api/resources/user/types/User.ts @@ -0,0 +1,14 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as SeedApi from "../../../index"; + +export interface User { + id: string; + username: string; + email?: string; + age?: number; + weight?: number; + metadata?: SeedApi.Metadata; +} diff --git a/seed/ts-express/grpc/api/resources/user/types/index.ts b/seed/ts-express/grpc/api/resources/user/types/index.ts new file mode 100644 index 00000000000..aff31139192 --- /dev/null +++ b/seed/ts-express/grpc/api/resources/user/types/index.ts @@ -0,0 +1,4 @@ +export * from "./Metadata"; +export * from "./MetadataValue"; +export * from "./User"; +export * from "./CreateUserResponse"; diff --git a/seed/ts-express/grpc/core/index.ts b/seed/ts-express/grpc/core/index.ts new file mode 100644 index 00000000000..3ae53c06d38 --- /dev/null +++ b/seed/ts-express/grpc/core/index.ts @@ -0,0 +1 @@ +export * as serialization from "./schemas"; diff --git a/seed/ts-express/grpc/core/schemas/Schema.ts b/seed/ts-express/grpc/core/schemas/Schema.ts new file mode 100644 index 00000000000..19acc5dc44b --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/Schema.ts @@ -0,0 +1,98 @@ +import { SchemaUtils } from "./builders"; + +export type Schema = BaseSchema & SchemaUtils; + +export type inferRaw = S extends Schema ? Raw : never; +export type inferParsed = S extends Schema ? Parsed : never; + +export interface BaseSchema { + parse: (raw: unknown, opts?: SchemaOptions) => MaybeValid; + json: (parsed: unknown, opts?: SchemaOptions) => MaybeValid; + getType: () => SchemaType | SchemaType; +} + +export const SchemaType = { + DATE: "date", + ENUM: "enum", + LIST: "list", + STRING_LITERAL: "stringLiteral", + BOOLEAN_LITERAL: "booleanLiteral", + OBJECT: "object", + ANY: "any", + BOOLEAN: "boolean", + NUMBER: "number", + STRING: "string", + UNKNOWN: "unknown", + RECORD: "record", + SET: "set", + UNION: "union", + UNDISCRIMINATED_UNION: "undiscriminatedUnion", + OPTIONAL: "optional", +} as const; +export type SchemaType = typeof SchemaType[keyof typeof SchemaType]; + +export type MaybeValid = Valid | Invalid; + +export interface Valid { + ok: true; + value: T; +} + +export interface Invalid { + ok: false; + errors: ValidationError[]; +} + +export interface ValidationError { + path: string[]; + message: string; +} + +export interface SchemaOptions { + /** + * how to handle unrecognized keys in objects + * + * @default "fail" + */ + unrecognizedObjectKeys?: "fail" | "passthrough" | "strip"; + + /** + * whether to fail when an unrecognized discriminant value is + * encountered in a union + * + * @default false + */ + allowUnrecognizedUnionMembers?: boolean; + + /** + * whether to fail when an unrecognized enum value is encountered + * + * @default false + */ + allowUnrecognizedEnumValues?: boolean; + + /** + * whether to allow data that doesn't conform to the schema. + * invalid data is passed through without transformation. + * + * when this is enabled, .parse() and .json() will always + * return `ok: true`. `.parseOrThrow()` and `.jsonOrThrow()` + * will never fail. + * + * @default false + */ + skipValidation?: boolean; + + /** + * each validation failure contains a "path" property, which is + * the breadcrumbs to the offending node in the JSON. you can supply + * a prefix that is prepended to all the errors' paths. this can be + * helpful for zurg's internal debug logging. + */ + breadcrumbsPrefix?: string[]; + + /** + * whether to send 'null' for optional properties explicitly set to 'undefined'. + */ + omitUndefined?: boolean; +} diff --git a/seed/ts-express/grpc/core/schemas/builders/date/date.ts b/seed/ts-express/grpc/core/schemas/builders/date/date.ts new file mode 100644 index 00000000000..b70f24b045a --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/builders/date/date.ts @@ -0,0 +1,65 @@ +import { BaseSchema, Schema, SchemaType } from "../../Schema"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { getSchemaUtils } from "../schema-utils"; + +// https://stackoverflow.com/questions/12756159/regex-and-iso8601-formatted-datetime +const ISO_8601_REGEX = + /^([+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24:?00)([.,]\d+(?!:))?)?(\17[0-5]\d([.,]\d+)?)?([zZ]|([+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/; + +export function date(): Schema { + const baseSchema: BaseSchema = { + parse: (raw, { breadcrumbsPrefix = [] } = {}) => { + if (typeof raw !== "string") { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(raw, "string"), + }, + ], + }; + } + if (!ISO_8601_REGEX.test(raw)) { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(raw, "ISO 8601 date string"), + }, + ], + }; + } + return { + ok: true, + value: new Date(raw), + }; + }, + json: (date, { breadcrumbsPrefix = [] } = {}) => { + if (date instanceof Date) { + return { + ok: true, + value: date.toISOString(), + }; + } else { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(date, "Date object"), + }, + ], + }; + } + }, + getType: () => SchemaType.DATE, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + }; +} diff --git a/seed/ts-express/grpc/core/schemas/builders/date/index.ts b/seed/ts-express/grpc/core/schemas/builders/date/index.ts new file mode 100644 index 00000000000..187b29040f6 --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/builders/date/index.ts @@ -0,0 +1 @@ +export { date } from "./date"; diff --git a/seed/ts-express/grpc/core/schemas/builders/enum/enum.ts b/seed/ts-express/grpc/core/schemas/builders/enum/enum.ts new file mode 100644 index 00000000000..c1e24d69dec --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/builders/enum/enum.ts @@ -0,0 +1,43 @@ +import { Schema, SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; + +export function enum_(values: E): Schema { + const validValues = new Set(values); + + const schemaCreator = createIdentitySchemaCreator( + SchemaType.ENUM, + (value, { allowUnrecognizedEnumValues, breadcrumbsPrefix = [] } = {}) => { + if (typeof value !== "string") { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "string"), + }, + ], + }; + } + + if (!validValues.has(value) && !allowUnrecognizedEnumValues) { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "enum"), + }, + ], + }; + } + + return { + ok: true, + value: value as U, + }; + } + ); + + return schemaCreator(); +} diff --git a/seed/ts-express/grpc/core/schemas/builders/enum/index.ts b/seed/ts-express/grpc/core/schemas/builders/enum/index.ts new file mode 100644 index 00000000000..fe6faed93e3 --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/builders/enum/index.ts @@ -0,0 +1 @@ +export { enum_ } from "./enum"; diff --git a/seed/ts-express/grpc/core/schemas/builders/index.ts b/seed/ts-express/grpc/core/schemas/builders/index.ts new file mode 100644 index 00000000000..050cd2c4efb --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/builders/index.ts @@ -0,0 +1,13 @@ +export * from "./date"; +export * from "./enum"; +export * from "./lazy"; +export * from "./list"; +export * from "./literals"; +export * from "./object"; +export * from "./object-like"; +export * from "./primitives"; +export * from "./record"; +export * from "./schema-utils"; +export * from "./set"; +export * from "./undiscriminated-union"; +export * from "./union"; diff --git a/seed/ts-express/grpc/core/schemas/builders/lazy/index.ts b/seed/ts-express/grpc/core/schemas/builders/lazy/index.ts new file mode 100644 index 00000000000..77420fb031c --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/builders/lazy/index.ts @@ -0,0 +1,3 @@ +export { lazy } from "./lazy"; +export type { SchemaGetter } from "./lazy"; +export { lazyObject } from "./lazyObject"; diff --git a/seed/ts-express/grpc/core/schemas/builders/lazy/lazy.ts b/seed/ts-express/grpc/core/schemas/builders/lazy/lazy.ts new file mode 100644 index 00000000000..835c61f8a56 --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/builders/lazy/lazy.ts @@ -0,0 +1,32 @@ +import { BaseSchema, Schema } from "../../Schema"; +import { getSchemaUtils } from "../schema-utils"; + +export type SchemaGetter> = () => SchemaType; + +export function lazy(getter: SchemaGetter>): Schema { + const baseSchema = constructLazyBaseSchema(getter); + return { + ...baseSchema, + ...getSchemaUtils(baseSchema), + }; +} + +export function constructLazyBaseSchema( + getter: SchemaGetter> +): BaseSchema { + return { + parse: (raw, opts) => getMemoizedSchema(getter).parse(raw, opts), + json: (parsed, opts) => getMemoizedSchema(getter).json(parsed, opts), + getType: () => getMemoizedSchema(getter).getType(), + }; +} + +type MemoizedGetter> = SchemaGetter & { __zurg_memoized?: SchemaType }; + +export function getMemoizedSchema>(getter: SchemaGetter): SchemaType { + const castedGetter = getter as MemoizedGetter; + if (castedGetter.__zurg_memoized == null) { + castedGetter.__zurg_memoized = getter(); + } + return castedGetter.__zurg_memoized; +} diff --git a/seed/ts-express/grpc/core/schemas/builders/lazy/lazyObject.ts b/seed/ts-express/grpc/core/schemas/builders/lazy/lazyObject.ts new file mode 100644 index 00000000000..38c9e28404b --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/builders/lazy/lazyObject.ts @@ -0,0 +1,20 @@ +import { getObjectUtils } from "../object"; +import { getObjectLikeUtils } from "../object-like"; +import { BaseObjectSchema, ObjectSchema } from "../object/types"; +import { getSchemaUtils } from "../schema-utils"; +import { constructLazyBaseSchema, getMemoizedSchema, SchemaGetter } from "./lazy"; + +export function lazyObject(getter: SchemaGetter>): ObjectSchema { + const baseSchema: BaseObjectSchema = { + ...constructLazyBaseSchema(getter), + _getRawProperties: () => getMemoizedSchema(getter)._getRawProperties(), + _getParsedProperties: () => getMemoizedSchema(getter)._getParsedProperties(), + }; + + return { + ...baseSchema, + ...getSchemaUtils(baseSchema), + ...getObjectLikeUtils(baseSchema), + ...getObjectUtils(baseSchema), + }; +} diff --git a/seed/ts-express/grpc/core/schemas/builders/list/index.ts b/seed/ts-express/grpc/core/schemas/builders/list/index.ts new file mode 100644 index 00000000000..25f4bcc1737 --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/builders/list/index.ts @@ -0,0 +1 @@ +export { list } from "./list"; diff --git a/seed/ts-express/grpc/core/schemas/builders/list/list.ts b/seed/ts-express/grpc/core/schemas/builders/list/list.ts new file mode 100644 index 00000000000..e4c5c4a4a99 --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/builders/list/list.ts @@ -0,0 +1,73 @@ +import { BaseSchema, MaybeValid, Schema, SchemaType, ValidationError } from "../../Schema"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { getSchemaUtils } from "../schema-utils"; + +export function list(schema: Schema): Schema { + const baseSchema: BaseSchema = { + parse: (raw, opts) => + validateAndTransformArray(raw, (item, index) => + schema.parse(item, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `[${index}]`], + }) + ), + json: (parsed, opts) => + validateAndTransformArray(parsed, (item, index) => + schema.json(item, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `[${index}]`], + }) + ), + getType: () => SchemaType.LIST, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + }; +} + +function validateAndTransformArray( + value: unknown, + transformItem: (item: Raw, index: number) => MaybeValid +): MaybeValid { + if (!Array.isArray(value)) { + return { + ok: false, + errors: [ + { + message: getErrorMessageForIncorrectType(value, "list"), + path: [], + }, + ], + }; + } + + const maybeValidItems = value.map((item, index) => transformItem(item, index)); + + return maybeValidItems.reduce>( + (acc, item) => { + if (acc.ok && item.ok) { + return { + ok: true, + value: [...acc.value, item.value], + }; + } + + const errors: ValidationError[] = []; + if (!acc.ok) { + errors.push(...acc.errors); + } + if (!item.ok) { + errors.push(...item.errors); + } + + return { + ok: false, + errors, + }; + }, + { ok: true, value: [] } + ); +} diff --git a/seed/ts-express/grpc/core/schemas/builders/literals/booleanLiteral.ts b/seed/ts-express/grpc/core/schemas/builders/literals/booleanLiteral.ts new file mode 100644 index 00000000000..a83d22cd48a --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/builders/literals/booleanLiteral.ts @@ -0,0 +1,29 @@ +import { Schema, SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; + +export function booleanLiteral(literal: V): Schema { + const schemaCreator = createIdentitySchemaCreator( + SchemaType.BOOLEAN_LITERAL, + (value, { breadcrumbsPrefix = [] } = {}) => { + if (value === literal) { + return { + ok: true, + value: literal, + }; + } else { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, `${literal.toString()}`), + }, + ], + }; + } + } + ); + + return schemaCreator(); +} diff --git a/seed/ts-express/grpc/core/schemas/builders/literals/index.ts b/seed/ts-express/grpc/core/schemas/builders/literals/index.ts new file mode 100644 index 00000000000..d2bf08fc6ca --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/builders/literals/index.ts @@ -0,0 +1,2 @@ +export { stringLiteral } from "./stringLiteral"; +export { booleanLiteral } from "./booleanLiteral"; diff --git a/seed/ts-express/grpc/core/schemas/builders/literals/stringLiteral.ts b/seed/ts-express/grpc/core/schemas/builders/literals/stringLiteral.ts new file mode 100644 index 00000000000..3939b76b48d --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/builders/literals/stringLiteral.ts @@ -0,0 +1,29 @@ +import { Schema, SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; + +export function stringLiteral(literal: V): Schema { + const schemaCreator = createIdentitySchemaCreator( + SchemaType.STRING_LITERAL, + (value, { breadcrumbsPrefix = [] } = {}) => { + if (value === literal) { + return { + ok: true, + value: literal, + }; + } else { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, `"${literal}"`), + }, + ], + }; + } + } + ); + + return schemaCreator(); +} diff --git a/seed/ts-express/grpc/core/schemas/builders/object-like/getObjectLikeUtils.ts b/seed/ts-express/grpc/core/schemas/builders/object-like/getObjectLikeUtils.ts new file mode 100644 index 00000000000..8331d08da89 --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/builders/object-like/getObjectLikeUtils.ts @@ -0,0 +1,79 @@ +import { BaseSchema } from "../../Schema"; +import { filterObject } from "../../utils/filterObject"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { isPlainObject } from "../../utils/isPlainObject"; +import { getSchemaUtils } from "../schema-utils"; +import { ObjectLikeSchema, ObjectLikeUtils } from "./types"; + +export function getObjectLikeUtils(schema: BaseSchema): ObjectLikeUtils { + return { + withParsedProperties: (properties) => withParsedProperties(schema, properties), + }; +} + +/** + * object-like utils are defined in one file to resolve issues with circular imports + */ + +export function withParsedProperties( + objectLike: BaseSchema, + properties: { [K in keyof Properties]: Properties[K] | ((parsed: ParsedObjectShape) => Properties[K]) } +): ObjectLikeSchema { + const objectSchema: BaseSchema = { + parse: (raw, opts) => { + const parsedObject = objectLike.parse(raw, opts); + if (!parsedObject.ok) { + return parsedObject; + } + + const additionalProperties = Object.entries(properties).reduce>( + (processed, [key, value]) => { + return { + ...processed, + [key]: typeof value === "function" ? value(parsedObject.value) : value, + }; + }, + {} + ); + + return { + ok: true, + value: { + ...parsedObject.value, + ...(additionalProperties as Properties), + }, + }; + }, + + json: (parsed, opts) => { + if (!isPlainObject(parsed)) { + return { + ok: false, + errors: [ + { + path: opts?.breadcrumbsPrefix ?? [], + message: getErrorMessageForIncorrectType(parsed, "object"), + }, + ], + }; + } + + // strip out added properties + const addedPropertyKeys = new Set(Object.keys(properties)); + const parsedWithoutAddedProperties = filterObject( + parsed, + Object.keys(parsed).filter((key) => !addedPropertyKeys.has(key)) + ); + + return objectLike.json(parsedWithoutAddedProperties as ParsedObjectShape, opts); + }, + + getType: () => objectLike.getType(), + }; + + return { + ...objectSchema, + ...getSchemaUtils(objectSchema), + ...getObjectLikeUtils(objectSchema), + }; +} diff --git a/seed/ts-express/grpc/core/schemas/builders/object-like/index.ts b/seed/ts-express/grpc/core/schemas/builders/object-like/index.ts new file mode 100644 index 00000000000..c342e72cf9d --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/builders/object-like/index.ts @@ -0,0 +1,2 @@ +export { getObjectLikeUtils, withParsedProperties } from "./getObjectLikeUtils"; +export type { ObjectLikeSchema, ObjectLikeUtils } from "./types"; diff --git a/seed/ts-express/grpc/core/schemas/builders/object-like/types.ts b/seed/ts-express/grpc/core/schemas/builders/object-like/types.ts new file mode 100644 index 00000000000..75b3698729c --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/builders/object-like/types.ts @@ -0,0 +1,11 @@ +import { BaseSchema, Schema } from "../../Schema"; + +export type ObjectLikeSchema = Schema & + BaseSchema & + ObjectLikeUtils; + +export interface ObjectLikeUtils { + withParsedProperties: >(properties: { + [K in keyof T]: T[K] | ((parsed: Parsed) => T[K]); + }) => ObjectLikeSchema; +} diff --git a/seed/ts-express/grpc/core/schemas/builders/object/index.ts b/seed/ts-express/grpc/core/schemas/builders/object/index.ts new file mode 100644 index 00000000000..e3f4388db28 --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/builders/object/index.ts @@ -0,0 +1,22 @@ +export { getObjectUtils, object } from "./object"; +export { objectWithoutOptionalProperties } from "./objectWithoutOptionalProperties"; +export type { + inferObjectWithoutOptionalPropertiesSchemaFromPropertySchemas, + inferParsedObjectWithoutOptionalPropertiesFromPropertySchemas, +} from "./objectWithoutOptionalProperties"; +export { isProperty, property } from "./property"; +export type { Property } from "./property"; +export type { + BaseObjectSchema, + inferObjectSchemaFromPropertySchemas, + inferParsedObject, + inferParsedObjectFromPropertySchemas, + inferParsedPropertySchema, + inferRawKey, + inferRawObject, + inferRawObjectFromPropertySchemas, + inferRawPropertySchema, + ObjectSchema, + ObjectUtils, + PropertySchemas, +} from "./types"; diff --git a/seed/ts-express/grpc/core/schemas/builders/object/object.ts b/seed/ts-express/grpc/core/schemas/builders/object/object.ts new file mode 100644 index 00000000000..e00136d72fc --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/builders/object/object.ts @@ -0,0 +1,324 @@ +import { MaybeValid, Schema, SchemaType, ValidationError } from "../../Schema"; +import { entries } from "../../utils/entries"; +import { filterObject } from "../../utils/filterObject"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { isPlainObject } from "../../utils/isPlainObject"; +import { keys } from "../../utils/keys"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { partition } from "../../utils/partition"; +import { getObjectLikeUtils } from "../object-like"; +import { getSchemaUtils } from "../schema-utils"; +import { isProperty } from "./property"; +import { + BaseObjectSchema, + inferObjectSchemaFromPropertySchemas, + inferParsedObjectFromPropertySchemas, + inferRawObjectFromPropertySchemas, + ObjectSchema, + ObjectUtils, + PropertySchemas, +} from "./types"; + +interface ObjectPropertyWithRawKey { + rawKey: string; + parsedKey: string; + valueSchema: Schema; +} + +export function object>( + schemas: T +): inferObjectSchemaFromPropertySchemas { + const baseSchema: BaseObjectSchema< + inferRawObjectFromPropertySchemas, + inferParsedObjectFromPropertySchemas + > = { + _getRawProperties: () => + Object.entries(schemas).map(([parsedKey, propertySchema]) => + isProperty(propertySchema) ? propertySchema.rawKey : parsedKey + ) as unknown as (keyof inferRawObjectFromPropertySchemas)[], + _getParsedProperties: () => keys(schemas) as unknown as (keyof inferParsedObjectFromPropertySchemas)[], + + parse: (raw, opts) => { + const rawKeyToProperty: Record = {}; + const requiredKeys: string[] = []; + + for (const [parsedKey, schemaOrObjectProperty] of entries(schemas)) { + const rawKey = isProperty(schemaOrObjectProperty) ? schemaOrObjectProperty.rawKey : parsedKey; + const valueSchema: Schema = isProperty(schemaOrObjectProperty) + ? schemaOrObjectProperty.valueSchema + : schemaOrObjectProperty; + + const property: ObjectPropertyWithRawKey = { + rawKey, + parsedKey: parsedKey as string, + valueSchema, + }; + + rawKeyToProperty[rawKey] = property; + + if (isSchemaRequired(valueSchema)) { + requiredKeys.push(rawKey); + } + } + + return validateAndTransformObject({ + value: raw, + requiredKeys, + getProperty: (rawKey) => { + const property = rawKeyToProperty[rawKey]; + if (property == null) { + return undefined; + } + return { + transformedKey: property.parsedKey, + transform: (propertyValue) => + property.valueSchema.parse(propertyValue, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), rawKey], + }), + }; + }, + unrecognizedObjectKeys: opts?.unrecognizedObjectKeys, + skipValidation: opts?.skipValidation, + breadcrumbsPrefix: opts?.breadcrumbsPrefix, + omitUndefined: opts?.omitUndefined, + }); + }, + + json: (parsed, opts) => { + const requiredKeys: string[] = []; + + for (const [parsedKey, schemaOrObjectProperty] of entries(schemas)) { + const valueSchema: Schema = isProperty(schemaOrObjectProperty) + ? schemaOrObjectProperty.valueSchema + : schemaOrObjectProperty; + + if (isSchemaRequired(valueSchema)) { + requiredKeys.push(parsedKey as string); + } + } + + return validateAndTransformObject({ + value: parsed, + requiredKeys, + getProperty: ( + parsedKey + ): { transformedKey: string; transform: (propertyValue: unknown) => MaybeValid } | undefined => { + const property = schemas[parsedKey as keyof T]; + + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (property == null) { + return undefined; + } + + if (isProperty(property)) { + return { + transformedKey: property.rawKey, + transform: (propertyValue) => + property.valueSchema.json(propertyValue, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), parsedKey], + }), + }; + } else { + return { + transformedKey: parsedKey, + transform: (propertyValue) => + property.json(propertyValue, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), parsedKey], + }), + }; + } + }, + unrecognizedObjectKeys: opts?.unrecognizedObjectKeys, + skipValidation: opts?.skipValidation, + breadcrumbsPrefix: opts?.breadcrumbsPrefix, + omitUndefined: opts?.omitUndefined, + }); + }, + + getType: () => SchemaType.OBJECT, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + ...getObjectLikeUtils(baseSchema), + ...getObjectUtils(baseSchema), + }; +} + +function validateAndTransformObject({ + value, + requiredKeys, + getProperty, + unrecognizedObjectKeys = "fail", + skipValidation = false, + breadcrumbsPrefix = [], +}: { + value: unknown; + requiredKeys: string[]; + getProperty: ( + preTransformedKey: string + ) => { transformedKey: string; transform: (propertyValue: unknown) => MaybeValid } | undefined; + unrecognizedObjectKeys: "fail" | "passthrough" | "strip" | undefined; + skipValidation: boolean | undefined; + breadcrumbsPrefix: string[] | undefined; + omitUndefined: boolean | undefined; +}): MaybeValid { + if (!isPlainObject(value)) { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "object"), + }, + ], + }; + } + + const missingRequiredKeys = new Set(requiredKeys); + const errors: ValidationError[] = []; + const transformed: Record = {}; + + for (const [preTransformedKey, preTransformedItemValue] of Object.entries(value)) { + const property = getProperty(preTransformedKey); + + if (property != null) { + missingRequiredKeys.delete(preTransformedKey); + + const value = property.transform(preTransformedItemValue); + if (value.ok) { + transformed[property.transformedKey] = value.value; + } else { + transformed[preTransformedKey] = preTransformedItemValue; + errors.push(...value.errors); + } + } else { + switch (unrecognizedObjectKeys) { + case "fail": + errors.push({ + path: [...breadcrumbsPrefix, preTransformedKey], + message: `Unexpected key "${preTransformedKey}"`, + }); + break; + case "strip": + break; + case "passthrough": + transformed[preTransformedKey] = preTransformedItemValue; + break; + } + } + } + + errors.push( + ...requiredKeys + .filter((key) => missingRequiredKeys.has(key)) + .map((key) => ({ + path: breadcrumbsPrefix, + message: `Missing required key "${key}"`, + })) + ); + + if (errors.length === 0 || skipValidation) { + return { + ok: true, + value: transformed as Transformed, + }; + } else { + return { + ok: false, + errors, + }; + } +} + +export function getObjectUtils(schema: BaseObjectSchema): ObjectUtils { + return { + extend: (extension: ObjectSchema) => { + const baseSchema: BaseObjectSchema = { + _getParsedProperties: () => [...schema._getParsedProperties(), ...extension._getParsedProperties()], + _getRawProperties: () => [...schema._getRawProperties(), ...extension._getRawProperties()], + parse: (raw, opts) => { + return validateAndTransformExtendedObject({ + extensionKeys: extension._getRawProperties(), + value: raw, + transformBase: (rawBase) => schema.parse(rawBase, opts), + transformExtension: (rawExtension) => extension.parse(rawExtension, opts), + }); + }, + json: (parsed, opts) => { + return validateAndTransformExtendedObject({ + extensionKeys: extension._getParsedProperties(), + value: parsed, + transformBase: (parsedBase) => schema.json(parsedBase, opts), + transformExtension: (parsedExtension) => extension.json(parsedExtension, opts), + }); + }, + getType: () => SchemaType.OBJECT, + }; + + return { + ...baseSchema, + ...getSchemaUtils(baseSchema), + ...getObjectLikeUtils(baseSchema), + ...getObjectUtils(baseSchema), + }; + }, + }; +} + +function validateAndTransformExtendedObject({ + extensionKeys, + value, + transformBase, + transformExtension, +}: { + extensionKeys: (keyof PreTransformedExtension)[]; + value: unknown; + transformBase: (value: unknown) => MaybeValid; + transformExtension: (value: unknown) => MaybeValid; +}): MaybeValid { + const extensionPropertiesSet = new Set(extensionKeys); + const [extensionProperties, baseProperties] = partition(keys(value), (key) => + extensionPropertiesSet.has(key as keyof PreTransformedExtension) + ); + + const transformedBase = transformBase(filterObject(value, baseProperties)); + const transformedExtension = transformExtension(filterObject(value, extensionProperties)); + + if (transformedBase.ok && transformedExtension.ok) { + return { + ok: true, + value: { + ...transformedBase.value, + ...transformedExtension.value, + }, + }; + } else { + return { + ok: false, + errors: [ + ...(transformedBase.ok ? [] : transformedBase.errors), + ...(transformedExtension.ok ? [] : transformedExtension.errors), + ], + }; + } +} + +function isSchemaRequired(schema: Schema): boolean { + return !isSchemaOptional(schema); +} + +function isSchemaOptional(schema: Schema): boolean { + switch (schema.getType()) { + case SchemaType.ANY: + case SchemaType.UNKNOWN: + case SchemaType.OPTIONAL: + return true; + default: + return false; + } +} diff --git a/seed/ts-express/grpc/core/schemas/builders/object/objectWithoutOptionalProperties.ts b/seed/ts-express/grpc/core/schemas/builders/object/objectWithoutOptionalProperties.ts new file mode 100644 index 00000000000..a0951f48efc --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/builders/object/objectWithoutOptionalProperties.ts @@ -0,0 +1,18 @@ +import { object } from "./object"; +import { inferParsedPropertySchema, inferRawObjectFromPropertySchemas, ObjectSchema, PropertySchemas } from "./types"; + +export function objectWithoutOptionalProperties>( + schemas: T +): inferObjectWithoutOptionalPropertiesSchemaFromPropertySchemas { + return object(schemas) as unknown as inferObjectWithoutOptionalPropertiesSchemaFromPropertySchemas; +} + +export type inferObjectWithoutOptionalPropertiesSchemaFromPropertySchemas> = + ObjectSchema< + inferRawObjectFromPropertySchemas, + inferParsedObjectWithoutOptionalPropertiesFromPropertySchemas + >; + +export type inferParsedObjectWithoutOptionalPropertiesFromPropertySchemas> = { + [K in keyof T]: inferParsedPropertySchema; +}; diff --git a/seed/ts-express/grpc/core/schemas/builders/object/property.ts b/seed/ts-express/grpc/core/schemas/builders/object/property.ts new file mode 100644 index 00000000000..d245c4b193a --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/builders/object/property.ts @@ -0,0 +1,23 @@ +import { Schema } from "../../Schema"; + +export function property( + rawKey: RawKey, + valueSchema: Schema +): Property { + return { + rawKey, + valueSchema, + isProperty: true, + }; +} + +export interface Property { + rawKey: RawKey; + valueSchema: Schema; + isProperty: true; +} + +export function isProperty>(maybeProperty: unknown): maybeProperty is O { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + return (maybeProperty as O).isProperty; +} diff --git a/seed/ts-express/grpc/core/schemas/builders/object/types.ts b/seed/ts-express/grpc/core/schemas/builders/object/types.ts new file mode 100644 index 00000000000..de9bb4074e5 --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/builders/object/types.ts @@ -0,0 +1,72 @@ +import { BaseSchema, inferParsed, inferRaw, Schema } from "../../Schema"; +import { addQuestionMarksToNullableProperties } from "../../utils/addQuestionMarksToNullableProperties"; +import { ObjectLikeUtils } from "../object-like"; +import { SchemaUtils } from "../schema-utils"; +import { Property } from "./property"; + +export type ObjectSchema = BaseObjectSchema & + ObjectLikeUtils & + ObjectUtils & + SchemaUtils; + +export interface BaseObjectSchema extends BaseSchema { + _getRawProperties: () => (keyof Raw)[]; + _getParsedProperties: () => (keyof Parsed)[]; +} + +export interface ObjectUtils { + extend: ( + schemas: ObjectSchema + ) => ObjectSchema; +} + +export type inferRawObject> = O extends ObjectSchema ? Raw : never; + +export type inferParsedObject> = O extends ObjectSchema + ? Parsed + : never; + +export type inferObjectSchemaFromPropertySchemas> = ObjectSchema< + inferRawObjectFromPropertySchemas, + inferParsedObjectFromPropertySchemas +>; + +export type inferRawObjectFromPropertySchemas> = + addQuestionMarksToNullableProperties<{ + [ParsedKey in keyof T as inferRawKey]: inferRawPropertySchema; + }>; + +export type inferParsedObjectFromPropertySchemas> = + addQuestionMarksToNullableProperties<{ + [K in keyof T]: inferParsedPropertySchema; + }>; + +export type PropertySchemas = Record< + ParsedKeys, + Property | Schema +>; + +export type inferRawPropertySchema

| Schema> = P extends Property< + any, + infer Raw, + any +> + ? Raw + : P extends Schema + ? inferRaw

+ : never; + +export type inferParsedPropertySchema

| Schema> = P extends Property< + any, + any, + infer Parsed +> + ? Parsed + : P extends Schema + ? inferParsed

+ : never; + +export type inferRawKey< + ParsedKey extends string | number | symbol, + P extends Property | Schema +> = P extends Property ? Raw : ParsedKey; diff --git a/seed/ts-express/grpc/core/schemas/builders/primitives/any.ts b/seed/ts-express/grpc/core/schemas/builders/primitives/any.ts new file mode 100644 index 00000000000..fcaeb04255a --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/builders/primitives/any.ts @@ -0,0 +1,4 @@ +import { SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; + +export const any = createIdentitySchemaCreator(SchemaType.ANY, (value) => ({ ok: true, value })); diff --git a/seed/ts-express/grpc/core/schemas/builders/primitives/boolean.ts b/seed/ts-express/grpc/core/schemas/builders/primitives/boolean.ts new file mode 100644 index 00000000000..fad60562120 --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/builders/primitives/boolean.ts @@ -0,0 +1,25 @@ +import { SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; + +export const boolean = createIdentitySchemaCreator( + SchemaType.BOOLEAN, + (value, { breadcrumbsPrefix = [] } = {}) => { + if (typeof value === "boolean") { + return { + ok: true, + value, + }; + } else { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "boolean"), + }, + ], + }; + } + } +); diff --git a/seed/ts-express/grpc/core/schemas/builders/primitives/index.ts b/seed/ts-express/grpc/core/schemas/builders/primitives/index.ts new file mode 100644 index 00000000000..788f9416bfe --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/builders/primitives/index.ts @@ -0,0 +1,5 @@ +export { any } from "./any"; +export { boolean } from "./boolean"; +export { number } from "./number"; +export { string } from "./string"; +export { unknown } from "./unknown"; diff --git a/seed/ts-express/grpc/core/schemas/builders/primitives/number.ts b/seed/ts-express/grpc/core/schemas/builders/primitives/number.ts new file mode 100644 index 00000000000..c2689456936 --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/builders/primitives/number.ts @@ -0,0 +1,25 @@ +import { SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; + +export const number = createIdentitySchemaCreator( + SchemaType.NUMBER, + (value, { breadcrumbsPrefix = [] } = {}) => { + if (typeof value === "number") { + return { + ok: true, + value, + }; + } else { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "number"), + }, + ], + }; + } + } +); diff --git a/seed/ts-express/grpc/core/schemas/builders/primitives/string.ts b/seed/ts-express/grpc/core/schemas/builders/primitives/string.ts new file mode 100644 index 00000000000..949f1f2a630 --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/builders/primitives/string.ts @@ -0,0 +1,25 @@ +import { SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; + +export const string = createIdentitySchemaCreator( + SchemaType.STRING, + (value, { breadcrumbsPrefix = [] } = {}) => { + if (typeof value === "string") { + return { + ok: true, + value, + }; + } else { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "string"), + }, + ], + }; + } + } +); diff --git a/seed/ts-express/grpc/core/schemas/builders/primitives/unknown.ts b/seed/ts-express/grpc/core/schemas/builders/primitives/unknown.ts new file mode 100644 index 00000000000..4d5249571f5 --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/builders/primitives/unknown.ts @@ -0,0 +1,4 @@ +import { SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; + +export const unknown = createIdentitySchemaCreator(SchemaType.UNKNOWN, (value) => ({ ok: true, value })); diff --git a/seed/ts-express/grpc/core/schemas/builders/record/index.ts b/seed/ts-express/grpc/core/schemas/builders/record/index.ts new file mode 100644 index 00000000000..82e25c5c2af --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/builders/record/index.ts @@ -0,0 +1,2 @@ +export { record } from "./record"; +export type { BaseRecordSchema, RecordSchema } from "./types"; diff --git a/seed/ts-express/grpc/core/schemas/builders/record/record.ts b/seed/ts-express/grpc/core/schemas/builders/record/record.ts new file mode 100644 index 00000000000..6683ac3609f --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/builders/record/record.ts @@ -0,0 +1,130 @@ +import { MaybeValid, Schema, SchemaType, ValidationError } from "../../Schema"; +import { entries } from "../../utils/entries"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { isPlainObject } from "../../utils/isPlainObject"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { getSchemaUtils } from "../schema-utils"; +import { BaseRecordSchema, RecordSchema } from "./types"; + +export function record( + keySchema: Schema, + valueSchema: Schema +): RecordSchema { + const baseSchema: BaseRecordSchema = { + parse: (raw, opts) => { + return validateAndTransformRecord({ + value: raw, + isKeyNumeric: keySchema.getType() === SchemaType.NUMBER, + transformKey: (key) => + keySchema.parse(key, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `${key} (key)`], + }), + transformValue: (value, key) => + valueSchema.parse(value, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `${key}`], + }), + breadcrumbsPrefix: opts?.breadcrumbsPrefix, + }); + }, + json: (parsed, opts) => { + return validateAndTransformRecord({ + value: parsed, + isKeyNumeric: keySchema.getType() === SchemaType.NUMBER, + transformKey: (key) => + keySchema.json(key, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `${key} (key)`], + }), + transformValue: (value, key) => + valueSchema.json(value, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `${key}`], + }), + breadcrumbsPrefix: opts?.breadcrumbsPrefix, + }); + }, + getType: () => SchemaType.RECORD, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + }; +} + +function validateAndTransformRecord({ + value, + isKeyNumeric, + transformKey, + transformValue, + breadcrumbsPrefix = [], +}: { + value: unknown; + isKeyNumeric: boolean; + transformKey: (key: string | number) => MaybeValid; + transformValue: (value: unknown, key: string | number) => MaybeValid; + breadcrumbsPrefix: string[] | undefined; +}): MaybeValid> { + if (!isPlainObject(value)) { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "object"), + }, + ], + }; + } + + return entries(value).reduce>>( + (accPromise, [stringKey, value]) => { + // skip nullish keys + if (value == null) { + return accPromise; + } + + const acc = accPromise; + + let key: string | number = stringKey; + if (isKeyNumeric) { + const numberKey = stringKey.length > 0 ? Number(stringKey) : NaN; + if (!isNaN(numberKey)) { + key = numberKey; + } + } + const transformedKey = transformKey(key); + + const transformedValue = transformValue(value, key); + + if (acc.ok && transformedKey.ok && transformedValue.ok) { + return { + ok: true, + value: { + ...acc.value, + [transformedKey.value]: transformedValue.value, + }, + }; + } + + const errors: ValidationError[] = []; + if (!acc.ok) { + errors.push(...acc.errors); + } + if (!transformedKey.ok) { + errors.push(...transformedKey.errors); + } + if (!transformedValue.ok) { + errors.push(...transformedValue.errors); + } + + return { + ok: false, + errors, + }; + }, + { ok: true, value: {} as Record } + ); +} diff --git a/seed/ts-express/grpc/core/schemas/builders/record/types.ts b/seed/ts-express/grpc/core/schemas/builders/record/types.ts new file mode 100644 index 00000000000..eb82cc7f65c --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/builders/record/types.ts @@ -0,0 +1,17 @@ +import { BaseSchema } from "../../Schema"; +import { SchemaUtils } from "../schema-utils"; + +export type RecordSchema< + RawKey extends string | number, + RawValue, + ParsedKey extends string | number, + ParsedValue +> = BaseRecordSchema & + SchemaUtils, Record>; + +export type BaseRecordSchema< + RawKey extends string | number, + RawValue, + ParsedKey extends string | number, + ParsedValue +> = BaseSchema, Record>; diff --git a/seed/ts-express/grpc/core/schemas/builders/schema-utils/JsonError.ts b/seed/ts-express/grpc/core/schemas/builders/schema-utils/JsonError.ts new file mode 100644 index 00000000000..2b89ca0e7ad --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/builders/schema-utils/JsonError.ts @@ -0,0 +1,9 @@ +import { ValidationError } from "../../Schema"; +import { stringifyValidationError } from "./stringifyValidationErrors"; + +export class JsonError extends Error { + constructor(public readonly errors: ValidationError[]) { + super(errors.map(stringifyValidationError).join("; ")); + Object.setPrototypeOf(this, JsonError.prototype); + } +} diff --git a/seed/ts-express/grpc/core/schemas/builders/schema-utils/ParseError.ts b/seed/ts-express/grpc/core/schemas/builders/schema-utils/ParseError.ts new file mode 100644 index 00000000000..d056eb45cf7 --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/builders/schema-utils/ParseError.ts @@ -0,0 +1,9 @@ +import { ValidationError } from "../../Schema"; +import { stringifyValidationError } from "./stringifyValidationErrors"; + +export class ParseError extends Error { + constructor(public readonly errors: ValidationError[]) { + super(errors.map(stringifyValidationError).join("; ")); + Object.setPrototypeOf(this, ParseError.prototype); + } +} diff --git a/seed/ts-express/grpc/core/schemas/builders/schema-utils/getSchemaUtils.ts b/seed/ts-express/grpc/core/schemas/builders/schema-utils/getSchemaUtils.ts new file mode 100644 index 00000000000..79ecad92132 --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/builders/schema-utils/getSchemaUtils.ts @@ -0,0 +1,105 @@ +import { BaseSchema, Schema, SchemaOptions, SchemaType } from "../../Schema"; +import { JsonError } from "./JsonError"; +import { ParseError } from "./ParseError"; + +export interface SchemaUtils { + optional: () => Schema; + transform: (transformer: SchemaTransformer) => Schema; + parseOrThrow: (raw: unknown, opts?: SchemaOptions) => Parsed; + jsonOrThrow: (raw: unknown, opts?: SchemaOptions) => Raw; +} + +export interface SchemaTransformer { + transform: (parsed: Parsed) => Transformed; + untransform: (transformed: any) => Parsed; +} + +export function getSchemaUtils(schema: BaseSchema): SchemaUtils { + return { + optional: () => optional(schema), + transform: (transformer) => transform(schema, transformer), + parseOrThrow: (raw, opts) => { + const parsed = schema.parse(raw, opts); + if (parsed.ok) { + return parsed.value; + } + throw new ParseError(parsed.errors); + }, + jsonOrThrow: (parsed, opts) => { + const raw = schema.json(parsed, opts); + if (raw.ok) { + return raw.value; + } + throw new JsonError(raw.errors); + }, + }; +} + +/** + * schema utils are defined in one file to resolve issues with circular imports + */ + +export function optional( + schema: BaseSchema +): Schema { + const baseSchema: BaseSchema = { + parse: (raw, opts) => { + if (raw == null) { + return { + ok: true, + value: undefined, + }; + } + return schema.parse(raw, opts); + }, + json: (parsed, opts) => { + if (opts?.omitUndefined && parsed === undefined) { + return { + ok: true, + value: undefined, + }; + } + if (parsed == null) { + return { + ok: true, + value: null, + }; + } + return schema.json(parsed, opts); + }, + getType: () => SchemaType.OPTIONAL, + }; + + return { + ...baseSchema, + ...getSchemaUtils(baseSchema), + }; +} + +export function transform( + schema: BaseSchema, + transformer: SchemaTransformer +): Schema { + const baseSchema: BaseSchema = { + parse: (raw, opts) => { + const parsed = schema.parse(raw, opts); + if (!parsed.ok) { + return parsed; + } + return { + ok: true, + value: transformer.transform(parsed.value), + }; + }, + json: (transformed, opts) => { + const parsed = transformer.untransform(transformed); + return schema.json(parsed, opts); + }, + getType: () => schema.getType(), + }; + + return { + ...baseSchema, + ...getSchemaUtils(baseSchema), + }; +} diff --git a/seed/ts-express/grpc/core/schemas/builders/schema-utils/index.ts b/seed/ts-express/grpc/core/schemas/builders/schema-utils/index.ts new file mode 100644 index 00000000000..aa04e051dfa --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/builders/schema-utils/index.ts @@ -0,0 +1,4 @@ +export { getSchemaUtils, optional, transform } from "./getSchemaUtils"; +export type { SchemaUtils } from "./getSchemaUtils"; +export { JsonError } from "./JsonError"; +export { ParseError } from "./ParseError"; diff --git a/seed/ts-express/grpc/core/schemas/builders/schema-utils/stringifyValidationErrors.ts b/seed/ts-express/grpc/core/schemas/builders/schema-utils/stringifyValidationErrors.ts new file mode 100644 index 00000000000..4160f0a2617 --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/builders/schema-utils/stringifyValidationErrors.ts @@ -0,0 +1,8 @@ +import { ValidationError } from "../../Schema"; + +export function stringifyValidationError(error: ValidationError): string { + if (error.path.length === 0) { + return error.message; + } + return `${error.path.join(" -> ")}: ${error.message}`; +} diff --git a/seed/ts-express/grpc/core/schemas/builders/set/index.ts b/seed/ts-express/grpc/core/schemas/builders/set/index.ts new file mode 100644 index 00000000000..f3310e8bdad --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/builders/set/index.ts @@ -0,0 +1 @@ +export { set } from "./set"; diff --git a/seed/ts-express/grpc/core/schemas/builders/set/set.ts b/seed/ts-express/grpc/core/schemas/builders/set/set.ts new file mode 100644 index 00000000000..e9e6bb7e539 --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/builders/set/set.ts @@ -0,0 +1,43 @@ +import { BaseSchema, Schema, SchemaType } from "../../Schema"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { list } from "../list"; +import { getSchemaUtils } from "../schema-utils"; + +export function set(schema: Schema): Schema> { + const listSchema = list(schema); + const baseSchema: BaseSchema> = { + parse: (raw, opts) => { + const parsedList = listSchema.parse(raw, opts); + if (parsedList.ok) { + return { + ok: true, + value: new Set(parsedList.value), + }; + } else { + return parsedList; + } + }, + json: (parsed, opts) => { + if (!(parsed instanceof Set)) { + return { + ok: false, + errors: [ + { + path: opts?.breadcrumbsPrefix ?? [], + message: getErrorMessageForIncorrectType(parsed, "Set"), + }, + ], + }; + } + const jsonList = listSchema.json([...parsed], opts); + return jsonList; + }, + getType: () => SchemaType.SET, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + }; +} diff --git a/seed/ts-express/grpc/core/schemas/builders/undiscriminated-union/index.ts b/seed/ts-express/grpc/core/schemas/builders/undiscriminated-union/index.ts new file mode 100644 index 00000000000..75b71cb3565 --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/builders/undiscriminated-union/index.ts @@ -0,0 +1,6 @@ +export type { + inferParsedUnidiscriminatedUnionSchema, + inferRawUnidiscriminatedUnionSchema, + UndiscriminatedUnionSchema, +} from "./types"; +export { undiscriminatedUnion } from "./undiscriminatedUnion"; diff --git a/seed/ts-express/grpc/core/schemas/builders/undiscriminated-union/types.ts b/seed/ts-express/grpc/core/schemas/builders/undiscriminated-union/types.ts new file mode 100644 index 00000000000..43e7108a060 --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/builders/undiscriminated-union/types.ts @@ -0,0 +1,10 @@ +import { inferParsed, inferRaw, Schema } from "../../Schema"; + +export type UndiscriminatedUnionSchema = Schema< + inferRawUnidiscriminatedUnionSchema, + inferParsedUnidiscriminatedUnionSchema +>; + +export type inferRawUnidiscriminatedUnionSchema = inferRaw; + +export type inferParsedUnidiscriminatedUnionSchema = inferParsed; diff --git a/seed/ts-express/grpc/core/schemas/builders/undiscriminated-union/undiscriminatedUnion.ts b/seed/ts-express/grpc/core/schemas/builders/undiscriminated-union/undiscriminatedUnion.ts new file mode 100644 index 00000000000..21ed3df0f40 --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/builders/undiscriminated-union/undiscriminatedUnion.ts @@ -0,0 +1,60 @@ +import { BaseSchema, MaybeValid, Schema, SchemaOptions, SchemaType, ValidationError } from "../../Schema"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { getSchemaUtils } from "../schema-utils"; +import { inferParsedUnidiscriminatedUnionSchema, inferRawUnidiscriminatedUnionSchema } from "./types"; + +export function undiscriminatedUnion, ...Schema[]]>( + schemas: Schemas +): Schema, inferParsedUnidiscriminatedUnionSchema> { + const baseSchema: BaseSchema< + inferRawUnidiscriminatedUnionSchema, + inferParsedUnidiscriminatedUnionSchema + > = { + parse: (raw, opts) => { + return validateAndTransformUndiscriminatedUnion>( + (schema, opts) => schema.parse(raw, opts), + schemas, + opts + ); + }, + json: (parsed, opts) => { + return validateAndTransformUndiscriminatedUnion>( + (schema, opts) => schema.json(parsed, opts), + schemas, + opts + ); + }, + getType: () => SchemaType.UNDISCRIMINATED_UNION, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + }; +} + +function validateAndTransformUndiscriminatedUnion( + transform: (schema: Schema, opts: SchemaOptions) => MaybeValid, + schemas: Schema[], + opts: SchemaOptions | undefined +): MaybeValid { + const errors: ValidationError[] = []; + for (const [index, schema] of schemas.entries()) { + const transformed = transform(schema, { ...opts, skipValidation: false }); + if (transformed.ok) { + return transformed; + } else { + for (const error of transformed.errors) { + errors.push({ + path: error.path, + message: `[Variant ${index}] ${error.message}`, + }); + } + } + } + + return { + ok: false, + errors, + }; +} diff --git a/seed/ts-express/grpc/core/schemas/builders/union/discriminant.ts b/seed/ts-express/grpc/core/schemas/builders/union/discriminant.ts new file mode 100644 index 00000000000..55065bc8946 --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/builders/union/discriminant.ts @@ -0,0 +1,14 @@ +export function discriminant( + parsedDiscriminant: ParsedDiscriminant, + rawDiscriminant: RawDiscriminant +): Discriminant { + return { + parsedDiscriminant, + rawDiscriminant, + }; +} + +export interface Discriminant { + parsedDiscriminant: ParsedDiscriminant; + rawDiscriminant: RawDiscriminant; +} diff --git a/seed/ts-express/grpc/core/schemas/builders/union/index.ts b/seed/ts-express/grpc/core/schemas/builders/union/index.ts new file mode 100644 index 00000000000..85fc008a2d8 --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/builders/union/index.ts @@ -0,0 +1,10 @@ +export { discriminant } from "./discriminant"; +export type { Discriminant } from "./discriminant"; +export type { + inferParsedDiscriminant, + inferParsedUnion, + inferRawDiscriminant, + inferRawUnion, + UnionSubtypes, +} from "./types"; +export { union } from "./union"; diff --git a/seed/ts-express/grpc/core/schemas/builders/union/types.ts b/seed/ts-express/grpc/core/schemas/builders/union/types.ts new file mode 100644 index 00000000000..6f82c868b2d --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/builders/union/types.ts @@ -0,0 +1,26 @@ +import { inferParsedObject, inferRawObject, ObjectSchema } from "../object"; +import { Discriminant } from "./discriminant"; + +export type UnionSubtypes = { + [K in DiscriminantValues]: ObjectSchema; +}; + +export type inferRawUnion, U extends UnionSubtypes> = { + [K in keyof U]: Record, K> & inferRawObject; +}[keyof U]; + +export type inferParsedUnion, U extends UnionSubtypes> = { + [K in keyof U]: Record, K> & inferParsedObject; +}[keyof U]; + +export type inferRawDiscriminant> = D extends string + ? D + : D extends Discriminant + ? Raw + : never; + +export type inferParsedDiscriminant> = D extends string + ? D + : D extends Discriminant + ? Parsed + : never; diff --git a/seed/ts-express/grpc/core/schemas/builders/union/union.ts b/seed/ts-express/grpc/core/schemas/builders/union/union.ts new file mode 100644 index 00000000000..ab61475a572 --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/builders/union/union.ts @@ -0,0 +1,170 @@ +import { BaseSchema, MaybeValid, SchemaType } from "../../Schema"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { isPlainObject } from "../../utils/isPlainObject"; +import { keys } from "../../utils/keys"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { enum_ } from "../enum"; +import { ObjectSchema } from "../object"; +import { getObjectLikeUtils, ObjectLikeSchema } from "../object-like"; +import { getSchemaUtils } from "../schema-utils"; +import { Discriminant } from "./discriminant"; +import { inferParsedDiscriminant, inferParsedUnion, inferRawDiscriminant, inferRawUnion, UnionSubtypes } from "./types"; + +export function union, U extends UnionSubtypes>( + discriminant: D, + union: U +): ObjectLikeSchema, inferParsedUnion> { + const rawDiscriminant = + typeof discriminant === "string" ? discriminant : (discriminant.rawDiscriminant as inferRawDiscriminant); + const parsedDiscriminant = + typeof discriminant === "string" + ? discriminant + : (discriminant.parsedDiscriminant as inferParsedDiscriminant); + + const discriminantValueSchema = enum_(keys(union) as string[]); + + const baseSchema: BaseSchema, inferParsedUnion> = { + parse: (raw, opts) => { + return transformAndValidateUnion({ + value: raw, + discriminant: rawDiscriminant, + transformedDiscriminant: parsedDiscriminant, + transformDiscriminantValue: (discriminantValue) => + discriminantValueSchema.parse(discriminantValue, { + allowUnrecognizedEnumValues: opts?.allowUnrecognizedUnionMembers, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), rawDiscriminant], + }), + getAdditionalPropertiesSchema: (discriminantValue) => union[discriminantValue], + allowUnrecognizedUnionMembers: opts?.allowUnrecognizedUnionMembers, + transformAdditionalProperties: (additionalProperties, additionalPropertiesSchema) => + additionalPropertiesSchema.parse(additionalProperties, opts), + breadcrumbsPrefix: opts?.breadcrumbsPrefix, + }); + }, + json: (parsed, opts) => { + return transformAndValidateUnion({ + value: parsed, + discriminant: parsedDiscriminant, + transformedDiscriminant: rawDiscriminant, + transformDiscriminantValue: (discriminantValue) => + discriminantValueSchema.json(discriminantValue, { + allowUnrecognizedEnumValues: opts?.allowUnrecognizedUnionMembers, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), parsedDiscriminant], + }), + getAdditionalPropertiesSchema: (discriminantValue) => union[discriminantValue], + allowUnrecognizedUnionMembers: opts?.allowUnrecognizedUnionMembers, + transformAdditionalProperties: (additionalProperties, additionalPropertiesSchema) => + additionalPropertiesSchema.json(additionalProperties, opts), + breadcrumbsPrefix: opts?.breadcrumbsPrefix, + }); + }, + getType: () => SchemaType.UNION, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + ...getObjectLikeUtils(baseSchema), + }; +} + +function transformAndValidateUnion< + TransformedDiscriminant extends string, + TransformedDiscriminantValue extends string, + TransformedAdditionalProperties +>({ + value, + discriminant, + transformedDiscriminant, + transformDiscriminantValue, + getAdditionalPropertiesSchema, + allowUnrecognizedUnionMembers = false, + transformAdditionalProperties, + breadcrumbsPrefix = [], +}: { + value: unknown; + discriminant: string; + transformedDiscriminant: TransformedDiscriminant; + transformDiscriminantValue: (discriminantValue: unknown) => MaybeValid; + getAdditionalPropertiesSchema: (discriminantValue: string) => ObjectSchema | undefined; + allowUnrecognizedUnionMembers: boolean | undefined; + transformAdditionalProperties: ( + additionalProperties: unknown, + additionalPropertiesSchema: ObjectSchema + ) => MaybeValid; + breadcrumbsPrefix: string[] | undefined; +}): MaybeValid & TransformedAdditionalProperties> { + if (!isPlainObject(value)) { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "object"), + }, + ], + }; + } + + const { [discriminant]: discriminantValue, ...additionalProperties } = value; + + if (discriminantValue == null) { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: `Missing discriminant ("${discriminant}")`, + }, + ], + }; + } + + const transformedDiscriminantValue = transformDiscriminantValue(discriminantValue); + if (!transformedDiscriminantValue.ok) { + return { + ok: false, + errors: transformedDiscriminantValue.errors, + }; + } + + const additionalPropertiesSchema = getAdditionalPropertiesSchema(transformedDiscriminantValue.value); + + if (additionalPropertiesSchema == null) { + if (allowUnrecognizedUnionMembers) { + return { + ok: true, + value: { + [transformedDiscriminant]: transformedDiscriminantValue.value, + ...additionalProperties, + } as Record & TransformedAdditionalProperties, + }; + } else { + return { + ok: false, + errors: [ + { + path: [...breadcrumbsPrefix, discriminant], + message: "Unexpected discriminant value", + }, + ], + }; + } + } + + const transformedAdditionalProperties = transformAdditionalProperties( + additionalProperties, + additionalPropertiesSchema + ); + if (!transformedAdditionalProperties.ok) { + return transformedAdditionalProperties; + } + + return { + ok: true, + value: { + [transformedDiscriminant]: discriminantValue, + ...transformedAdditionalProperties.value, + } as Record & TransformedAdditionalProperties, + }; +} diff --git a/seed/ts-express/grpc/core/schemas/index.ts b/seed/ts-express/grpc/core/schemas/index.ts new file mode 100644 index 00000000000..5429d8b43eb --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/index.ts @@ -0,0 +1,2 @@ +export * from "./builders"; +export type { inferParsed, inferRaw, Schema, SchemaOptions } from "./Schema"; diff --git a/seed/ts-express/grpc/core/schemas/utils/MaybePromise.ts b/seed/ts-express/grpc/core/schemas/utils/MaybePromise.ts new file mode 100644 index 00000000000..9cd354b3418 --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/utils/MaybePromise.ts @@ -0,0 +1 @@ +export type MaybePromise = T | Promise; diff --git a/seed/ts-express/grpc/core/schemas/utils/addQuestionMarksToNullableProperties.ts b/seed/ts-express/grpc/core/schemas/utils/addQuestionMarksToNullableProperties.ts new file mode 100644 index 00000000000..4111d703cd0 --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/utils/addQuestionMarksToNullableProperties.ts @@ -0,0 +1,15 @@ +export type addQuestionMarksToNullableProperties = { + [K in OptionalKeys]?: T[K]; +} & Pick>; + +export type OptionalKeys = { + [K in keyof T]-?: undefined extends T[K] + ? K + : null extends T[K] + ? K + : 1 extends (any extends T[K] ? 0 : 1) + ? never + : K; +}[keyof T]; + +export type RequiredKeys = Exclude>; diff --git a/seed/ts-express/grpc/core/schemas/utils/createIdentitySchemaCreator.ts b/seed/ts-express/grpc/core/schemas/utils/createIdentitySchemaCreator.ts new file mode 100644 index 00000000000..de107cf5ee1 --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/utils/createIdentitySchemaCreator.ts @@ -0,0 +1,21 @@ +import { getSchemaUtils } from "../builders/schema-utils"; +import { BaseSchema, MaybeValid, Schema, SchemaOptions, SchemaType } from "../Schema"; +import { maybeSkipValidation } from "./maybeSkipValidation"; + +export function createIdentitySchemaCreator( + schemaType: SchemaType, + validate: (value: unknown, opts?: SchemaOptions) => MaybeValid +): () => Schema { + return () => { + const baseSchema: BaseSchema = { + parse: validate, + json: validate, + getType: () => schemaType, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + }; + }; +} diff --git a/seed/ts-express/grpc/core/schemas/utils/entries.ts b/seed/ts-express/grpc/core/schemas/utils/entries.ts new file mode 100644 index 00000000000..e122952137d --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/utils/entries.ts @@ -0,0 +1,3 @@ +export function entries(object: T): [keyof T, T[keyof T]][] { + return Object.entries(object) as [keyof T, T[keyof T]][]; +} diff --git a/seed/ts-express/grpc/core/schemas/utils/filterObject.ts b/seed/ts-express/grpc/core/schemas/utils/filterObject.ts new file mode 100644 index 00000000000..2c25a3455bc --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/utils/filterObject.ts @@ -0,0 +1,10 @@ +export function filterObject(obj: T, keysToInclude: K[]): Pick { + const keysToIncludeSet = new Set(keysToInclude); + return Object.entries(obj).reduce((acc, [key, value]) => { + if (keysToIncludeSet.has(key as K)) { + acc[key as K] = value; + } + return acc; + // eslint-disable-next-line @typescript-eslint/prefer-reduce-type-parameter + }, {} as Pick); +} diff --git a/seed/ts-express/grpc/core/schemas/utils/getErrorMessageForIncorrectType.ts b/seed/ts-express/grpc/core/schemas/utils/getErrorMessageForIncorrectType.ts new file mode 100644 index 00000000000..438012df418 --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/utils/getErrorMessageForIncorrectType.ts @@ -0,0 +1,21 @@ +export function getErrorMessageForIncorrectType(value: unknown, expectedType: string): string { + return `Expected ${expectedType}. Received ${getTypeAsString(value)}.`; +} + +function getTypeAsString(value: unknown): string { + if (Array.isArray(value)) { + return "list"; + } + if (value === null) { + return "null"; + } + switch (typeof value) { + case "string": + return `"${value}"`; + case "number": + case "boolean": + case "undefined": + return `${value}`; + } + return typeof value; +} diff --git a/seed/ts-express/grpc/core/schemas/utils/isPlainObject.ts b/seed/ts-express/grpc/core/schemas/utils/isPlainObject.ts new file mode 100644 index 00000000000..db82a722c35 --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/utils/isPlainObject.ts @@ -0,0 +1,17 @@ +// borrowed from https://github.com/lodash/lodash/blob/master/isPlainObject.js +export function isPlainObject(value: unknown): value is Record { + if (typeof value !== "object" || value === null) { + return false; + } + + if (Object.getPrototypeOf(value) === null) { + return true; + } + + let proto = value; + while (Object.getPrototypeOf(proto) !== null) { + proto = Object.getPrototypeOf(proto); + } + + return Object.getPrototypeOf(value) === proto; +} diff --git a/seed/ts-express/grpc/core/schemas/utils/keys.ts b/seed/ts-express/grpc/core/schemas/utils/keys.ts new file mode 100644 index 00000000000..01867098287 --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/utils/keys.ts @@ -0,0 +1,3 @@ +export function keys(object: T): (keyof T)[] { + return Object.keys(object) as (keyof T)[]; +} diff --git a/seed/ts-express/grpc/core/schemas/utils/maybeSkipValidation.ts b/seed/ts-express/grpc/core/schemas/utils/maybeSkipValidation.ts new file mode 100644 index 00000000000..86c07abf2b4 --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/utils/maybeSkipValidation.ts @@ -0,0 +1,38 @@ +import { BaseSchema, MaybeValid, SchemaOptions } from "../Schema"; + +export function maybeSkipValidation, Raw, Parsed>(schema: S): S { + return { + ...schema, + json: transformAndMaybeSkipValidation(schema.json), + parse: transformAndMaybeSkipValidation(schema.parse), + }; +} + +function transformAndMaybeSkipValidation( + transform: (value: unknown, opts?: SchemaOptions) => MaybeValid +): (value: unknown, opts?: SchemaOptions) => MaybeValid { + return (value, opts): MaybeValid => { + const transformed = transform(value, opts); + const { skipValidation = false } = opts ?? {}; + if (!transformed.ok && skipValidation) { + // eslint-disable-next-line no-console + console.warn( + [ + "Failed to validate.", + ...transformed.errors.map( + (error) => + " - " + + (error.path.length > 0 ? `${error.path.join(".")}: ${error.message}` : error.message) + ), + ].join("\n") + ); + + return { + ok: true, + value: value as T, + }; + } else { + return transformed; + } + }; +} diff --git a/seed/ts-express/grpc/core/schemas/utils/partition.ts b/seed/ts-express/grpc/core/schemas/utils/partition.ts new file mode 100644 index 00000000000..f58d6f3d35f --- /dev/null +++ b/seed/ts-express/grpc/core/schemas/utils/partition.ts @@ -0,0 +1,12 @@ +export function partition(items: readonly T[], predicate: (item: T) => boolean): [T[], T[]] { + const trueItems: T[] = [], + falseItems: T[] = []; + for (const item of items) { + if (predicate(item)) { + trueItems.push(item); + } else { + falseItems.push(item); + } + } + return [trueItems, falseItems]; +} diff --git a/seed/ts-express/grpc/errors/SeedApiError.ts b/seed/ts-express/grpc/errors/SeedApiError.ts new file mode 100644 index 00000000000..2e63e9e1784 --- /dev/null +++ b/seed/ts-express/grpc/errors/SeedApiError.ts @@ -0,0 +1,14 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import express from "express"; + +export abstract class SeedApiError extends Error { + constructor(public readonly errorName?: string) { + super(); + Object.setPrototypeOf(this, SeedApiError.prototype); + } + + public abstract send(res: express.Response): Promise; +} diff --git a/seed/ts-express/grpc/errors/index.ts b/seed/ts-express/grpc/errors/index.ts new file mode 100644 index 00000000000..845ebc97622 --- /dev/null +++ b/seed/ts-express/grpc/errors/index.ts @@ -0,0 +1 @@ +export { SeedApiError } from "./SeedApiError"; diff --git a/seed/ts-express/grpc/index.ts b/seed/ts-express/grpc/index.ts new file mode 100644 index 00000000000..e63e1d32ac9 --- /dev/null +++ b/seed/ts-express/grpc/index.ts @@ -0,0 +1,3 @@ +export * as SeedApi from "./api"; +export { register } from "./register"; +export { SeedApiError } from "./errors"; diff --git a/seed/ts-express/grpc/register.ts b/seed/ts-express/grpc/register.ts new file mode 100644 index 00000000000..a3b76088b87 --- /dev/null +++ b/seed/ts-express/grpc/register.ts @@ -0,0 +1,15 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import express from "express"; +import { UserService } from "./api/resources/user/service/UserService"; + +export function register( + expressApp: express.Express | express.Router, + services: { + user: UserService; + } +): void { + (expressApp as any).use("/", services.user.toRouter()); +} diff --git a/seed/ts-express/grpc/serialization/index.ts b/seed/ts-express/grpc/serialization/index.ts new file mode 100644 index 00000000000..3e5335fe421 --- /dev/null +++ b/seed/ts-express/grpc/serialization/index.ts @@ -0,0 +1 @@ +export * from "./resources"; diff --git a/seed/ts-express/grpc/serialization/resources/index.ts b/seed/ts-express/grpc/serialization/resources/index.ts new file mode 100644 index 00000000000..ebc1d4fff94 --- /dev/null +++ b/seed/ts-express/grpc/serialization/resources/index.ts @@ -0,0 +1,3 @@ +export * as user from "./user"; +export * from "./user/types"; +export * from "./user/service/requests"; diff --git a/seed/ts-express/grpc/serialization/resources/user/index.ts b/seed/ts-express/grpc/serialization/resources/user/index.ts new file mode 100644 index 00000000000..fcc81debec4 --- /dev/null +++ b/seed/ts-express/grpc/serialization/resources/user/index.ts @@ -0,0 +1,2 @@ +export * from "./types"; +export * from "./service"; diff --git a/seed/ts-express/grpc/serialization/resources/user/service/index.ts b/seed/ts-express/grpc/serialization/resources/user/service/index.ts new file mode 100644 index 00000000000..415726b7fea --- /dev/null +++ b/seed/ts-express/grpc/serialization/resources/user/service/index.ts @@ -0,0 +1 @@ +export * from "./requests"; diff --git a/seed/ts-express/grpc/serialization/resources/user/service/requests/CreateUserRequest.ts b/seed/ts-express/grpc/serialization/resources/user/service/requests/CreateUserRequest.ts new file mode 100644 index 00000000000..8e7d8190af2 --- /dev/null +++ b/seed/ts-express/grpc/serialization/resources/user/service/requests/CreateUserRequest.ts @@ -0,0 +1,26 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../../index"; +import * as SeedApi from "../../../../../api/index"; +import * as core from "../../../../../core"; + +export const CreateUserRequest: core.serialization.Schema< + serializers.CreateUserRequest.Raw, + SeedApi.CreateUserRequest +> = core.serialization.object({ + username: core.serialization.string(), + email: core.serialization.string().optional(), + age: core.serialization.number().optional(), + weight: core.serialization.number().optional(), +}); + +export declare namespace CreateUserRequest { + interface Raw { + username: string; + email?: string | null; + age?: number | null; + weight?: number | null; + } +} diff --git a/seed/ts-express/grpc/serialization/resources/user/service/requests/index.ts b/seed/ts-express/grpc/serialization/resources/user/service/requests/index.ts new file mode 100644 index 00000000000..5632de83260 --- /dev/null +++ b/seed/ts-express/grpc/serialization/resources/user/service/requests/index.ts @@ -0,0 +1 @@ +export { CreateUserRequest } from "./CreateUserRequest"; diff --git a/seed/ts-express/grpc/serialization/resources/user/types/CreateUserResponse.ts b/seed/ts-express/grpc/serialization/resources/user/types/CreateUserResponse.ts new file mode 100644 index 00000000000..688eaafe490 --- /dev/null +++ b/seed/ts-express/grpc/serialization/resources/user/types/CreateUserResponse.ts @@ -0,0 +1,20 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as SeedApi from "../../../../api/index"; +import * as core from "../../../../core"; + +export const CreateUserResponse: core.serialization.ObjectSchema< + serializers.CreateUserResponse.Raw, + SeedApi.CreateUserResponse +> = core.serialization.object({ + user: core.serialization.lazyObject(() => serializers.User), +}); + +export declare namespace CreateUserResponse { + interface Raw { + user: serializers.User.Raw; + } +} diff --git a/seed/ts-express/grpc/serialization/resources/user/types/Metadata.ts b/seed/ts-express/grpc/serialization/resources/user/types/Metadata.ts new file mode 100644 index 00000000000..dbf02618349 --- /dev/null +++ b/seed/ts-express/grpc/serialization/resources/user/types/Metadata.ts @@ -0,0 +1,17 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as SeedApi from "../../../../api/index"; +import * as core from "../../../../core"; + +export const Metadata: core.serialization.Schema = + core.serialization.record( + core.serialization.string(), + core.serialization.lazy(() => serializers.MetadataValue).optional() + ); + +export declare namespace Metadata { + type Raw = Record; +} diff --git a/seed/ts-express/grpc/serialization/resources/user/types/MetadataValue.ts b/seed/ts-express/grpc/serialization/resources/user/types/MetadataValue.ts new file mode 100644 index 00000000000..4ff84e74aa7 --- /dev/null +++ b/seed/ts-express/grpc/serialization/resources/user/types/MetadataValue.ts @@ -0,0 +1,19 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as SeedApi from "../../../../api/index"; +import * as core from "../../../../core"; + +export const MetadataValue: core.serialization.Schema = + core.serialization.undiscriminatedUnion([ + core.serialization.number(), + core.serialization.string(), + core.serialization.boolean(), + core.serialization.list(core.serialization.lazy(() => serializers.MetadataValue)), + ]); + +export declare namespace MetadataValue { + type Raw = number | string | boolean | serializers.MetadataValue.Raw[]; +} diff --git a/seed/ts-express/grpc/serialization/resources/user/types/User.ts b/seed/ts-express/grpc/serialization/resources/user/types/User.ts new file mode 100644 index 00000000000..b66e38c0b1b --- /dev/null +++ b/seed/ts-express/grpc/serialization/resources/user/types/User.ts @@ -0,0 +1,27 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as SeedApi from "../../../../api/index"; +import * as core from "../../../../core"; + +export const User: core.serialization.ObjectSchema = core.serialization.object({ + id: core.serialization.string(), + username: core.serialization.string(), + email: core.serialization.string().optional(), + age: core.serialization.number().optional(), + weight: core.serialization.number().optional(), + metadata: core.serialization.lazy(() => serializers.Metadata).optional(), +}); + +export declare namespace User { + interface Raw { + id: string; + username: string; + email?: string | null; + age?: number | null; + weight?: number | null; + metadata?: serializers.Metadata.Raw | null; + } +} diff --git a/seed/ts-express/grpc/serialization/resources/user/types/index.ts b/seed/ts-express/grpc/serialization/resources/user/types/index.ts new file mode 100644 index 00000000000..aff31139192 --- /dev/null +++ b/seed/ts-express/grpc/serialization/resources/user/types/index.ts @@ -0,0 +1,4 @@ +export * from "./Metadata"; +export * from "./MetadataValue"; +export * from "./User"; +export * from "./CreateUserResponse"; diff --git a/seed/ts-express/grpc/snippet-templates.json b/seed/ts-express/grpc/snippet-templates.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/ts-express/grpc/snippet.json b/seed/ts-express/grpc/snippet.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/seed/ts-sdk/alias-extends/package.json b/seed/ts-sdk/alias-extends/package.json index 872692fa435..85f364f1c8b 100644 --- a/seed/ts-sdk/alias-extends/package.json +++ b/seed/ts-sdk/alias-extends/package.json @@ -22,6 +22,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/alias-extends/src/Client.ts b/seed/ts-sdk/alias-extends/src/Client.ts index 2977e3b7823..4ef8f23057e 100644 --- a/seed/ts-sdk/alias-extends/src/Client.ts +++ b/seed/ts-sdk/alias-extends/src/Client.ts @@ -47,6 +47,7 @@ export class SeedAliasExtendsClient { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/alias-extends", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/alias-extends/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/alias-extends/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/alias-extends/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/alias-extends/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/alias-extends/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/alias/package.json b/seed/ts-sdk/alias/package.json index 09faf63d0ed..15a0bb1745f 100644 --- a/seed/ts-sdk/alias/package.json +++ b/seed/ts-sdk/alias/package.json @@ -22,6 +22,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/alias/src/Client.ts b/seed/ts-sdk/alias/src/Client.ts index ed0e7ed516b..0fe7d65fce1 100644 --- a/seed/ts-sdk/alias/src/Client.ts +++ b/seed/ts-sdk/alias/src/Client.ts @@ -44,6 +44,7 @@ export class SeedAliasClient { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/alias", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/alias/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/alias/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/alias/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/alias/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/alias/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/api-wide-base-path/package.json b/seed/ts-sdk/api-wide-base-path/package.json index aaf30c5008e..8af7e56c38c 100644 --- a/seed/ts-sdk/api-wide-base-path/package.json +++ b/seed/ts-sdk/api-wide-base-path/package.json @@ -22,6 +22,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/api-wide-base-path/src/api/resources/service/client/Client.ts b/seed/ts-sdk/api-wide-base-path/src/api/resources/service/client/Client.ts index a6358af6c5b..5fddd0b44c2 100644 --- a/seed/ts-sdk/api-wide-base-path/src/api/resources/service/client/Client.ts +++ b/seed/ts-sdk/api-wide-base-path/src/api/resources/service/client/Client.ts @@ -52,6 +52,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/api-wide-base-path", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/api-wide-base-path/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/api-wide-base-path/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/api-wide-base-path/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/api-wide-base-path/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/api-wide-base-path/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/audiences/package.json b/seed/ts-sdk/audiences/package.json index b5f9c539224..6e4f4c3f89e 100644 --- a/seed/ts-sdk/audiences/package.json +++ b/seed/ts-sdk/audiences/package.json @@ -20,6 +20,7 @@ "devDependencies": { "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/audiences/src/api/resources/folderA/resources/service/client/Client.ts b/seed/ts-sdk/audiences/src/api/resources/folderA/resources/service/client/Client.ts index 24d2b80b947..2a35eb39f8a 100644 --- a/seed/ts-sdk/audiences/src/api/resources/folderA/resources/service/client/Client.ts +++ b/seed/ts-sdk/audiences/src/api/resources/folderA/resources/service/client/Client.ts @@ -39,6 +39,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/audiences", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/audiences/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/audiences/src/api/resources/foo/client/Client.ts b/seed/ts-sdk/audiences/src/api/resources/foo/client/Client.ts index 80d805f5b50..212a3a82550 100644 --- a/seed/ts-sdk/audiences/src/api/resources/foo/client/Client.ts +++ b/seed/ts-sdk/audiences/src/api/resources/foo/client/Client.ts @@ -53,6 +53,7 @@ export class Foo { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/audiences", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/audiences/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/audiences/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/audiences/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/audiences/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/audiences/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/auth-environment-variables/package.json b/seed/ts-sdk/auth-environment-variables/package.json index fa80de4545b..5e49d7c1d12 100644 --- a/seed/ts-sdk/auth-environment-variables/package.json +++ b/seed/ts-sdk/auth-environment-variables/package.json @@ -22,6 +22,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/auth-environment-variables/src/api/resources/service/client/Client.ts b/seed/ts-sdk/auth-environment-variables/src/api/resources/service/client/Client.ts index f2ec683341a..ef7f7ab09a5 100644 --- a/seed/ts-sdk/auth-environment-variables/src/api/resources/service/client/Client.ts +++ b/seed/ts-sdk/auth-environment-variables/src/api/resources/service/client/Client.ts @@ -48,6 +48,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/auth-environment-variables", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/auth-environment-variables/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, ...(await this._getCustomAuthorizationHeaders()), @@ -113,6 +114,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/auth-environment-variables", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/auth-environment-variables/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, "X-Endpoint-Header": xEndpointHeader, diff --git a/seed/ts-sdk/auth-environment-variables/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/auth-environment-variables/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/auth-environment-variables/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/auth-environment-variables/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/basic-auth-environment-variables/package.json b/seed/ts-sdk/basic-auth-environment-variables/package.json index 75265c78a0b..caf8571cc6f 100644 --- a/seed/ts-sdk/basic-auth-environment-variables/package.json +++ b/seed/ts-sdk/basic-auth-environment-variables/package.json @@ -23,6 +23,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/basic-auth-environment-variables/src/api/resources/basicAuth/client/Client.ts b/seed/ts-sdk/basic-auth-environment-variables/src/api/resources/basicAuth/client/Client.ts index c2e68f70e70..185c8f6ebf1 100644 --- a/seed/ts-sdk/basic-auth-environment-variables/src/api/resources/basicAuth/client/Client.ts +++ b/seed/ts-sdk/basic-auth-environment-variables/src/api/resources/basicAuth/client/Client.ts @@ -47,6 +47,7 @@ export class BasicAuth { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/basic-auth-environment-variables", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/basic-auth-environment-variables/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -122,6 +123,7 @@ export class BasicAuth { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/basic-auth-environment-variables", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/basic-auth-environment-variables/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/basic-auth-environment-variables/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/basic-auth-environment-variables/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/basic-auth-environment-variables/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/basic-auth-environment-variables/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/basic-auth/package.json b/seed/ts-sdk/basic-auth/package.json index 44569d40fb9..a79ef861e91 100644 --- a/seed/ts-sdk/basic-auth/package.json +++ b/seed/ts-sdk/basic-auth/package.json @@ -23,6 +23,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/basic-auth/src/api/resources/basicAuth/client/Client.ts b/seed/ts-sdk/basic-auth/src/api/resources/basicAuth/client/Client.ts index 663bda8f3d7..e52a935931b 100644 --- a/seed/ts-sdk/basic-auth/src/api/resources/basicAuth/client/Client.ts +++ b/seed/ts-sdk/basic-auth/src/api/resources/basicAuth/client/Client.ts @@ -47,6 +47,7 @@ export class BasicAuth { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/basic-auth", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/basic-auth/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -122,6 +123,7 @@ export class BasicAuth { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/basic-auth", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/basic-auth/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/basic-auth/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/basic-auth/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/basic-auth/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/basic-auth/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/bearer-token-environment-variable/package.json b/seed/ts-sdk/bearer-token-environment-variable/package.json index e1542bafb6c..2c97e82814a 100644 --- a/seed/ts-sdk/bearer-token-environment-variable/package.json +++ b/seed/ts-sdk/bearer-token-environment-variable/package.json @@ -23,6 +23,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/bearer-token-environment-variable/src/api/resources/service/client/Client.ts b/seed/ts-sdk/bearer-token-environment-variable/src/api/resources/service/client/Client.ts index ee235bcd2ae..b5602316e4a 100644 --- a/seed/ts-sdk/bearer-token-environment-variable/src/api/resources/service/client/Client.ts +++ b/seed/ts-sdk/bearer-token-environment-variable/src/api/resources/service/client/Client.ts @@ -43,6 +43,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/bearer-token-environment-variable", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/bearer-token-environment-variable/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/bearer-token-environment-variable/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/bearer-token-environment-variable/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/bearer-token-environment-variable/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/bearer-token-environment-variable/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/code-samples/package.json b/seed/ts-sdk/code-samples/package.json index 0ec76e0c001..9bb11480bc7 100644 --- a/seed/ts-sdk/code-samples/package.json +++ b/seed/ts-sdk/code-samples/package.json @@ -22,6 +22,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/code-samples/src/api/resources/service/client/Client.ts b/seed/ts-sdk/code-samples/src/api/resources/service/client/Client.ts index 1cf4b82ff52..422be386f50 100644 --- a/seed/ts-sdk/code-samples/src/api/resources/service/client/Client.ts +++ b/seed/ts-sdk/code-samples/src/api/resources/service/client/Client.ts @@ -46,6 +46,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/code-samples", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/code-samples/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/code-samples/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/code-samples/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/code-samples/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/code-samples/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/custom-auth/package.json b/seed/ts-sdk/custom-auth/package.json index bad428589e7..fbccaa5caad 100644 --- a/seed/ts-sdk/custom-auth/package.json +++ b/seed/ts-sdk/custom-auth/package.json @@ -22,6 +22,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/custom-auth/src/api/resources/customAuth/client/Client.ts b/seed/ts-sdk/custom-auth/src/api/resources/customAuth/client/Client.ts index 7471a5a6cce..65a7d175ace 100644 --- a/seed/ts-sdk/custom-auth/src/api/resources/customAuth/client/Client.ts +++ b/seed/ts-sdk/custom-auth/src/api/resources/customAuth/client/Client.ts @@ -45,6 +45,7 @@ export class CustomAuth { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/custom-auth", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/custom-auth/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, ...(await this._getCustomAuthorizationHeaders()), @@ -120,6 +121,7 @@ export class CustomAuth { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/custom-auth", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/custom-auth/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, ...(await this._getCustomAuthorizationHeaders()), diff --git a/seed/ts-sdk/custom-auth/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/custom-auth/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/custom-auth/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/custom-auth/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/enum/package.json b/seed/ts-sdk/enum/package.json index 312493a1a48..bfd921ff9a4 100644 --- a/seed/ts-sdk/enum/package.json +++ b/seed/ts-sdk/enum/package.json @@ -22,6 +22,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/enum/src/api/resources/inlinedRequest/client/Client.ts b/seed/ts-sdk/enum/src/api/resources/inlinedRequest/client/Client.ts index bcbd5168633..77071c0a8ae 100644 --- a/seed/ts-sdk/enum/src/api/resources/inlinedRequest/client/Client.ts +++ b/seed/ts-sdk/enum/src/api/resources/inlinedRequest/client/Client.ts @@ -47,6 +47,7 @@ export class InlinedRequest { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/enum", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/enum/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/enum/src/api/resources/pathParam/client/Client.ts b/seed/ts-sdk/enum/src/api/resources/pathParam/client/Client.ts index eaac275aa0b..6937ace7882 100644 --- a/seed/ts-sdk/enum/src/api/resources/pathParam/client/Client.ts +++ b/seed/ts-sdk/enum/src/api/resources/pathParam/client/Client.ts @@ -57,6 +57,7 @@ export class PathParam { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/enum", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/enum/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/enum/src/api/resources/queryParam/client/Client.ts b/seed/ts-sdk/enum/src/api/resources/queryParam/client/Client.ts index 86ad85350d3..29fcd52853d 100644 --- a/seed/ts-sdk/enum/src/api/resources/queryParam/client/Client.ts +++ b/seed/ts-sdk/enum/src/api/resources/queryParam/client/Client.ts @@ -60,6 +60,7 @@ export class QueryParam { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/enum", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/enum/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -155,6 +156,7 @@ export class QueryParam { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/enum", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/enum/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/enum/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/enum/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/enum/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/enum/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/error-property/union-utils/package.json b/seed/ts-sdk/error-property/union-utils/package.json index c540c960615..a74b71a434f 100644 --- a/seed/ts-sdk/error-property/union-utils/package.json +++ b/seed/ts-sdk/error-property/union-utils/package.json @@ -22,6 +22,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/error-property/union-utils/src/api/resources/propertyBasedError/client/Client.ts b/seed/ts-sdk/error-property/union-utils/src/api/resources/propertyBasedError/client/Client.ts index b8349b2c35f..62f2a7bc74b 100644 --- a/seed/ts-sdk/error-property/union-utils/src/api/resources/propertyBasedError/client/Client.ts +++ b/seed/ts-sdk/error-property/union-utils/src/api/resources/propertyBasedError/client/Client.ts @@ -44,6 +44,7 @@ export class PropertyBasedError { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/error-property", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/error-property/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/error-property/union-utils/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/error-property/union-utils/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/error-property/union-utils/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/error-property/union-utils/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/examples/examples-with-api-reference/package.json b/seed/ts-sdk/examples/examples-with-api-reference/package.json index c9f903eda56..efd52229b0e 100644 --- a/seed/ts-sdk/examples/examples-with-api-reference/package.json +++ b/seed/ts-sdk/examples/examples-with-api-reference/package.json @@ -23,6 +23,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/examples/examples-with-api-reference/src/Client.ts b/seed/ts-sdk/examples/examples-with-api-reference/src/Client.ts index d6f156e3934..c5df1328075 100644 --- a/seed/ts-sdk/examples/examples-with-api-reference/src/Client.ts +++ b/seed/ts-sdk/examples/examples-with-api-reference/src/Client.ts @@ -45,6 +45,7 @@ export class SeedExamplesClient { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/examples", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/examples/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/examples/examples-with-api-reference/src/api/resources/file/resources/notification/resources/service/client/Client.ts b/seed/ts-sdk/examples/examples-with-api-reference/src/api/resources/file/resources/notification/resources/service/client/Client.ts index d76e0b905ce..89fe212ad26 100644 --- a/seed/ts-sdk/examples/examples-with-api-reference/src/api/resources/file/resources/notification/resources/service/client/Client.ts +++ b/seed/ts-sdk/examples/examples-with-api-reference/src/api/resources/file/resources/notification/resources/service/client/Client.ts @@ -50,6 +50,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/examples", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/examples/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/examples/examples-with-api-reference/src/api/resources/file/resources/service/client/Client.ts b/seed/ts-sdk/examples/examples-with-api-reference/src/api/resources/file/resources/service/client/Client.ts index e398b0bbdbb..e3d2ee48753 100644 --- a/seed/ts-sdk/examples/examples-with-api-reference/src/api/resources/file/resources/service/client/Client.ts +++ b/seed/ts-sdk/examples/examples-with-api-reference/src/api/resources/file/resources/service/client/Client.ts @@ -56,6 +56,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/examples", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/examples/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, "X-File-API-Version": xFileApiVersion, diff --git a/seed/ts-sdk/examples/examples-with-api-reference/src/api/resources/health/resources/service/client/Client.ts b/seed/ts-sdk/examples/examples-with-api-reference/src/api/resources/health/resources/service/client/Client.ts index e3437112df4..f012bab5e08 100644 --- a/seed/ts-sdk/examples/examples-with-api-reference/src/api/resources/health/resources/service/client/Client.ts +++ b/seed/ts-sdk/examples/examples-with-api-reference/src/api/resources/health/resources/service/client/Client.ts @@ -48,6 +48,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/examples", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/examples/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -100,6 +101,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/examples", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/examples/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/examples/examples-with-api-reference/src/api/resources/service/client/Client.ts b/seed/ts-sdk/examples/examples-with-api-reference/src/api/resources/service/client/Client.ts index 3feba276965..5192612b55f 100644 --- a/seed/ts-sdk/examples/examples-with-api-reference/src/api/resources/service/client/Client.ts +++ b/seed/ts-sdk/examples/examples-with-api-reference/src/api/resources/service/client/Client.ts @@ -50,6 +50,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/examples", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/examples/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -129,6 +130,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/examples", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/examples/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -207,6 +209,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/examples", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/examples/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, "X-API-Version": xApiVersion, @@ -264,6 +267,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/examples", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/examples/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/examples/examples-with-api-reference/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/examples/examples-with-api-reference/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/examples/examples-with-api-reference/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/examples/examples-with-api-reference/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/examples/retain-original-casing/package.json b/seed/ts-sdk/examples/retain-original-casing/package.json index c9f903eda56..efd52229b0e 100644 --- a/seed/ts-sdk/examples/retain-original-casing/package.json +++ b/seed/ts-sdk/examples/retain-original-casing/package.json @@ -23,6 +23,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/examples/retain-original-casing/src/Client.ts b/seed/ts-sdk/examples/retain-original-casing/src/Client.ts index d6f156e3934..c5df1328075 100644 --- a/seed/ts-sdk/examples/retain-original-casing/src/Client.ts +++ b/seed/ts-sdk/examples/retain-original-casing/src/Client.ts @@ -45,6 +45,7 @@ export class SeedExamplesClient { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/examples", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/examples/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/examples/retain-original-casing/src/api/resources/file/resources/notification/resources/service/client/Client.ts b/seed/ts-sdk/examples/retain-original-casing/src/api/resources/file/resources/notification/resources/service/client/Client.ts index d76e0b905ce..89fe212ad26 100644 --- a/seed/ts-sdk/examples/retain-original-casing/src/api/resources/file/resources/notification/resources/service/client/Client.ts +++ b/seed/ts-sdk/examples/retain-original-casing/src/api/resources/file/resources/notification/resources/service/client/Client.ts @@ -50,6 +50,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/examples", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/examples/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/examples/retain-original-casing/src/api/resources/file/resources/service/client/Client.ts b/seed/ts-sdk/examples/retain-original-casing/src/api/resources/file/resources/service/client/Client.ts index 23c0ca25cf8..bcf645171ce 100644 --- a/seed/ts-sdk/examples/retain-original-casing/src/api/resources/file/resources/service/client/Client.ts +++ b/seed/ts-sdk/examples/retain-original-casing/src/api/resources/file/resources/service/client/Client.ts @@ -56,6 +56,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/examples", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/examples/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, "X-File-API-Version": xFileApiVersion, diff --git a/seed/ts-sdk/examples/retain-original-casing/src/api/resources/health/resources/service/client/Client.ts b/seed/ts-sdk/examples/retain-original-casing/src/api/resources/health/resources/service/client/Client.ts index e3437112df4..f012bab5e08 100644 --- a/seed/ts-sdk/examples/retain-original-casing/src/api/resources/health/resources/service/client/Client.ts +++ b/seed/ts-sdk/examples/retain-original-casing/src/api/resources/health/resources/service/client/Client.ts @@ -48,6 +48,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/examples", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/examples/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -100,6 +101,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/examples", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/examples/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/examples/retain-original-casing/src/api/resources/service/client/Client.ts b/seed/ts-sdk/examples/retain-original-casing/src/api/resources/service/client/Client.ts index d0b94a46269..7c84f5f2595 100644 --- a/seed/ts-sdk/examples/retain-original-casing/src/api/resources/service/client/Client.ts +++ b/seed/ts-sdk/examples/retain-original-casing/src/api/resources/service/client/Client.ts @@ -50,6 +50,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/examples", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/examples/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -129,6 +130,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/examples", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/examples/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -207,6 +209,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/examples", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/examples/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, "X-API-Version": xApiVersion, @@ -264,6 +267,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/examples", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/examples/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/examples/retain-original-casing/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/examples/retain-original-casing/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/examples/retain-original-casing/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/examples/retain-original-casing/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/exhaustive/allow-extra-fields/package.json b/seed/ts-sdk/exhaustive/allow-extra-fields/package.json index ca0d197313d..27ead133beb 100644 --- a/seed/ts-sdk/exhaustive/allow-extra-fields/package.json +++ b/seed/ts-sdk/exhaustive/allow-extra-fields/package.json @@ -23,6 +23,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/exhaustive/allow-extra-fields/src/api/resources/endpoints/resources/container/client/Client.ts b/seed/ts-sdk/exhaustive/allow-extra-fields/src/api/resources/endpoints/resources/container/client/Client.ts index dbe2a108c26..b45ff553cf8 100644 --- a/seed/ts-sdk/exhaustive/allow-extra-fields/src/api/resources/endpoints/resources/container/client/Client.ts +++ b/seed/ts-sdk/exhaustive/allow-extra-fields/src/api/resources/endpoints/resources/container/client/Client.ts @@ -46,6 +46,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -112,6 +113,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -176,6 +178,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -242,6 +245,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -308,6 +312,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -376,6 +381,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -442,6 +448,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/allow-extra-fields/src/api/resources/endpoints/resources/enum/client/Client.ts b/seed/ts-sdk/exhaustive/allow-extra-fields/src/api/resources/endpoints/resources/enum/client/Client.ts index 1f4d8f42d09..2fe86c0b9ce 100644 --- a/seed/ts-sdk/exhaustive/allow-extra-fields/src/api/resources/endpoints/resources/enum/client/Client.ts +++ b/seed/ts-sdk/exhaustive/allow-extra-fields/src/api/resources/endpoints/resources/enum/client/Client.ts @@ -46,6 +46,7 @@ export class Enum { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/allow-extra-fields/src/api/resources/endpoints/resources/httpMethods/client/Client.ts b/seed/ts-sdk/exhaustive/allow-extra-fields/src/api/resources/endpoints/resources/httpMethods/client/Client.ts index cabb489ca77..a245d73ab10 100644 --- a/seed/ts-sdk/exhaustive/allow-extra-fields/src/api/resources/endpoints/resources/httpMethods/client/Client.ts +++ b/seed/ts-sdk/exhaustive/allow-extra-fields/src/api/resources/endpoints/resources/httpMethods/client/Client.ts @@ -43,6 +43,7 @@ export class HttpMethods { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -104,6 +105,7 @@ export class HttpMethods { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -172,6 +174,7 @@ export class HttpMethods { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -254,6 +257,7 @@ export class HttpMethods { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -315,6 +319,7 @@ export class HttpMethods { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/allow-extra-fields/src/api/resources/endpoints/resources/object/client/Client.ts b/seed/ts-sdk/exhaustive/allow-extra-fields/src/api/resources/endpoints/resources/object/client/Client.ts index e450074f88f..789b4d32a65 100644 --- a/seed/ts-sdk/exhaustive/allow-extra-fields/src/api/resources/endpoints/resources/object/client/Client.ts +++ b/seed/ts-sdk/exhaustive/allow-extra-fields/src/api/resources/endpoints/resources/object/client/Client.ts @@ -65,6 +65,7 @@ export class Object_ { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -134,6 +135,7 @@ export class Object_ { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -204,6 +206,7 @@ export class Object_ { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -290,6 +293,7 @@ export class Object_ { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -378,6 +382,7 @@ export class Object_ { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -464,6 +469,7 @@ export class Object_ { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/allow-extra-fields/src/api/resources/endpoints/resources/params/client/Client.ts b/seed/ts-sdk/exhaustive/allow-extra-fields/src/api/resources/endpoints/resources/params/client/Client.ts index 4f16c37d09c..fcc24c1eced 100644 --- a/seed/ts-sdk/exhaustive/allow-extra-fields/src/api/resources/endpoints/resources/params/client/Client.ts +++ b/seed/ts-sdk/exhaustive/allow-extra-fields/src/api/resources/endpoints/resources/params/client/Client.ts @@ -48,6 +48,7 @@ export class Params { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -116,6 +117,7 @@ export class Params { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -190,6 +192,7 @@ export class Params { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -257,6 +260,7 @@ export class Params { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -319,6 +323,7 @@ export class Params { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/allow-extra-fields/src/api/resources/endpoints/resources/primitive/client/Client.ts b/seed/ts-sdk/exhaustive/allow-extra-fields/src/api/resources/endpoints/resources/primitive/client/Client.ts index dc588c16733..2984d243431 100644 --- a/seed/ts-sdk/exhaustive/allow-extra-fields/src/api/resources/endpoints/resources/primitive/client/Client.ts +++ b/seed/ts-sdk/exhaustive/allow-extra-fields/src/api/resources/endpoints/resources/primitive/client/Client.ts @@ -42,6 +42,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -103,6 +104,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -164,6 +166,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -225,6 +228,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -286,6 +290,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -347,6 +352,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -408,6 +414,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -469,6 +476,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -530,6 +538,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/allow-extra-fields/src/api/resources/endpoints/resources/union/client/Client.ts b/seed/ts-sdk/exhaustive/allow-extra-fields/src/api/resources/endpoints/resources/union/client/Client.ts index c9bbfcc3737..a13ee9f56be 100644 --- a/seed/ts-sdk/exhaustive/allow-extra-fields/src/api/resources/endpoints/resources/union/client/Client.ts +++ b/seed/ts-sdk/exhaustive/allow-extra-fields/src/api/resources/endpoints/resources/union/client/Client.ts @@ -50,6 +50,7 @@ export class Union { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/allow-extra-fields/src/api/resources/inlinedRequests/client/Client.ts b/seed/ts-sdk/exhaustive/allow-extra-fields/src/api/resources/inlinedRequests/client/Client.ts index d46954277c4..e77820c5944 100644 --- a/seed/ts-sdk/exhaustive/allow-extra-fields/src/api/resources/inlinedRequests/client/Client.ts +++ b/seed/ts-sdk/exhaustive/allow-extra-fields/src/api/resources/inlinedRequests/client/Client.ts @@ -70,6 +70,7 @@ export class InlinedRequests { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/allow-extra-fields/src/api/resources/noAuth/client/Client.ts b/seed/ts-sdk/exhaustive/allow-extra-fields/src/api/resources/noAuth/client/Client.ts index 9cadcbff1b5..0d9f7cee142 100644 --- a/seed/ts-sdk/exhaustive/allow-extra-fields/src/api/resources/noAuth/client/Client.ts +++ b/seed/ts-sdk/exhaustive/allow-extra-fields/src/api/resources/noAuth/client/Client.ts @@ -49,6 +49,7 @@ export class NoAuth { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/allow-extra-fields/src/api/resources/noReqBody/client/Client.ts b/seed/ts-sdk/exhaustive/allow-extra-fields/src/api/resources/noReqBody/client/Client.ts index 38c66c24efb..f5cc75b22de 100644 --- a/seed/ts-sdk/exhaustive/allow-extra-fields/src/api/resources/noReqBody/client/Client.ts +++ b/seed/ts-sdk/exhaustive/allow-extra-fields/src/api/resources/noReqBody/client/Client.ts @@ -44,6 +44,7 @@ export class NoReqBody { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -99,6 +100,7 @@ export class NoReqBody { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/allow-extra-fields/src/api/resources/reqWithHeaders/client/Client.ts b/seed/ts-sdk/exhaustive/allow-extra-fields/src/api/resources/reqWithHeaders/client/Client.ts index 78379ca72f8..f8b47454b6e 100644 --- a/seed/ts-sdk/exhaustive/allow-extra-fields/src/api/resources/reqWithHeaders/client/Client.ts +++ b/seed/ts-sdk/exhaustive/allow-extra-fields/src/api/resources/reqWithHeaders/client/Client.ts @@ -51,6 +51,7 @@ export class ReqWithHeaders { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, "X-TEST-SERVICE-HEADER": xTestServiceHeader, diff --git a/seed/ts-sdk/exhaustive/allow-extra-fields/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/exhaustive/allow-extra-fields/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/exhaustive/allow-extra-fields/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/exhaustive/allow-extra-fields/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/exhaustive/bundle/package.json b/seed/ts-sdk/exhaustive/bundle/package.json index f0f0984ce3e..e5b54123e95 100644 --- a/seed/ts-sdk/exhaustive/bundle/package.json +++ b/seed/ts-sdk/exhaustive/bundle/package.json @@ -52,6 +52,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/exhaustive/bundle/src/api/resources/endpoints/resources/container/client/Client.ts b/seed/ts-sdk/exhaustive/bundle/src/api/resources/endpoints/resources/container/client/Client.ts index 53a61fc47cf..df30f4382ec 100644 --- a/seed/ts-sdk/exhaustive/bundle/src/api/resources/endpoints/resources/container/client/Client.ts +++ b/seed/ts-sdk/exhaustive/bundle/src/api/resources/endpoints/resources/container/client/Client.ts @@ -45,6 +45,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -104,6 +105,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -153,6 +155,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -212,6 +215,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -263,6 +267,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -321,6 +326,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -380,6 +386,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/bundle/src/api/resources/endpoints/resources/enum/client/Client.ts b/seed/ts-sdk/exhaustive/bundle/src/api/resources/endpoints/resources/enum/client/Client.ts index edd09ec1733..207b5f34d06 100644 --- a/seed/ts-sdk/exhaustive/bundle/src/api/resources/endpoints/resources/enum/client/Client.ts +++ b/seed/ts-sdk/exhaustive/bundle/src/api/resources/endpoints/resources/enum/client/Client.ts @@ -45,6 +45,7 @@ export class Enum { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/bundle/src/api/resources/endpoints/resources/httpMethods/client/Client.ts b/seed/ts-sdk/exhaustive/bundle/src/api/resources/endpoints/resources/httpMethods/client/Client.ts index 5e3f74f70f2..cff848c31ca 100644 --- a/seed/ts-sdk/exhaustive/bundle/src/api/resources/endpoints/resources/httpMethods/client/Client.ts +++ b/seed/ts-sdk/exhaustive/bundle/src/api/resources/endpoints/resources/httpMethods/client/Client.ts @@ -45,6 +45,7 @@ export class HttpMethods { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -93,6 +94,7 @@ export class HttpMethods { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -144,6 +146,7 @@ export class HttpMethods { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -209,6 +212,7 @@ export class HttpMethods { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -256,6 +260,7 @@ export class HttpMethods { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/bundle/src/api/resources/endpoints/resources/object/client/Client.ts b/seed/ts-sdk/exhaustive/bundle/src/api/resources/endpoints/resources/object/client/Client.ts index 466a8c3211f..8d99ae3dc0e 100644 --- a/seed/ts-sdk/exhaustive/bundle/src/api/resources/endpoints/resources/object/client/Client.ts +++ b/seed/ts-sdk/exhaustive/bundle/src/api/resources/endpoints/resources/object/client/Client.ts @@ -69,6 +69,7 @@ export class Object_ { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -126,6 +127,7 @@ export class Object_ { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -181,6 +183,7 @@ export class Object_ { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -255,6 +258,7 @@ export class Object_ { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -333,6 +337,7 @@ export class Object_ { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -409,6 +414,7 @@ export class Object_ { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/bundle/src/api/resources/endpoints/resources/params/client/Client.ts b/seed/ts-sdk/exhaustive/bundle/src/api/resources/endpoints/resources/params/client/Client.ts index 34544acc8e4..1a644c78eb9 100644 --- a/seed/ts-sdk/exhaustive/bundle/src/api/resources/endpoints/resources/params/client/Client.ts +++ b/seed/ts-sdk/exhaustive/bundle/src/api/resources/endpoints/resources/params/client/Client.ts @@ -50,6 +50,7 @@ export class Params { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -105,6 +106,7 @@ export class Params { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -166,6 +168,7 @@ export class Params { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -220,6 +223,7 @@ export class Params { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -269,6 +273,7 @@ export class Params { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/bundle/src/api/resources/endpoints/resources/primitive/client/Client.ts b/seed/ts-sdk/exhaustive/bundle/src/api/resources/endpoints/resources/primitive/client/Client.ts index 1fa5469e2d8..a58635e381f 100644 --- a/seed/ts-sdk/exhaustive/bundle/src/api/resources/endpoints/resources/primitive/client/Client.ts +++ b/seed/ts-sdk/exhaustive/bundle/src/api/resources/endpoints/resources/primitive/client/Client.ts @@ -45,6 +45,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -94,6 +95,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -143,6 +145,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -192,6 +195,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -241,6 +245,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -290,6 +295,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -339,6 +345,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -388,6 +395,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -437,6 +445,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/bundle/src/api/resources/endpoints/resources/union/client/Client.ts b/seed/ts-sdk/exhaustive/bundle/src/api/resources/endpoints/resources/union/client/Client.ts index 2ac7a6aa005..c53b0d5a7d8 100644 --- a/seed/ts-sdk/exhaustive/bundle/src/api/resources/endpoints/resources/union/client/Client.ts +++ b/seed/ts-sdk/exhaustive/bundle/src/api/resources/endpoints/resources/union/client/Client.ts @@ -48,6 +48,7 @@ export class Union { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/bundle/src/api/resources/inlinedRequests/client/Client.ts b/seed/ts-sdk/exhaustive/bundle/src/api/resources/inlinedRequests/client/Client.ts index 8168201dc45..7880b14697e 100644 --- a/seed/ts-sdk/exhaustive/bundle/src/api/resources/inlinedRequests/client/Client.ts +++ b/seed/ts-sdk/exhaustive/bundle/src/api/resources/inlinedRequests/client/Client.ts @@ -72,6 +72,7 @@ export class InlinedRequests { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/bundle/src/api/resources/noAuth/client/Client.ts b/seed/ts-sdk/exhaustive/bundle/src/api/resources/noAuth/client/Client.ts index 91cade48506..7c4758a5b62 100644 --- a/seed/ts-sdk/exhaustive/bundle/src/api/resources/noAuth/client/Client.ts +++ b/seed/ts-sdk/exhaustive/bundle/src/api/resources/noAuth/client/Client.ts @@ -49,6 +49,7 @@ export class NoAuth { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/bundle/src/api/resources/noReqBody/client/Client.ts b/seed/ts-sdk/exhaustive/bundle/src/api/resources/noReqBody/client/Client.ts index 1e5334b1462..51cc188128d 100644 --- a/seed/ts-sdk/exhaustive/bundle/src/api/resources/noReqBody/client/Client.ts +++ b/seed/ts-sdk/exhaustive/bundle/src/api/resources/noReqBody/client/Client.ts @@ -43,6 +43,7 @@ export class NoReqBody { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -87,6 +88,7 @@ export class NoReqBody { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/bundle/src/api/resources/reqWithHeaders/client/Client.ts b/seed/ts-sdk/exhaustive/bundle/src/api/resources/reqWithHeaders/client/Client.ts index 95c8f75fdef..f6f058a99f1 100644 --- a/seed/ts-sdk/exhaustive/bundle/src/api/resources/reqWithHeaders/client/Client.ts +++ b/seed/ts-sdk/exhaustive/bundle/src/api/resources/reqWithHeaders/client/Client.ts @@ -50,6 +50,7 @@ export class ReqWithHeaders { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, "X-TEST-SERVICE-HEADER": xTestServiceHeader, diff --git a/seed/ts-sdk/exhaustive/bundle/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/exhaustive/bundle/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/exhaustive/bundle/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/exhaustive/bundle/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/exhaustive/custom-package-json/package.json b/seed/ts-sdk/exhaustive/custom-package-json/package.json index 94dc0214345..1dc06e3d5c3 100644 --- a/seed/ts-sdk/exhaustive/custom-package-json/package.json +++ b/seed/ts-sdk/exhaustive/custom-package-json/package.json @@ -54,6 +54,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/exhaustive/custom-package-json/src/api/resources/endpoints/resources/container/client/Client.ts b/seed/ts-sdk/exhaustive/custom-package-json/src/api/resources/endpoints/resources/container/client/Client.ts index 53a61fc47cf..df30f4382ec 100644 --- a/seed/ts-sdk/exhaustive/custom-package-json/src/api/resources/endpoints/resources/container/client/Client.ts +++ b/seed/ts-sdk/exhaustive/custom-package-json/src/api/resources/endpoints/resources/container/client/Client.ts @@ -45,6 +45,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -104,6 +105,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -153,6 +155,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -212,6 +215,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -263,6 +267,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -321,6 +326,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -380,6 +386,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/custom-package-json/src/api/resources/endpoints/resources/enum/client/Client.ts b/seed/ts-sdk/exhaustive/custom-package-json/src/api/resources/endpoints/resources/enum/client/Client.ts index edd09ec1733..207b5f34d06 100644 --- a/seed/ts-sdk/exhaustive/custom-package-json/src/api/resources/endpoints/resources/enum/client/Client.ts +++ b/seed/ts-sdk/exhaustive/custom-package-json/src/api/resources/endpoints/resources/enum/client/Client.ts @@ -45,6 +45,7 @@ export class Enum { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/custom-package-json/src/api/resources/endpoints/resources/httpMethods/client/Client.ts b/seed/ts-sdk/exhaustive/custom-package-json/src/api/resources/endpoints/resources/httpMethods/client/Client.ts index 5e3f74f70f2..cff848c31ca 100644 --- a/seed/ts-sdk/exhaustive/custom-package-json/src/api/resources/endpoints/resources/httpMethods/client/Client.ts +++ b/seed/ts-sdk/exhaustive/custom-package-json/src/api/resources/endpoints/resources/httpMethods/client/Client.ts @@ -45,6 +45,7 @@ export class HttpMethods { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -93,6 +94,7 @@ export class HttpMethods { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -144,6 +146,7 @@ export class HttpMethods { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -209,6 +212,7 @@ export class HttpMethods { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -256,6 +260,7 @@ export class HttpMethods { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/custom-package-json/src/api/resources/endpoints/resources/object/client/Client.ts b/seed/ts-sdk/exhaustive/custom-package-json/src/api/resources/endpoints/resources/object/client/Client.ts index 466a8c3211f..8d99ae3dc0e 100644 --- a/seed/ts-sdk/exhaustive/custom-package-json/src/api/resources/endpoints/resources/object/client/Client.ts +++ b/seed/ts-sdk/exhaustive/custom-package-json/src/api/resources/endpoints/resources/object/client/Client.ts @@ -69,6 +69,7 @@ export class Object_ { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -126,6 +127,7 @@ export class Object_ { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -181,6 +183,7 @@ export class Object_ { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -255,6 +258,7 @@ export class Object_ { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -333,6 +337,7 @@ export class Object_ { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -409,6 +414,7 @@ export class Object_ { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/custom-package-json/src/api/resources/endpoints/resources/params/client/Client.ts b/seed/ts-sdk/exhaustive/custom-package-json/src/api/resources/endpoints/resources/params/client/Client.ts index 34544acc8e4..1a644c78eb9 100644 --- a/seed/ts-sdk/exhaustive/custom-package-json/src/api/resources/endpoints/resources/params/client/Client.ts +++ b/seed/ts-sdk/exhaustive/custom-package-json/src/api/resources/endpoints/resources/params/client/Client.ts @@ -50,6 +50,7 @@ export class Params { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -105,6 +106,7 @@ export class Params { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -166,6 +168,7 @@ export class Params { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -220,6 +223,7 @@ export class Params { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -269,6 +273,7 @@ export class Params { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/custom-package-json/src/api/resources/endpoints/resources/primitive/client/Client.ts b/seed/ts-sdk/exhaustive/custom-package-json/src/api/resources/endpoints/resources/primitive/client/Client.ts index 1fa5469e2d8..a58635e381f 100644 --- a/seed/ts-sdk/exhaustive/custom-package-json/src/api/resources/endpoints/resources/primitive/client/Client.ts +++ b/seed/ts-sdk/exhaustive/custom-package-json/src/api/resources/endpoints/resources/primitive/client/Client.ts @@ -45,6 +45,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -94,6 +95,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -143,6 +145,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -192,6 +195,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -241,6 +245,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -290,6 +295,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -339,6 +345,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -388,6 +395,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -437,6 +445,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/custom-package-json/src/api/resources/endpoints/resources/union/client/Client.ts b/seed/ts-sdk/exhaustive/custom-package-json/src/api/resources/endpoints/resources/union/client/Client.ts index 2ac7a6aa005..c53b0d5a7d8 100644 --- a/seed/ts-sdk/exhaustive/custom-package-json/src/api/resources/endpoints/resources/union/client/Client.ts +++ b/seed/ts-sdk/exhaustive/custom-package-json/src/api/resources/endpoints/resources/union/client/Client.ts @@ -48,6 +48,7 @@ export class Union { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/custom-package-json/src/api/resources/inlinedRequests/client/Client.ts b/seed/ts-sdk/exhaustive/custom-package-json/src/api/resources/inlinedRequests/client/Client.ts index 8168201dc45..7880b14697e 100644 --- a/seed/ts-sdk/exhaustive/custom-package-json/src/api/resources/inlinedRequests/client/Client.ts +++ b/seed/ts-sdk/exhaustive/custom-package-json/src/api/resources/inlinedRequests/client/Client.ts @@ -72,6 +72,7 @@ export class InlinedRequests { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/custom-package-json/src/api/resources/noAuth/client/Client.ts b/seed/ts-sdk/exhaustive/custom-package-json/src/api/resources/noAuth/client/Client.ts index 91cade48506..7c4758a5b62 100644 --- a/seed/ts-sdk/exhaustive/custom-package-json/src/api/resources/noAuth/client/Client.ts +++ b/seed/ts-sdk/exhaustive/custom-package-json/src/api/resources/noAuth/client/Client.ts @@ -49,6 +49,7 @@ export class NoAuth { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/custom-package-json/src/api/resources/noReqBody/client/Client.ts b/seed/ts-sdk/exhaustive/custom-package-json/src/api/resources/noReqBody/client/Client.ts index 1e5334b1462..51cc188128d 100644 --- a/seed/ts-sdk/exhaustive/custom-package-json/src/api/resources/noReqBody/client/Client.ts +++ b/seed/ts-sdk/exhaustive/custom-package-json/src/api/resources/noReqBody/client/Client.ts @@ -43,6 +43,7 @@ export class NoReqBody { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -87,6 +88,7 @@ export class NoReqBody { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/custom-package-json/src/api/resources/reqWithHeaders/client/Client.ts b/seed/ts-sdk/exhaustive/custom-package-json/src/api/resources/reqWithHeaders/client/Client.ts index 95c8f75fdef..f6f058a99f1 100644 --- a/seed/ts-sdk/exhaustive/custom-package-json/src/api/resources/reqWithHeaders/client/Client.ts +++ b/seed/ts-sdk/exhaustive/custom-package-json/src/api/resources/reqWithHeaders/client/Client.ts @@ -50,6 +50,7 @@ export class ReqWithHeaders { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, "X-TEST-SERVICE-HEADER": xTestServiceHeader, diff --git a/seed/ts-sdk/exhaustive/custom-package-json/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/exhaustive/custom-package-json/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/exhaustive/custom-package-json/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/exhaustive/custom-package-json/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/exhaustive/dev-dependencies/package.json b/seed/ts-sdk/exhaustive/dev-dependencies/package.json index a6d27a94711..ebd259b391f 100644 --- a/seed/ts-sdk/exhaustive/dev-dependencies/package.json +++ b/seed/ts-sdk/exhaustive/dev-dependencies/package.json @@ -60,6 +60,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "^29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/exhaustive/dev-dependencies/src/api/resources/endpoints/resources/container/client/Client.ts b/seed/ts-sdk/exhaustive/dev-dependencies/src/api/resources/endpoints/resources/container/client/Client.ts index 53a61fc47cf..df30f4382ec 100644 --- a/seed/ts-sdk/exhaustive/dev-dependencies/src/api/resources/endpoints/resources/container/client/Client.ts +++ b/seed/ts-sdk/exhaustive/dev-dependencies/src/api/resources/endpoints/resources/container/client/Client.ts @@ -45,6 +45,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -104,6 +105,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -153,6 +155,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -212,6 +215,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -263,6 +267,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -321,6 +326,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -380,6 +386,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/dev-dependencies/src/api/resources/endpoints/resources/enum/client/Client.ts b/seed/ts-sdk/exhaustive/dev-dependencies/src/api/resources/endpoints/resources/enum/client/Client.ts index edd09ec1733..207b5f34d06 100644 --- a/seed/ts-sdk/exhaustive/dev-dependencies/src/api/resources/endpoints/resources/enum/client/Client.ts +++ b/seed/ts-sdk/exhaustive/dev-dependencies/src/api/resources/endpoints/resources/enum/client/Client.ts @@ -45,6 +45,7 @@ export class Enum { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/dev-dependencies/src/api/resources/endpoints/resources/httpMethods/client/Client.ts b/seed/ts-sdk/exhaustive/dev-dependencies/src/api/resources/endpoints/resources/httpMethods/client/Client.ts index 5e3f74f70f2..cff848c31ca 100644 --- a/seed/ts-sdk/exhaustive/dev-dependencies/src/api/resources/endpoints/resources/httpMethods/client/Client.ts +++ b/seed/ts-sdk/exhaustive/dev-dependencies/src/api/resources/endpoints/resources/httpMethods/client/Client.ts @@ -45,6 +45,7 @@ export class HttpMethods { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -93,6 +94,7 @@ export class HttpMethods { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -144,6 +146,7 @@ export class HttpMethods { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -209,6 +212,7 @@ export class HttpMethods { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -256,6 +260,7 @@ export class HttpMethods { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/dev-dependencies/src/api/resources/endpoints/resources/object/client/Client.ts b/seed/ts-sdk/exhaustive/dev-dependencies/src/api/resources/endpoints/resources/object/client/Client.ts index 466a8c3211f..8d99ae3dc0e 100644 --- a/seed/ts-sdk/exhaustive/dev-dependencies/src/api/resources/endpoints/resources/object/client/Client.ts +++ b/seed/ts-sdk/exhaustive/dev-dependencies/src/api/resources/endpoints/resources/object/client/Client.ts @@ -69,6 +69,7 @@ export class Object_ { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -126,6 +127,7 @@ export class Object_ { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -181,6 +183,7 @@ export class Object_ { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -255,6 +258,7 @@ export class Object_ { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -333,6 +337,7 @@ export class Object_ { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -409,6 +414,7 @@ export class Object_ { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/dev-dependencies/src/api/resources/endpoints/resources/params/client/Client.ts b/seed/ts-sdk/exhaustive/dev-dependencies/src/api/resources/endpoints/resources/params/client/Client.ts index 34544acc8e4..1a644c78eb9 100644 --- a/seed/ts-sdk/exhaustive/dev-dependencies/src/api/resources/endpoints/resources/params/client/Client.ts +++ b/seed/ts-sdk/exhaustive/dev-dependencies/src/api/resources/endpoints/resources/params/client/Client.ts @@ -50,6 +50,7 @@ export class Params { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -105,6 +106,7 @@ export class Params { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -166,6 +168,7 @@ export class Params { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -220,6 +223,7 @@ export class Params { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -269,6 +273,7 @@ export class Params { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/dev-dependencies/src/api/resources/endpoints/resources/primitive/client/Client.ts b/seed/ts-sdk/exhaustive/dev-dependencies/src/api/resources/endpoints/resources/primitive/client/Client.ts index 1fa5469e2d8..a58635e381f 100644 --- a/seed/ts-sdk/exhaustive/dev-dependencies/src/api/resources/endpoints/resources/primitive/client/Client.ts +++ b/seed/ts-sdk/exhaustive/dev-dependencies/src/api/resources/endpoints/resources/primitive/client/Client.ts @@ -45,6 +45,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -94,6 +95,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -143,6 +145,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -192,6 +195,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -241,6 +245,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -290,6 +295,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -339,6 +345,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -388,6 +395,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -437,6 +445,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/dev-dependencies/src/api/resources/endpoints/resources/union/client/Client.ts b/seed/ts-sdk/exhaustive/dev-dependencies/src/api/resources/endpoints/resources/union/client/Client.ts index 2ac7a6aa005..c53b0d5a7d8 100644 --- a/seed/ts-sdk/exhaustive/dev-dependencies/src/api/resources/endpoints/resources/union/client/Client.ts +++ b/seed/ts-sdk/exhaustive/dev-dependencies/src/api/resources/endpoints/resources/union/client/Client.ts @@ -48,6 +48,7 @@ export class Union { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/dev-dependencies/src/api/resources/inlinedRequests/client/Client.ts b/seed/ts-sdk/exhaustive/dev-dependencies/src/api/resources/inlinedRequests/client/Client.ts index 8168201dc45..7880b14697e 100644 --- a/seed/ts-sdk/exhaustive/dev-dependencies/src/api/resources/inlinedRequests/client/Client.ts +++ b/seed/ts-sdk/exhaustive/dev-dependencies/src/api/resources/inlinedRequests/client/Client.ts @@ -72,6 +72,7 @@ export class InlinedRequests { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/dev-dependencies/src/api/resources/noAuth/client/Client.ts b/seed/ts-sdk/exhaustive/dev-dependencies/src/api/resources/noAuth/client/Client.ts index 91cade48506..7c4758a5b62 100644 --- a/seed/ts-sdk/exhaustive/dev-dependencies/src/api/resources/noAuth/client/Client.ts +++ b/seed/ts-sdk/exhaustive/dev-dependencies/src/api/resources/noAuth/client/Client.ts @@ -49,6 +49,7 @@ export class NoAuth { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/dev-dependencies/src/api/resources/noReqBody/client/Client.ts b/seed/ts-sdk/exhaustive/dev-dependencies/src/api/resources/noReqBody/client/Client.ts index 1e5334b1462..51cc188128d 100644 --- a/seed/ts-sdk/exhaustive/dev-dependencies/src/api/resources/noReqBody/client/Client.ts +++ b/seed/ts-sdk/exhaustive/dev-dependencies/src/api/resources/noReqBody/client/Client.ts @@ -43,6 +43,7 @@ export class NoReqBody { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -87,6 +88,7 @@ export class NoReqBody { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/dev-dependencies/src/api/resources/reqWithHeaders/client/Client.ts b/seed/ts-sdk/exhaustive/dev-dependencies/src/api/resources/reqWithHeaders/client/Client.ts index 95c8f75fdef..f6f058a99f1 100644 --- a/seed/ts-sdk/exhaustive/dev-dependencies/src/api/resources/reqWithHeaders/client/Client.ts +++ b/seed/ts-sdk/exhaustive/dev-dependencies/src/api/resources/reqWithHeaders/client/Client.ts @@ -50,6 +50,7 @@ export class ReqWithHeaders { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, "X-TEST-SERVICE-HEADER": xTestServiceHeader, diff --git a/seed/ts-sdk/exhaustive/dev-dependencies/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/exhaustive/dev-dependencies/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/exhaustive/dev-dependencies/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/exhaustive/dev-dependencies/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/exhaustive/jsr/package.json b/seed/ts-sdk/exhaustive/jsr/package.json index ca0d197313d..27ead133beb 100644 --- a/seed/ts-sdk/exhaustive/jsr/package.json +++ b/seed/ts-sdk/exhaustive/jsr/package.json @@ -23,6 +23,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/exhaustive/jsr/src/api/resources/endpoints/resources/container/client/Client.ts b/seed/ts-sdk/exhaustive/jsr/src/api/resources/endpoints/resources/container/client/Client.ts index 3b8622100d5..ae42594f459 100644 --- a/seed/ts-sdk/exhaustive/jsr/src/api/resources/endpoints/resources/container/client/Client.ts +++ b/seed/ts-sdk/exhaustive/jsr/src/api/resources/endpoints/resources/container/client/Client.ts @@ -46,6 +46,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -110,6 +111,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -172,6 +174,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -236,6 +239,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -300,6 +304,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -366,6 +371,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -430,6 +436,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/jsr/src/api/resources/endpoints/resources/enum/client/Client.ts b/seed/ts-sdk/exhaustive/jsr/src/api/resources/endpoints/resources/enum/client/Client.ts index a90778e1558..ef3cc786b00 100644 --- a/seed/ts-sdk/exhaustive/jsr/src/api/resources/endpoints/resources/enum/client/Client.ts +++ b/seed/ts-sdk/exhaustive/jsr/src/api/resources/endpoints/resources/enum/client/Client.ts @@ -46,6 +46,7 @@ export class Enum { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/jsr/src/api/resources/endpoints/resources/httpMethods/client/Client.ts b/seed/ts-sdk/exhaustive/jsr/src/api/resources/endpoints/resources/httpMethods/client/Client.ts index a8a61166980..35bacb8c4f8 100644 --- a/seed/ts-sdk/exhaustive/jsr/src/api/resources/endpoints/resources/httpMethods/client/Client.ts +++ b/seed/ts-sdk/exhaustive/jsr/src/api/resources/endpoints/resources/httpMethods/client/Client.ts @@ -43,6 +43,7 @@ export class HttpMethods { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -104,6 +105,7 @@ export class HttpMethods { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -168,6 +170,7 @@ export class HttpMethods { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -246,6 +249,7 @@ export class HttpMethods { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -303,6 +307,7 @@ export class HttpMethods { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/jsr/src/api/resources/endpoints/resources/object/client/Client.ts b/seed/ts-sdk/exhaustive/jsr/src/api/resources/endpoints/resources/object/client/Client.ts index 2ee970c0dae..215e0c9fa73 100644 --- a/seed/ts-sdk/exhaustive/jsr/src/api/resources/endpoints/resources/object/client/Client.ts +++ b/seed/ts-sdk/exhaustive/jsr/src/api/resources/endpoints/resources/object/client/Client.ts @@ -65,6 +65,7 @@ export class Object_ { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -130,6 +131,7 @@ export class Object_ { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -196,6 +198,7 @@ export class Object_ { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -278,6 +281,7 @@ export class Object_ { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -364,6 +368,7 @@ export class Object_ { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -448,6 +453,7 @@ export class Object_ { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/jsr/src/api/resources/endpoints/resources/params/client/Client.ts b/seed/ts-sdk/exhaustive/jsr/src/api/resources/endpoints/resources/params/client/Client.ts index 4b1d2093401..9a371594240 100644 --- a/seed/ts-sdk/exhaustive/jsr/src/api/resources/endpoints/resources/params/client/Client.ts +++ b/seed/ts-sdk/exhaustive/jsr/src/api/resources/endpoints/resources/params/client/Client.ts @@ -48,6 +48,7 @@ export class Params { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -116,6 +117,7 @@ export class Params { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -190,6 +192,7 @@ export class Params { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -257,6 +260,7 @@ export class Params { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -319,6 +323,7 @@ export class Params { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/jsr/src/api/resources/endpoints/resources/primitive/client/Client.ts b/seed/ts-sdk/exhaustive/jsr/src/api/resources/endpoints/resources/primitive/client/Client.ts index cd5fe688864..37ddb05f94f 100644 --- a/seed/ts-sdk/exhaustive/jsr/src/api/resources/endpoints/resources/primitive/client/Client.ts +++ b/seed/ts-sdk/exhaustive/jsr/src/api/resources/endpoints/resources/primitive/client/Client.ts @@ -42,6 +42,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -101,6 +102,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -160,6 +162,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -219,6 +222,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -278,6 +282,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -337,6 +342,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -396,6 +402,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -455,6 +462,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -514,6 +522,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/jsr/src/api/resources/endpoints/resources/union/client/Client.ts b/seed/ts-sdk/exhaustive/jsr/src/api/resources/endpoints/resources/union/client/Client.ts index e7a3bcea99c..145d89bf8c5 100644 --- a/seed/ts-sdk/exhaustive/jsr/src/api/resources/endpoints/resources/union/client/Client.ts +++ b/seed/ts-sdk/exhaustive/jsr/src/api/resources/endpoints/resources/union/client/Client.ts @@ -50,6 +50,7 @@ export class Union { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/jsr/src/api/resources/inlinedRequests/client/Client.ts b/seed/ts-sdk/exhaustive/jsr/src/api/resources/inlinedRequests/client/Client.ts index 413a1debbe8..2a852a6df22 100644 --- a/seed/ts-sdk/exhaustive/jsr/src/api/resources/inlinedRequests/client/Client.ts +++ b/seed/ts-sdk/exhaustive/jsr/src/api/resources/inlinedRequests/client/Client.ts @@ -70,6 +70,7 @@ export class InlinedRequests { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/jsr/src/api/resources/noAuth/client/Client.ts b/seed/ts-sdk/exhaustive/jsr/src/api/resources/noAuth/client/Client.ts index 9cadcbff1b5..0d9f7cee142 100644 --- a/seed/ts-sdk/exhaustive/jsr/src/api/resources/noAuth/client/Client.ts +++ b/seed/ts-sdk/exhaustive/jsr/src/api/resources/noAuth/client/Client.ts @@ -49,6 +49,7 @@ export class NoAuth { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/jsr/src/api/resources/noReqBody/client/Client.ts b/seed/ts-sdk/exhaustive/jsr/src/api/resources/noReqBody/client/Client.ts index 38c66c24efb..f5cc75b22de 100644 --- a/seed/ts-sdk/exhaustive/jsr/src/api/resources/noReqBody/client/Client.ts +++ b/seed/ts-sdk/exhaustive/jsr/src/api/resources/noReqBody/client/Client.ts @@ -44,6 +44,7 @@ export class NoReqBody { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -99,6 +100,7 @@ export class NoReqBody { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/jsr/src/api/resources/reqWithHeaders/client/Client.ts b/seed/ts-sdk/exhaustive/jsr/src/api/resources/reqWithHeaders/client/Client.ts index 8fed42e0ca9..d3aa426c694 100644 --- a/seed/ts-sdk/exhaustive/jsr/src/api/resources/reqWithHeaders/client/Client.ts +++ b/seed/ts-sdk/exhaustive/jsr/src/api/resources/reqWithHeaders/client/Client.ts @@ -51,6 +51,7 @@ export class ReqWithHeaders { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, "X-TEST-SERVICE-HEADER": xTestServiceHeader, diff --git a/seed/ts-sdk/exhaustive/jsr/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/exhaustive/jsr/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/exhaustive/jsr/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/exhaustive/jsr/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/exhaustive/no-custom-config/package.json b/seed/ts-sdk/exhaustive/no-custom-config/package.json index ca0d197313d..27ead133beb 100644 --- a/seed/ts-sdk/exhaustive/no-custom-config/package.json +++ b/seed/ts-sdk/exhaustive/no-custom-config/package.json @@ -23,6 +23,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/exhaustive/no-custom-config/src/api/resources/endpoints/resources/container/client/Client.ts b/seed/ts-sdk/exhaustive/no-custom-config/src/api/resources/endpoints/resources/container/client/Client.ts index 3b8622100d5..ae42594f459 100644 --- a/seed/ts-sdk/exhaustive/no-custom-config/src/api/resources/endpoints/resources/container/client/Client.ts +++ b/seed/ts-sdk/exhaustive/no-custom-config/src/api/resources/endpoints/resources/container/client/Client.ts @@ -46,6 +46,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -110,6 +111,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -172,6 +174,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -236,6 +239,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -300,6 +304,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -366,6 +371,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -430,6 +436,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/no-custom-config/src/api/resources/endpoints/resources/enum/client/Client.ts b/seed/ts-sdk/exhaustive/no-custom-config/src/api/resources/endpoints/resources/enum/client/Client.ts index a90778e1558..ef3cc786b00 100644 --- a/seed/ts-sdk/exhaustive/no-custom-config/src/api/resources/endpoints/resources/enum/client/Client.ts +++ b/seed/ts-sdk/exhaustive/no-custom-config/src/api/resources/endpoints/resources/enum/client/Client.ts @@ -46,6 +46,7 @@ export class Enum { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/no-custom-config/src/api/resources/endpoints/resources/httpMethods/client/Client.ts b/seed/ts-sdk/exhaustive/no-custom-config/src/api/resources/endpoints/resources/httpMethods/client/Client.ts index a8a61166980..35bacb8c4f8 100644 --- a/seed/ts-sdk/exhaustive/no-custom-config/src/api/resources/endpoints/resources/httpMethods/client/Client.ts +++ b/seed/ts-sdk/exhaustive/no-custom-config/src/api/resources/endpoints/resources/httpMethods/client/Client.ts @@ -43,6 +43,7 @@ export class HttpMethods { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -104,6 +105,7 @@ export class HttpMethods { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -168,6 +170,7 @@ export class HttpMethods { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -246,6 +249,7 @@ export class HttpMethods { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -303,6 +307,7 @@ export class HttpMethods { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/no-custom-config/src/api/resources/endpoints/resources/object/client/Client.ts b/seed/ts-sdk/exhaustive/no-custom-config/src/api/resources/endpoints/resources/object/client/Client.ts index 2ee970c0dae..215e0c9fa73 100644 --- a/seed/ts-sdk/exhaustive/no-custom-config/src/api/resources/endpoints/resources/object/client/Client.ts +++ b/seed/ts-sdk/exhaustive/no-custom-config/src/api/resources/endpoints/resources/object/client/Client.ts @@ -65,6 +65,7 @@ export class Object_ { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -130,6 +131,7 @@ export class Object_ { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -196,6 +198,7 @@ export class Object_ { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -278,6 +281,7 @@ export class Object_ { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -364,6 +368,7 @@ export class Object_ { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -448,6 +453,7 @@ export class Object_ { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/no-custom-config/src/api/resources/endpoints/resources/params/client/Client.ts b/seed/ts-sdk/exhaustive/no-custom-config/src/api/resources/endpoints/resources/params/client/Client.ts index 4b1d2093401..9a371594240 100644 --- a/seed/ts-sdk/exhaustive/no-custom-config/src/api/resources/endpoints/resources/params/client/Client.ts +++ b/seed/ts-sdk/exhaustive/no-custom-config/src/api/resources/endpoints/resources/params/client/Client.ts @@ -48,6 +48,7 @@ export class Params { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -116,6 +117,7 @@ export class Params { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -190,6 +192,7 @@ export class Params { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -257,6 +260,7 @@ export class Params { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -319,6 +323,7 @@ export class Params { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/no-custom-config/src/api/resources/endpoints/resources/primitive/client/Client.ts b/seed/ts-sdk/exhaustive/no-custom-config/src/api/resources/endpoints/resources/primitive/client/Client.ts index cd5fe688864..37ddb05f94f 100644 --- a/seed/ts-sdk/exhaustive/no-custom-config/src/api/resources/endpoints/resources/primitive/client/Client.ts +++ b/seed/ts-sdk/exhaustive/no-custom-config/src/api/resources/endpoints/resources/primitive/client/Client.ts @@ -42,6 +42,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -101,6 +102,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -160,6 +162,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -219,6 +222,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -278,6 +282,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -337,6 +342,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -396,6 +402,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -455,6 +462,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -514,6 +522,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/no-custom-config/src/api/resources/endpoints/resources/union/client/Client.ts b/seed/ts-sdk/exhaustive/no-custom-config/src/api/resources/endpoints/resources/union/client/Client.ts index e7a3bcea99c..145d89bf8c5 100644 --- a/seed/ts-sdk/exhaustive/no-custom-config/src/api/resources/endpoints/resources/union/client/Client.ts +++ b/seed/ts-sdk/exhaustive/no-custom-config/src/api/resources/endpoints/resources/union/client/Client.ts @@ -50,6 +50,7 @@ export class Union { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/no-custom-config/src/api/resources/inlinedRequests/client/Client.ts b/seed/ts-sdk/exhaustive/no-custom-config/src/api/resources/inlinedRequests/client/Client.ts index 413a1debbe8..2a852a6df22 100644 --- a/seed/ts-sdk/exhaustive/no-custom-config/src/api/resources/inlinedRequests/client/Client.ts +++ b/seed/ts-sdk/exhaustive/no-custom-config/src/api/resources/inlinedRequests/client/Client.ts @@ -70,6 +70,7 @@ export class InlinedRequests { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/no-custom-config/src/api/resources/noAuth/client/Client.ts b/seed/ts-sdk/exhaustive/no-custom-config/src/api/resources/noAuth/client/Client.ts index 9cadcbff1b5..0d9f7cee142 100644 --- a/seed/ts-sdk/exhaustive/no-custom-config/src/api/resources/noAuth/client/Client.ts +++ b/seed/ts-sdk/exhaustive/no-custom-config/src/api/resources/noAuth/client/Client.ts @@ -49,6 +49,7 @@ export class NoAuth { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/no-custom-config/src/api/resources/noReqBody/client/Client.ts b/seed/ts-sdk/exhaustive/no-custom-config/src/api/resources/noReqBody/client/Client.ts index 38c66c24efb..f5cc75b22de 100644 --- a/seed/ts-sdk/exhaustive/no-custom-config/src/api/resources/noReqBody/client/Client.ts +++ b/seed/ts-sdk/exhaustive/no-custom-config/src/api/resources/noReqBody/client/Client.ts @@ -44,6 +44,7 @@ export class NoReqBody { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -99,6 +100,7 @@ export class NoReqBody { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/no-custom-config/src/api/resources/reqWithHeaders/client/Client.ts b/seed/ts-sdk/exhaustive/no-custom-config/src/api/resources/reqWithHeaders/client/Client.ts index 8fed42e0ca9..d3aa426c694 100644 --- a/seed/ts-sdk/exhaustive/no-custom-config/src/api/resources/reqWithHeaders/client/Client.ts +++ b/seed/ts-sdk/exhaustive/no-custom-config/src/api/resources/reqWithHeaders/client/Client.ts @@ -51,6 +51,7 @@ export class ReqWithHeaders { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, "X-TEST-SERVICE-HEADER": xTestServiceHeader, diff --git a/seed/ts-sdk/exhaustive/no-custom-config/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/exhaustive/no-custom-config/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/exhaustive/no-custom-config/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/exhaustive/no-custom-config/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/exhaustive/retain-original-casing/package.json b/seed/ts-sdk/exhaustive/retain-original-casing/package.json index ca0d197313d..27ead133beb 100644 --- a/seed/ts-sdk/exhaustive/retain-original-casing/package.json +++ b/seed/ts-sdk/exhaustive/retain-original-casing/package.json @@ -23,6 +23,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/exhaustive/retain-original-casing/src/api/resources/endpoints/resources/container/client/Client.ts b/seed/ts-sdk/exhaustive/retain-original-casing/src/api/resources/endpoints/resources/container/client/Client.ts index 3b8622100d5..ae42594f459 100644 --- a/seed/ts-sdk/exhaustive/retain-original-casing/src/api/resources/endpoints/resources/container/client/Client.ts +++ b/seed/ts-sdk/exhaustive/retain-original-casing/src/api/resources/endpoints/resources/container/client/Client.ts @@ -46,6 +46,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -110,6 +111,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -172,6 +174,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -236,6 +239,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -300,6 +304,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -366,6 +371,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -430,6 +436,7 @@ export class Container { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/retain-original-casing/src/api/resources/endpoints/resources/enum/client/Client.ts b/seed/ts-sdk/exhaustive/retain-original-casing/src/api/resources/endpoints/resources/enum/client/Client.ts index a90778e1558..ef3cc786b00 100644 --- a/seed/ts-sdk/exhaustive/retain-original-casing/src/api/resources/endpoints/resources/enum/client/Client.ts +++ b/seed/ts-sdk/exhaustive/retain-original-casing/src/api/resources/endpoints/resources/enum/client/Client.ts @@ -46,6 +46,7 @@ export class Enum { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/retain-original-casing/src/api/resources/endpoints/resources/httpMethods/client/Client.ts b/seed/ts-sdk/exhaustive/retain-original-casing/src/api/resources/endpoints/resources/httpMethods/client/Client.ts index a8a61166980..35bacb8c4f8 100644 --- a/seed/ts-sdk/exhaustive/retain-original-casing/src/api/resources/endpoints/resources/httpMethods/client/Client.ts +++ b/seed/ts-sdk/exhaustive/retain-original-casing/src/api/resources/endpoints/resources/httpMethods/client/Client.ts @@ -43,6 +43,7 @@ export class HttpMethods { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -104,6 +105,7 @@ export class HttpMethods { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -168,6 +170,7 @@ export class HttpMethods { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -246,6 +249,7 @@ export class HttpMethods { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -303,6 +307,7 @@ export class HttpMethods { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/retain-original-casing/src/api/resources/endpoints/resources/object/client/Client.ts b/seed/ts-sdk/exhaustive/retain-original-casing/src/api/resources/endpoints/resources/object/client/Client.ts index 893f7b7e8a3..8d9bb5e2f66 100644 --- a/seed/ts-sdk/exhaustive/retain-original-casing/src/api/resources/endpoints/resources/object/client/Client.ts +++ b/seed/ts-sdk/exhaustive/retain-original-casing/src/api/resources/endpoints/resources/object/client/Client.ts @@ -65,6 +65,7 @@ export class Object_ { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -130,6 +131,7 @@ export class Object_ { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -196,6 +198,7 @@ export class Object_ { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -278,6 +281,7 @@ export class Object_ { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -364,6 +368,7 @@ export class Object_ { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -448,6 +453,7 @@ export class Object_ { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/retain-original-casing/src/api/resources/endpoints/resources/params/client/Client.ts b/seed/ts-sdk/exhaustive/retain-original-casing/src/api/resources/endpoints/resources/params/client/Client.ts index 4b1d2093401..9a371594240 100644 --- a/seed/ts-sdk/exhaustive/retain-original-casing/src/api/resources/endpoints/resources/params/client/Client.ts +++ b/seed/ts-sdk/exhaustive/retain-original-casing/src/api/resources/endpoints/resources/params/client/Client.ts @@ -48,6 +48,7 @@ export class Params { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -116,6 +117,7 @@ export class Params { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -190,6 +192,7 @@ export class Params { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -257,6 +260,7 @@ export class Params { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -319,6 +323,7 @@ export class Params { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/retain-original-casing/src/api/resources/endpoints/resources/primitive/client/Client.ts b/seed/ts-sdk/exhaustive/retain-original-casing/src/api/resources/endpoints/resources/primitive/client/Client.ts index cd5fe688864..37ddb05f94f 100644 --- a/seed/ts-sdk/exhaustive/retain-original-casing/src/api/resources/endpoints/resources/primitive/client/Client.ts +++ b/seed/ts-sdk/exhaustive/retain-original-casing/src/api/resources/endpoints/resources/primitive/client/Client.ts @@ -42,6 +42,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -101,6 +102,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -160,6 +162,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -219,6 +222,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -278,6 +282,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -337,6 +342,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -396,6 +402,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -455,6 +462,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -514,6 +522,7 @@ export class Primitive { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/retain-original-casing/src/api/resources/endpoints/resources/union/client/Client.ts b/seed/ts-sdk/exhaustive/retain-original-casing/src/api/resources/endpoints/resources/union/client/Client.ts index e7a3bcea99c..145d89bf8c5 100644 --- a/seed/ts-sdk/exhaustive/retain-original-casing/src/api/resources/endpoints/resources/union/client/Client.ts +++ b/seed/ts-sdk/exhaustive/retain-original-casing/src/api/resources/endpoints/resources/union/client/Client.ts @@ -50,6 +50,7 @@ export class Union { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/retain-original-casing/src/api/resources/inlinedRequests/client/Client.ts b/seed/ts-sdk/exhaustive/retain-original-casing/src/api/resources/inlinedRequests/client/Client.ts index 76f3fee209a..0156e1c54c1 100644 --- a/seed/ts-sdk/exhaustive/retain-original-casing/src/api/resources/inlinedRequests/client/Client.ts +++ b/seed/ts-sdk/exhaustive/retain-original-casing/src/api/resources/inlinedRequests/client/Client.ts @@ -70,6 +70,7 @@ export class InlinedRequests { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/retain-original-casing/src/api/resources/noAuth/client/Client.ts b/seed/ts-sdk/exhaustive/retain-original-casing/src/api/resources/noAuth/client/Client.ts index 9cadcbff1b5..0d9f7cee142 100644 --- a/seed/ts-sdk/exhaustive/retain-original-casing/src/api/resources/noAuth/client/Client.ts +++ b/seed/ts-sdk/exhaustive/retain-original-casing/src/api/resources/noAuth/client/Client.ts @@ -49,6 +49,7 @@ export class NoAuth { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/retain-original-casing/src/api/resources/noReqBody/client/Client.ts b/seed/ts-sdk/exhaustive/retain-original-casing/src/api/resources/noReqBody/client/Client.ts index 38c66c24efb..f5cc75b22de 100644 --- a/seed/ts-sdk/exhaustive/retain-original-casing/src/api/resources/noReqBody/client/Client.ts +++ b/seed/ts-sdk/exhaustive/retain-original-casing/src/api/resources/noReqBody/client/Client.ts @@ -44,6 +44,7 @@ export class NoReqBody { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -99,6 +100,7 @@ export class NoReqBody { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/exhaustive/retain-original-casing/src/api/resources/reqWithHeaders/client/Client.ts b/seed/ts-sdk/exhaustive/retain-original-casing/src/api/resources/reqWithHeaders/client/Client.ts index e549be5020b..d08112059c5 100644 --- a/seed/ts-sdk/exhaustive/retain-original-casing/src/api/resources/reqWithHeaders/client/Client.ts +++ b/seed/ts-sdk/exhaustive/retain-original-casing/src/api/resources/reqWithHeaders/client/Client.ts @@ -55,6 +55,7 @@ export class ReqWithHeaders { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/exhaustive", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/exhaustive/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, "X-TEST-SERVICE-HEADER": xTestServiceHeader, diff --git a/seed/ts-sdk/exhaustive/retain-original-casing/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/exhaustive/retain-original-casing/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/exhaustive/retain-original-casing/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/exhaustive/retain-original-casing/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/extends/package.json b/seed/ts-sdk/extends/package.json index da6f9e5cbf5..a26d67469b7 100644 --- a/seed/ts-sdk/extends/package.json +++ b/seed/ts-sdk/extends/package.json @@ -22,6 +22,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/extends/src/Client.ts b/seed/ts-sdk/extends/src/Client.ts index 8f93b62442c..070d0c4f834 100644 --- a/seed/ts-sdk/extends/src/Client.ts +++ b/seed/ts-sdk/extends/src/Client.ts @@ -48,6 +48,7 @@ export class SeedExtendsClient { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/extends", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/extends/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/extends/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/extends/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/extends/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/extends/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/extra-properties/package.json b/seed/ts-sdk/extra-properties/package.json index a9c55f198ae..bd14f90bc90 100644 --- a/seed/ts-sdk/extra-properties/package.json +++ b/seed/ts-sdk/extra-properties/package.json @@ -22,6 +22,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/extra-properties/src/api/resources/user/client/Client.ts b/seed/ts-sdk/extra-properties/src/api/resources/user/client/Client.ts index 0ef521fe9cb..43e8ec74ed4 100644 --- a/seed/ts-sdk/extra-properties/src/api/resources/user/client/Client.ts +++ b/seed/ts-sdk/extra-properties/src/api/resources/user/client/Client.ts @@ -46,6 +46,7 @@ export class User { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/extra-properties", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/extra-properties/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/extra-properties/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/extra-properties/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/extra-properties/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/extra-properties/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/file-download/file-download-reponse-headers/package.json b/seed/ts-sdk/file-download/file-download-reponse-headers/package.json index 9443c25e226..4a3b0202b29 100644 --- a/seed/ts-sdk/file-download/file-download-reponse-headers/package.json +++ b/seed/ts-sdk/file-download/file-download-reponse-headers/package.json @@ -20,6 +20,7 @@ "devDependencies": { "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/file-download/file-download-reponse-headers/src/api/resources/service/client/Client.ts b/seed/ts-sdk/file-download/file-download-reponse-headers/src/api/resources/service/client/Client.ts index 106ba968d58..bb0051c4bb1 100644 --- a/seed/ts-sdk/file-download/file-download-reponse-headers/src/api/resources/service/client/Client.ts +++ b/seed/ts-sdk/file-download/file-download-reponse-headers/src/api/resources/service/client/Client.ts @@ -36,6 +36,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/file-download", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/file-download/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/file-download/file-download-reponse-headers/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/file-download/file-download-reponse-headers/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/file-download/file-download-reponse-headers/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/file-download/file-download-reponse-headers/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/file-download/no-custom-config/package.json b/seed/ts-sdk/file-download/no-custom-config/package.json index 9443c25e226..4a3b0202b29 100644 --- a/seed/ts-sdk/file-download/no-custom-config/package.json +++ b/seed/ts-sdk/file-download/no-custom-config/package.json @@ -20,6 +20,7 @@ "devDependencies": { "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/file-download/no-custom-config/src/api/resources/service/client/Client.ts b/seed/ts-sdk/file-download/no-custom-config/src/api/resources/service/client/Client.ts index 516e44c6528..18161fb43c8 100644 --- a/seed/ts-sdk/file-download/no-custom-config/src/api/resources/service/client/Client.ts +++ b/seed/ts-sdk/file-download/no-custom-config/src/api/resources/service/client/Client.ts @@ -32,6 +32,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/file-download", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/file-download/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/file-download/no-custom-config/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/file-download/no-custom-config/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/file-download/no-custom-config/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/file-download/no-custom-config/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/file-upload/no-custom-config/package.json b/seed/ts-sdk/file-upload/no-custom-config/package.json index 70972c559f5..6ac4318712f 100644 --- a/seed/ts-sdk/file-upload/no-custom-config/package.json +++ b/seed/ts-sdk/file-upload/no-custom-config/package.json @@ -23,6 +23,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/file-upload/no-custom-config/src/api/resources/service/client/Client.ts b/seed/ts-sdk/file-upload/no-custom-config/src/api/resources/service/client/Client.ts index 675e3c9f676..98261a7efa1 100644 --- a/seed/ts-sdk/file-upload/no-custom-config/src/api/resources/service/client/Client.ts +++ b/seed/ts-sdk/file-upload/no-custom-config/src/api/resources/service/client/Client.ts @@ -89,6 +89,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/file-upload", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/file-upload/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, ..._maybeEncodedRequest.headers, @@ -144,6 +145,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/file-upload", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/file-upload/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, ..._maybeEncodedRequest.headers, @@ -234,6 +236,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/file-upload", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/file-upload/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, ..._maybeEncodedRequest.headers, diff --git a/seed/ts-sdk/file-upload/no-custom-config/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/file-upload/no-custom-config/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/file-upload/no-custom-config/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/file-upload/no-custom-config/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/file-upload/wrap-file-properties/package.json b/seed/ts-sdk/file-upload/wrap-file-properties/package.json index 70972c559f5..6ac4318712f 100644 --- a/seed/ts-sdk/file-upload/wrap-file-properties/package.json +++ b/seed/ts-sdk/file-upload/wrap-file-properties/package.json @@ -23,6 +23,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/file-upload/wrap-file-properties/src/api/resources/service/client/Client.ts b/seed/ts-sdk/file-upload/wrap-file-properties/src/api/resources/service/client/Client.ts index 36f31ceb420..b00ad9b1516 100644 --- a/seed/ts-sdk/file-upload/wrap-file-properties/src/api/resources/service/client/Client.ts +++ b/seed/ts-sdk/file-upload/wrap-file-properties/src/api/resources/service/client/Client.ts @@ -80,6 +80,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/file-upload", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/file-upload/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, ..._maybeEncodedRequest.headers, @@ -140,6 +141,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/file-upload", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/file-upload/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, ..._maybeEncodedRequest.headers, @@ -229,6 +231,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/file-upload", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/file-upload/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, ..._maybeEncodedRequest.headers, diff --git a/seed/ts-sdk/file-upload/wrap-file-properties/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/file-upload/wrap-file-properties/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/file-upload/wrap-file-properties/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/file-upload/wrap-file-properties/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/folders/package.json b/seed/ts-sdk/folders/package.json index 3d708abc3db..a5c7173da5a 100644 --- a/seed/ts-sdk/folders/package.json +++ b/seed/ts-sdk/folders/package.json @@ -22,6 +22,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/folders/src/Client.ts b/seed/ts-sdk/folders/src/Client.ts index a6ef49e2917..1d6db284c66 100644 --- a/seed/ts-sdk/folders/src/Client.ts +++ b/seed/ts-sdk/folders/src/Client.ts @@ -39,6 +39,7 @@ export class SeedApiClient { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/folders", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/folders/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/folders/src/api/resources/a/resources/b/client/Client.ts b/seed/ts-sdk/folders/src/api/resources/a/resources/b/client/Client.ts index 4ecc143f7db..d2f21a5460d 100644 --- a/seed/ts-sdk/folders/src/api/resources/a/resources/b/client/Client.ts +++ b/seed/ts-sdk/folders/src/api/resources/a/resources/b/client/Client.ts @@ -37,6 +37,7 @@ export class B { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/folders", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/folders/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/folders/src/api/resources/a/resources/c/client/Client.ts b/seed/ts-sdk/folders/src/api/resources/a/resources/c/client/Client.ts index 6ca3d30ea31..4cd32910f8b 100644 --- a/seed/ts-sdk/folders/src/api/resources/a/resources/c/client/Client.ts +++ b/seed/ts-sdk/folders/src/api/resources/a/resources/c/client/Client.ts @@ -37,6 +37,7 @@ export class C { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/folders", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/folders/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/folders/src/api/resources/folder/client/Client.ts b/seed/ts-sdk/folders/src/api/resources/folder/client/Client.ts index b6c6ba6b3fb..165e6923595 100644 --- a/seed/ts-sdk/folders/src/api/resources/folder/client/Client.ts +++ b/seed/ts-sdk/folders/src/api/resources/folder/client/Client.ts @@ -38,6 +38,7 @@ export class Folder { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/folders", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/folders/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/folders/src/api/resources/folder/resources/service/client/Client.ts b/seed/ts-sdk/folders/src/api/resources/folder/resources/service/client/Client.ts index 768b4ad0d25..85f81cd67ab 100644 --- a/seed/ts-sdk/folders/src/api/resources/folder/resources/service/client/Client.ts +++ b/seed/ts-sdk/folders/src/api/resources/folder/resources/service/client/Client.ts @@ -40,6 +40,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/folders", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/folders/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -94,6 +95,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/folders", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/folders/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/folders/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/folders/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/folders/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/folders/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/grpc-proto/.github/workflows/ci.yml b/seed/ts-sdk/grpc-proto/.github/workflows/ci.yml new file mode 100644 index 00000000000..b64a6cbbb4a --- /dev/null +++ b/seed/ts-sdk/grpc-proto/.github/workflows/ci.yml @@ -0,0 +1,57 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up node + uses: actions/setup-node@v3 + + - name: Compile + run: yarn && yarn build + + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up node + uses: actions/setup-node@v3 + + - name: Compile + run: yarn && yarn test + + publish: + needs: [ compile, test ] + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + - name: Set up node + uses: actions/setup-node@v3 + - name: Install dependencies + run: yarn install + - name: Build + run: yarn build + + - name: Publish to npm + run: | + npm config set //registry.npmjs.org/:_authToken ${NPM_TOKEN} + if [[ ${GITHUB_REF} == *alpha* ]]; then + npm publish --access public --tag alpha + elif [[ ${GITHUB_REF} == *beta* ]]; then + npm publish --access public --tag beta + else + npm publish --access public + fi + env: + NPM_TOKEN: ${{ secrets. }} \ No newline at end of file diff --git a/seed/ts-sdk/grpc-proto/.gitignore b/seed/ts-sdk/grpc-proto/.gitignore new file mode 100644 index 00000000000..72271e049c0 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/.gitignore @@ -0,0 +1,3 @@ +node_modules +.DS_Store +/dist \ No newline at end of file diff --git a/seed/ts-sdk/grpc-proto/.mock/fern.config.json b/seed/ts-sdk/grpc-proto/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/ts-sdk/grpc-proto/.mock/generators.yml b/seed/ts-sdk/grpc-proto/.mock/generators.yml new file mode 100644 index 00000000000..902c4f50168 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/.mock/generators.yml @@ -0,0 +1,5 @@ +api: + - proto: + root: proto + target: proto/user/v1/user.proto + local-generation: true \ No newline at end of file diff --git a/seed/ts-sdk/grpc-proto/.mock/proto/google/api/annotations.proto b/seed/ts-sdk/grpc-proto/.mock/proto/google/api/annotations.proto new file mode 100644 index 00000000000..8ff42098404 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/.mock/proto/google/api/annotations.proto @@ -0,0 +1,31 @@ +// Copyright 2015 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/api/http.proto"; +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "AnnotationsProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // See `HttpRule`. + HttpRule http = 72295728; +} \ No newline at end of file diff --git a/seed/ts-sdk/grpc-proto/.mock/proto/google/api/field_behavior.proto b/seed/ts-sdk/grpc-proto/.mock/proto/google/api/field_behavior.proto new file mode 100644 index 00000000000..128799c558d --- /dev/null +++ b/seed/ts-sdk/grpc-proto/.mock/proto/google/api/field_behavior.proto @@ -0,0 +1,104 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "FieldBehaviorProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.FieldOptions { + // A designation of a specific field behavior (required, output only, etc.) + // in protobuf messages. + // + // Examples: + // + // string name = 1 [(google.api.field_behavior) = REQUIRED]; + // State state = 1 [(google.api.field_behavior) = OUTPUT_ONLY]; + // google.protobuf.Duration ttl = 1 + // [(google.api.field_behavior) = INPUT_ONLY]; + // google.protobuf.Timestamp expire_time = 1 + // [(google.api.field_behavior) = OUTPUT_ONLY, + // (google.api.field_behavior) = IMMUTABLE]; + repeated google.api.FieldBehavior field_behavior = 1052; +} + +// An indicator of the behavior of a given field (for example, that a field +// is required in requests, or given as output but ignored as input). +// This **does not** change the behavior in protocol buffers itself; it only +// denotes the behavior and may affect how API tooling handles the field. +// +// Note: This enum **may** receive new values in the future. +enum FieldBehavior { + // Conventional default for enums. Do not use this. + FIELD_BEHAVIOR_UNSPECIFIED = 0; + + // Specifically denotes a field as optional. + // While all fields in protocol buffers are optional, this may be specified + // for emphasis if appropriate. + OPTIONAL = 1; + + // Denotes a field as required. + // This indicates that the field **must** be provided as part of the request, + // and failure to do so will cause an error (usually `INVALID_ARGUMENT`). + REQUIRED = 2; + + // Denotes a field as output only. + // This indicates that the field is provided in responses, but including the + // field in a request does nothing (the server *must* ignore it and + // *must not* throw an error as a result of the field's presence). + OUTPUT_ONLY = 3; + + // Denotes a field as input only. + // This indicates that the field is provided in requests, and the + // corresponding field is not included in output. + INPUT_ONLY = 4; + + // Denotes a field as immutable. + // This indicates that the field may be set once in a request to create a + // resource, but may not be changed thereafter. + IMMUTABLE = 5; + + // Denotes that a (repeated) field is an unordered list. + // This indicates that the service may provide the elements of the list + // in any arbitrary order, rather than the order the user originally + // provided. Additionally, the list's order may or may not be stable. + UNORDERED_LIST = 6; + + // Denotes that this field returns a non-empty default value if not set. + // This indicates that if the user provides the empty value in a request, + // a non-empty value will be returned. The user will not be aware of what + // non-empty value to expect. + NON_EMPTY_DEFAULT = 7; + + // Denotes that the field in a resource (a message annotated with + // google.api.resource) is used in the resource name to uniquely identify the + // resource. For AIP-compliant APIs, this should only be applied to the + // `name` field on the resource. + // + // This behavior should not be applied to references to other resources within + // the message. + // + // The identifier field of resources often have different field behavior + // depending on the request it is embedded in (e.g. for Create methods name + // is optional and unused, while for Update methods it is required). Instead + // of method-specific annotations, only `IDENTIFIER` is required. + IDENTIFIER = 8; +} \ No newline at end of file diff --git a/seed/ts-sdk/grpc-proto/.mock/proto/google/api/http.proto b/seed/ts-sdk/grpc-proto/.mock/proto/google/api/http.proto new file mode 100644 index 00000000000..c8392381eb9 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/.mock/proto/google/api/http.proto @@ -0,0 +1,379 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "HttpProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Defines the HTTP configuration for an API service. It contains a list of +// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method +// to one or more HTTP REST API methods. +message Http { + // A list of HTTP configuration rules that apply to individual API methods. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated HttpRule rules = 1; + + // When set to true, URL path parameters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // + // The default behavior is to not decode RFC 6570 reserved characters in multi + // segment matches. + bool fully_decode_reserved_expansion = 2; +} + +// # gRPC Transcoding +// +// gRPC Transcoding is a feature for mapping between a gRPC method and one or +// more HTTP REST endpoints. It allows developers to build a single API service +// that supports both gRPC APIs and REST APIs. Many systems, including [Google +// APIs](https://github.com/googleapis/googleapis), +// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC +// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), +// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature +// and use it for large scale production services. +// +// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies +// how different portions of the gRPC request message are mapped to the URL +// path, URL query parameters, and HTTP request body. It also controls how the +// gRPC response message is mapped to the HTTP response body. `HttpRule` is +// typically specified as an `google.api.http` annotation on the gRPC method. +// +// Each mapping specifies a URL path template and an HTTP method. The path +// template may refer to one or more fields in the gRPC request message, as long +// as each field is a non-repeated field with a primitive (non-message) type. +// The path template controls how fields of the request message are mapped to +// the URL path. +// +// Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/{name=messages/*}" +// }; +// } +// } +// message GetMessageRequest { +// string name = 1; // Mapped to URL path. +// } +// message Message { +// string text = 1; // The resource content. +// } +// +// This enables an HTTP REST to gRPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` +// +// Any fields in the request message which are not bound by the path template +// automatically become HTTP query parameters if there is no HTTP request body. +// For example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get:"/v1/messages/{message_id}" +// }; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // Mapped to URL path. +// int64 revision = 2; // Mapped to URL query parameter `revision`. +// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. +// } +// +// This enables a HTTP JSON to RPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | +// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: +// "foo"))` +// +// Note that fields which are mapped to URL query parameters must have a +// primitive type or a repeated primitive type or a non-repeated message type. +// In the case of a repeated type, the parameter can be repeated in the URL +// as `...?param=A¶m=B`. In the case of a message type, each field of the +// message is mapped to a separate parameter, such as +// `...?foo.a=A&foo.b=B&foo.c=C`. +// +// For HTTP methods that allow a request body, the `body` field +// specifies the mapping. Consider a REST update method on the +// message resource collection: +// +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } +// +// The following HTTP JSON to RPC mapping is enabled, where the +// representation of the JSON in the request body is determined by +// protos JSON encoding: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" message { text: "Hi!" })` +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. This enables the following alternative definition of +// the update method: +// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } +// +// +// The following HTTP JSON to RPC mapping is enabled: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" text: "Hi!")` +// +// Note that when using `*` in the body mapping, it is not possible to +// have HTTP parameters, as all fields not bound by the path end in +// the body. This makes this option more rarely used in practice when +// defining REST APIs. The common usage of `*` is in custom methods +// which don't use the URL at all for transferring data. +// +// It is possible to define multiple HTTP methods for one RPC by using +// the `additional_bindings` option. Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } +// +// This enables the following two alternative HTTP JSON to RPC mappings: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` +// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: +// "123456")` +// +// ## Rules for HTTP mapping +// +// 1. Leaf request fields (recursive expansion nested messages in the request +// message) are classified into three categories: +// - Fields referred by the path template. They are passed via the URL path. +// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They +// are passed via the HTTP +// request body. +// - All other fields are passed via the URL query parameters, and the +// parameter name is the field path in the request message. A repeated +// field can be represented as multiple query parameters under the same +// name. +// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL +// query parameter, all fields +// are passed via URL path and HTTP request body. +// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP +// request body, all +// fields are passed via URL path and URL query parameters. +// +// ### Path template syntax +// +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +// +// The syntax `*` matches a single URL path segment. The syntax `**` matches +// zero or more URL path segments, which must be the last part of the URL path +// except the `Verb`. +// +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable +// matches a single path segment, its template may be omitted, e.g. `{var}` +// is equivalent to `{var=*}`. +// +// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` +// contains any reserved character, such characters should be percent-encoded +// before the matching. +// +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path on the client +// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The +// server side does the reverse decoding. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{var}`. +// +// If a variable contains multiple path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path on the +// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. +// The server side does the reverse decoding, except "%2F" and "%2f" are left +// unchanged. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{+var}`. +// +// ## Using gRPC API Service Configuration +// +// gRPC API Service Configuration (service config) is a configuration language +// for configuring a gRPC service to become a user-facing product. The +// service config is simply the YAML representation of the `google.api.Service` +// proto message. +// +// As an alternative to annotating your proto file, you can configure gRPC +// transcoding in your service config YAML files. You do this by specifying a +// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same +// effect as the proto annotation. This can be particularly useful if you +// have a proto that is reused in multiple services. Note that any transcoding +// specified in the service config will override any matching transcoding +// configuration in the proto. +// +// Example: +// +// http: +// rules: +// # Selects a gRPC method and applies HttpRule to it. +// - selector: example.v1.Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} +// +// ## Special notes +// +// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the +// proto to JSON conversion must follow the [proto3 +// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). +// +// While the single segment variable follows the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String +// Expansion, the multi segment variable **does not** follow RFC 6570 Section +// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding +// for multi segment variables. +// +// The path variables **must not** refer to any repeated or mapped field, +// because client libraries are not capable of handling such variable expansion. +// +// The path variables **must not** capture the leading "/" character. The reason +// is that the most common use case "{var}" does not capture the leading "/" +// character. For consistency, all path variables must share the same behavior. +// +// Repeated message fields must not be mapped to URL query parameters, because +// no client library can support such complicated mapping. +// +// If an API needs to use a JSON array for request or response body, it can map +// the request or response body to a repeated field. However, some gRPC +// Transcoding implementations may not support this feature. +message HttpRule { + // Selects a method to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax + // details. + string selector = 1; + + // Determines the URL pattern is matched by this rules. This pattern can be + // used with any of the {get|put|post|delete|patch} methods. A custom method + // can be defined using the 'custom' field. + oneof pattern { + // Maps to HTTP GET. Used for listing and getting information about + // resources. + string get = 2; + + // Maps to HTTP PUT. Used for replacing a resource. + string put = 3; + + // Maps to HTTP POST. Used for creating a resource or performing an action. + string post = 4; + + // Maps to HTTP DELETE. Used for deleting a resource. + string delete = 5; + + // Maps to HTTP PATCH. Used for updating a resource. + string patch = 6; + + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. + CustomHttpPattern custom = 8; + } + + // The name of the request field whose value is mapped to the HTTP request + // body, or `*` for mapping all request fields not captured by the path + // pattern to the HTTP body, or omitted for not having any HTTP request body. + // + // NOTE: the referred field must be present at the top-level of the request + // message type. + string body = 7; + + // Optional. The name of the response field whose value is mapped to the HTTP + // response body. When omitted, the entire response message will be used + // as the HTTP response body. + // + // NOTE: The referred field must be present at the top-level of the response + // message type. + string response_body = 12; + + // Additional HTTP bindings for the selector. Nested bindings must + // not contain an `additional_bindings` field themselves (that is, + // the nesting may only be one level deep). + repeated HttpRule additional_bindings = 11; +} + +// A custom pattern is used for defining custom HTTP verb. +message CustomHttpPattern { + // The name of this custom HTTP verb. + string kind = 1; + + // The path matched by this custom verb. + string path = 2; +} \ No newline at end of file diff --git a/seed/ts-sdk/grpc-proto/.mock/proto/user/v1/user.proto b/seed/ts-sdk/grpc-proto/.mock/proto/user/v1/user.proto new file mode 100644 index 00000000000..a3ad0195bd4 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/.mock/proto/user/v1/user.proto @@ -0,0 +1,39 @@ +syntax = "proto3"; + +package user.v1; + +import "google/api/annotations.proto"; +import "google/api/field_behavior.proto"; +import "google/protobuf/struct.proto"; + +option go_package = "user/v1"; +option csharp_namespace = "User.V1"; + +message UserModel { + string username = 1; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateRequest { + string username = 1; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateResponse { + UserModel user = 1; +} + +service User { + rpc Create(CreateRequest) returns (CreateResponse) { + option (google.api.http) = { + post: "/users" + body: "*" + }; + } +} \ No newline at end of file diff --git a/seed/ts-sdk/grpc-proto/.npmignore b/seed/ts-sdk/grpc-proto/.npmignore new file mode 100644 index 00000000000..6db0876c41c --- /dev/null +++ b/seed/ts-sdk/grpc-proto/.npmignore @@ -0,0 +1,9 @@ +node_modules +src +tests +.gitignore +.github +.fernignore +.prettierrc.yml +tsconfig.json +yarn.lock \ No newline at end of file diff --git a/seed/ts-sdk/grpc-proto/.prettierrc.yml b/seed/ts-sdk/grpc-proto/.prettierrc.yml new file mode 100644 index 00000000000..0c06786bf53 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/.prettierrc.yml @@ -0,0 +1,2 @@ +tabWidth: 4 +printWidth: 120 diff --git a/seed/ts-sdk/grpc-proto/README.md b/seed/ts-sdk/grpc-proto/README.md new file mode 100644 index 00000000000..4cb8287b18e --- /dev/null +++ b/seed/ts-sdk/grpc-proto/README.md @@ -0,0 +1,135 @@ +# Seed TypeScript Library + +[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-SDK%20generated%20by%20Fern-brightgreen)](https://github.com/fern-api/fern) +[![npm shield](https://img.shields.io/npm/v/@fern/grpc-proto)](https://www.npmjs.com/package/@fern/grpc-proto) + +The Seed TypeScript library provides convenient access to the Seed API from TypeScript. + +## Installation + +```sh +npm i -s @fern/grpc-proto +``` + +## Usage + +Instantiate and use the client with the following: + +```typescript +import { SeedApiClient } from "@fern/grpc-proto"; + +const client = new SeedApiClient({ environment: "YOUR_BASE_URL" }); +await client.user.create(); +``` + +## Request And Response Types + +The SDK exports all request and response types as TypeScript interfaces. Simply import them with the +following namespace: + +```typescript +import { SeedApi } from "@fern/grpc-proto"; + +const request: SeedApi.CreateRequest = { + ... +}; +``` + +## Exception Handling + +When the API returns a non-success status code (4xx or 5xx response), a subclass of the following error +will be thrown. + +```typescript +import { SeedApiError } from "@fern/grpc-proto"; + +try { + await client.user.create(...); +} catch (err) { + if (err instanceof SeedApiError) { + console.log(err.statusCode); + console.log(err.message); + console.log(err.body); + } +} +``` + +## Advanced + +### Retries + +The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long +as the request is deemed retriable and the number of retry attempts has not grown larger than the configured +retry limit (default: 2). + +A request is deemed retriable when any of the following HTTP status codes is returned: + +- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) +- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) +- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) + +Use the `maxRetries` request option to configure this behavior. + +```typescript +const response = await client.user.create(..., { + maxRetries: 0 // override maxRetries at the request level +}); +``` + +### Timeouts + +The SDK defaults to a 60 second timeout. Use the `timeoutInSeconds` option to configure this behavior. + +```typescript +const response = await client.user.create(..., { + timeoutInSeconds: 30 // override timeout to 30s +}); +``` + +### Aborting Requests + +The SDK allows users to abort requests at any point by passing in an abort signal. + +```typescript +const controller = new AbortController(); +const response = await client.user.create(..., { + abortSignal: controller.signal +}); +controller.abort(); // aborts the request +``` + +### Runtime Compatibility + +The SDK defaults to `node-fetch` but will use the global fetch client if present. The SDK works in the following +runtimes: + +- Node.js 18+ +- Vercel +- Cloudflare Workers +- Deno v1.25+ +- Bun 1.0+ +- React Native + +### Customizing Fetch Client + +The SDK provides a way for your to customize the underlying HTTP client / Fetch function. If you're running in an +unsupported environment, this provides a way for you to break glass and ensure the SDK works. + +```typescript +import { SeedApiClient } from "@fern/grpc-proto"; + +const client = new SeedApiClient({ + ... + fetcher: // provide your implementation here +}); +``` + +## Contributing + +While we value open-source contributions to this SDK, this library is generated programmatically. +Additions made directly to this library would have to be moved over to our generation code, +otherwise they would be overwritten upon the next generated release. Feel free to open a PR as +a proof of concept, but know that we will not be able to merge it as-is. We suggest opening +an issue first to discuss with us! + +On the other hand, contributions to the README are always very welcome! diff --git a/seed/ts-sdk/grpc-proto/jest.config.js b/seed/ts-sdk/grpc-proto/jest.config.js new file mode 100644 index 00000000000..35d6e65bf93 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/jest.config.js @@ -0,0 +1,5 @@ +/** @type {import('jest').Config} */ +module.exports = { + preset: "ts-jest", + testEnvironment: "node", +}; diff --git a/seed/ts-sdk/grpc-proto/package.json b/seed/ts-sdk/grpc-proto/package.json new file mode 100644 index 00000000000..4dfef2e3f7c --- /dev/null +++ b/seed/ts-sdk/grpc-proto/package.json @@ -0,0 +1,39 @@ +{ + "name": "@fern/grpc-proto", + "version": "0.0.1", + "private": false, + "repository": "https://github.com/grpc-proto/fern", + "main": "./index.js", + "types": "./index.d.ts", + "scripts": { + "format": "prettier . --write --ignore-unknown", + "build": "tsc", + "prepack": "cp -rv dist/. .", + "test": "jest" + }, + "dependencies": { + "url-join": "4.0.1", + "form-data": "^4.0.0", + "formdata-node": "^6.0.3", + "node-fetch": "2.7.0", + "qs": "6.11.2" + }, + "devDependencies": { + "@types/url-join": "4.0.1", + "@types/qs": "6.9.8", + "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", + "jest": "29.7.0", + "@types/jest": "29.5.5", + "ts-jest": "29.1.1", + "jest-environment-jsdom": "29.7.0", + "@types/node": "17.0.33", + "prettier": "2.7.1", + "typescript": "4.6.4" + }, + "browser": { + "fs": false, + "os": false, + "path": false + } +} diff --git a/seed/ts-sdk/grpc-proto/reference.md b/seed/ts-sdk/grpc-proto/reference.md new file mode 100644 index 00000000000..dec8241c9c7 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/reference.md @@ -0,0 +1,51 @@ +# Reference + +## User + +

client.user.create({ ...params }) -> SeedApi.CreateResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```typescript +await client.user.create(); +``` + +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request:** `SeedApi.CreateRequest` + +
+
+ +
+
+ +**requestOptions:** `User.RequestOptions` + +
+
+
+
+ +
+
+
diff --git a/seed/ts-sdk/grpc-proto/snippet-templates.json b/seed/ts-sdk/grpc-proto/snippet-templates.json new file mode 100644 index 00000000000..b295bd42cf8 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/snippet-templates.json @@ -0,0 +1,174 @@ +[ + { + "sdk": { + "package": "@fern/grpc-proto", + "version": "0.0.1", + "type": "typescript" + }, + "endpointId": { + "path": "/users", + "method": "POST", + "identifierOverride": "endpoint_user.create" + }, + "snippetTemplate": { + "clientInstantiation": { + "imports": [ + "import { SeedApiClient } from \"@fern/grpc-proto\";" + ], + "templateString": "const client = new SeedApiClient($FERN_INPUT);", + "isOptional": false, + "inputDelimiter": ",", + "templateInputs": [ + { + "value": { + "imports": [], + "templateString": "{ $FERN_INPUT }", + "isOptional": true, + "templateInputs": [ + { + "value": { + "imports": [], + "templateString": "environment: \"YOUR_BASE_URL\"", + "isOptional": false, + "templateInputs": [], + "type": "generic" + }, + "type": "template" + } + ], + "type": "generic" + }, + "type": "template" + } + ], + "type": "generic" + }, + "functionInvocation": { + "imports": [], + "templateString": "await client.user.create(\n\t$FERN_INPUT\n)", + "isOptional": false, + "inputDelimiter": ",\n\t", + "templateInputs": [ + { + "value": { + "imports": [], + "templateString": "{\n\t\t$FERN_INPUT\n\t}", + "isOptional": true, + "inputDelimiter": ",\n\t\t", + "templateInputs": [ + { + "value": { + "imports": [], + "templateString": "username: $FERN_INPUT", + "isOptional": true, + "templateInputs": [ + { + "location": "BODY", + "path": "username", + "type": "payload" + } + ], + "type": "generic" + }, + "type": "template" + }, + { + "value": { + "imports": [], + "templateString": "email: $FERN_INPUT", + "isOptional": true, + "templateInputs": [ + { + "location": "BODY", + "path": "email", + "type": "payload" + } + ], + "type": "generic" + }, + "type": "template" + }, + { + "value": { + "imports": [], + "templateString": "age: $FERN_INPUT", + "isOptional": true, + "templateInputs": [ + { + "location": "BODY", + "path": "age", + "type": "payload" + } + ], + "type": "generic" + }, + "type": "template" + }, + { + "value": { + "imports": [], + "templateString": "weight: $FERN_INPUT", + "isOptional": true, + "templateInputs": [ + { + "location": "BODY", + "path": "weight", + "type": "payload" + } + ], + "type": "generic" + }, + "type": "template" + }, + { + "value": { + "imports": [], + "isOptional": true, + "containerTemplateString": "metadata: {\n\t\t\t$FERN_INPUT\n\t\t}", + "delimiter": ",\n\t\t\t", + "keyValueSeparator": ": ", + "keyTemplate": { + "imports": [], + "templateString": "$FERN_INPUT", + "isOptional": true, + "templateInputs": [ + { + "location": "RELATIVE", + "type": "payload" + } + ], + "type": "generic" + }, + "valueTemplate": { + "imports": [], + "templateString": "$FERN_INPUT", + "isOptional": true, + "templateInputs": [ + { + "location": "RELATIVE", + "type": "payload" + } + ], + "type": "generic" + }, + "templateInput": { + "location": "BODY", + "path": "metadata", + "type": "payload" + }, + "type": "dict" + }, + "type": "template" + } + ], + "type": "generic" + }, + "type": "template" + } + ], + "type": "generic" + }, + "type": "v1" + } + } +] \ No newline at end of file diff --git a/seed/ts-sdk/grpc-proto/snippet.json b/seed/ts-sdk/grpc-proto/snippet.json new file mode 100644 index 00000000000..c85620a68d0 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/snippet.json @@ -0,0 +1,16 @@ +{ + "endpoints": [ + { + "id": { + "path": "/users", + "method": "POST", + "identifier_override": "endpoint_user.create" + }, + "snippet": { + "type": "typescript", + "client": "import { SeedApiClient } from \"@fern/grpc-proto\";\n\nconst client = new SeedApiClient({ environment: \"YOUR_BASE_URL\" });\nawait client.user.create();\n" + } + } + ], + "types": {} +} \ No newline at end of file diff --git a/seed/ts-sdk/grpc-proto/src/Client.ts b/seed/ts-sdk/grpc-proto/src/Client.ts new file mode 100644 index 00000000000..96d0608351d --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/Client.ts @@ -0,0 +1,31 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as core from "./core"; +import { User } from "./api/resources/user/client/Client"; + +export declare namespace SeedApiClient { + interface Options { + environment: core.Supplier; + } + + interface RequestOptions { + /** The maximum time to wait for a response in seconds. */ + timeoutInSeconds?: number; + /** The number of times to retry the request. Defaults to 2. */ + maxRetries?: number; + /** A hook to abort the request. */ + abortSignal?: AbortSignal; + } +} + +export class SeedApiClient { + constructor(protected readonly _options: SeedApiClient.Options) {} + + protected _user: User | undefined; + + public get user(): User { + return (this._user ??= new User(this._options)); + } +} diff --git a/seed/ts-sdk/grpc-proto/src/api/index.ts b/seed/ts-sdk/grpc-proto/src/api/index.ts new file mode 100644 index 00000000000..3ce0a3e38e8 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/api/index.ts @@ -0,0 +1,2 @@ +export * from "./types"; +export * from "./resources"; diff --git a/seed/ts-sdk/grpc-proto/src/api/resources/index.ts b/seed/ts-sdk/grpc-proto/src/api/resources/index.ts new file mode 100644 index 00000000000..7b5adc8b2ff --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/api/resources/index.ts @@ -0,0 +1,2 @@ +export * as user from "./user"; +export * from "./user/client/requests"; diff --git a/seed/ts-sdk/grpc-proto/src/api/resources/user/client/Client.ts b/seed/ts-sdk/grpc-proto/src/api/resources/user/client/Client.ts new file mode 100644 index 00000000000..b3c76577e32 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/api/resources/user/client/Client.ts @@ -0,0 +1,88 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as core from "../../../../core"; +import * as SeedApi from "../../../index"; +import * as serializers from "../../../../serialization/index"; +import urlJoin from "url-join"; +import * as errors from "../../../../errors/index"; + +export declare namespace User { + interface Options { + environment: core.Supplier; + } + + interface RequestOptions { + /** The maximum time to wait for a response in seconds. */ + timeoutInSeconds?: number; + /** The number of times to retry the request. Defaults to 2. */ + maxRetries?: number; + /** A hook to abort the request. */ + abortSignal?: AbortSignal; + } +} + +export class User { + constructor(protected readonly _options: User.Options) {} + + /** + * @param {SeedApi.CreateRequest} request + * @param {User.RequestOptions} requestOptions - Request-specific configuration. + * + * @example + * await client.user.create() + */ + public async create( + request: SeedApi.CreateRequest = {}, + requestOptions?: User.RequestOptions + ): Promise { + const _response = await core.fetcher({ + url: urlJoin(await core.Supplier.get(this._options.environment), "users"), + method: "POST", + headers: { + "X-Fern-Language": "JavaScript", + "X-Fern-SDK-Name": "@fern/grpc-proto", + "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/grpc-proto/0.0.1", + "X-Fern-Runtime": core.RUNTIME.type, + "X-Fern-Runtime-Version": core.RUNTIME.version, + }, + contentType: "application/json", + requestType: "json", + body: serializers.CreateRequest.jsonOrThrow(request, { unrecognizedObjectKeys: "strip" }), + timeoutMs: requestOptions?.timeoutInSeconds != null ? requestOptions.timeoutInSeconds * 1000 : 60000, + maxRetries: requestOptions?.maxRetries, + abortSignal: requestOptions?.abortSignal, + }); + if (_response.ok) { + return serializers.CreateResponse.parseOrThrow(_response.body, { + unrecognizedObjectKeys: "passthrough", + allowUnrecognizedUnionMembers: true, + allowUnrecognizedEnumValues: true, + breadcrumbsPrefix: ["response"], + }); + } + + if (_response.error.reason === "status-code") { + throw new errors.SeedApiError({ + statusCode: _response.error.statusCode, + body: _response.error.body, + }); + } + + switch (_response.error.reason) { + case "non-json": + throw new errors.SeedApiError({ + statusCode: _response.error.statusCode, + body: _response.error.rawBody, + }); + case "timeout": + throw new errors.SeedApiTimeoutError(); + case "unknown": + throw new errors.SeedApiError({ + message: _response.error.errorMessage, + }); + } + } +} diff --git a/seed/ts-sdk/grpc-proto/src/api/resources/user/client/index.ts b/seed/ts-sdk/grpc-proto/src/api/resources/user/client/index.ts new file mode 100644 index 00000000000..415726b7fea --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/api/resources/user/client/index.ts @@ -0,0 +1 @@ +export * from "./requests"; diff --git a/seed/ts-sdk/grpc-proto/src/api/resources/user/client/requests/CreateRequest.ts b/seed/ts-sdk/grpc-proto/src/api/resources/user/client/requests/CreateRequest.ts new file mode 100644 index 00000000000..e2786b6bade --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/api/resources/user/client/requests/CreateRequest.ts @@ -0,0 +1,15 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +/** + * @example + * {} + */ +export interface CreateRequest { + username?: string; + email?: string; + age?: number; + weight?: number; + metadata?: Record; +} diff --git a/seed/ts-sdk/grpc-proto/src/api/resources/user/client/requests/index.ts b/seed/ts-sdk/grpc-proto/src/api/resources/user/client/requests/index.ts new file mode 100644 index 00000000000..e43e91cc89b --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/api/resources/user/client/requests/index.ts @@ -0,0 +1 @@ +export { type CreateRequest } from "./CreateRequest"; diff --git a/seed/ts-sdk/grpc-proto/src/api/resources/user/index.ts b/seed/ts-sdk/grpc-proto/src/api/resources/user/index.ts new file mode 100644 index 00000000000..5ec76921e18 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/api/resources/user/index.ts @@ -0,0 +1 @@ +export * from "./client"; diff --git a/seed/ts-sdk/grpc-proto/src/api/types/CreateResponse.ts b/seed/ts-sdk/grpc-proto/src/api/types/CreateResponse.ts new file mode 100644 index 00000000000..03aa8d0606d --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/api/types/CreateResponse.ts @@ -0,0 +1,9 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as SeedApi from "../index"; + +export interface CreateResponse { + user?: SeedApi.UserModel; +} diff --git a/seed/ts-sdk/grpc-proto/src/api/types/UserModel.ts b/seed/ts-sdk/grpc-proto/src/api/types/UserModel.ts new file mode 100644 index 00000000000..591c0c6bb88 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/api/types/UserModel.ts @@ -0,0 +1,11 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +export interface UserModel { + username?: string; + email?: string; + age?: number; + weight?: number; + metadata?: Record; +} diff --git a/seed/ts-sdk/grpc-proto/src/api/types/index.ts b/seed/ts-sdk/grpc-proto/src/api/types/index.ts new file mode 100644 index 00000000000..8685532c76b --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/api/types/index.ts @@ -0,0 +1,2 @@ +export * from "./CreateResponse"; +export * from "./UserModel"; diff --git a/seed/ts-sdk/grpc-proto/src/core/fetcher/APIResponse.ts b/seed/ts-sdk/grpc-proto/src/core/fetcher/APIResponse.ts new file mode 100644 index 00000000000..3664d09e168 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/fetcher/APIResponse.ts @@ -0,0 +1,12 @@ +export type APIResponse = SuccessfulResponse | FailedResponse; + +export interface SuccessfulResponse { + ok: true; + body: T; + headers?: Record; +} + +export interface FailedResponse { + ok: false; + error: T; +} diff --git a/seed/ts-sdk/grpc-proto/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/grpc-proto/src/core/fetcher/Fetcher.ts new file mode 100644 index 00000000000..d67bc042107 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/fetcher/Fetcher.ts @@ -0,0 +1,143 @@ +import { APIResponse } from "./APIResponse"; +import { createRequestUrl } from "./createRequestUrl"; +import { getFetchFn } from "./getFetchFn"; +import { getRequestBody } from "./getRequestBody"; +import { getResponseBody } from "./getResponseBody"; +import { makeRequest } from "./makeRequest"; +import { requestWithRetries } from "./requestWithRetries"; + +export type FetchFunction = (args: Fetcher.Args) => Promise>; + +export declare namespace Fetcher { + export interface Args { + url: string; + method: string; + contentType?: string; + headers?: Record; + queryParameters?: Record; + body?: unknown; + timeoutMs?: number; + maxRetries?: number; + withCredentials?: boolean; + abortSignal?: AbortSignal; + requestType?: "json" | "file" | "bytes"; + responseType?: "json" | "blob" | "sse" | "streaming" | "text"; + duplex?: "half"; + } + + export type Error = FailedStatusCodeError | NonJsonError | TimeoutError | UnknownError; + + export interface FailedStatusCodeError { + reason: "status-code"; + statusCode: number; + body: unknown; + } + + export interface NonJsonError { + reason: "non-json"; + statusCode: number; + rawBody: string; + } + + export interface TimeoutError { + reason: "timeout"; + } + + export interface UnknownError { + reason: "unknown"; + errorMessage: string; + } +} + +export async function fetcherImpl(args: Fetcher.Args): Promise> { + const headers: Record = {}; + if (args.body !== undefined && args.contentType != null) { + headers["Content-Type"] = args.contentType; + } + + if (args.headers != null) { + for (const [key, value] of Object.entries(args.headers)) { + if (value != null) { + headers[key] = value; + } + } + } + + const url = createRequestUrl(args.url, args.queryParameters); + let requestBody: BodyInit | undefined = await getRequestBody({ + body: args.body, + type: args.requestType === "json" ? "json" : "other", + }); + const fetchFn = await getFetchFn(); + + try { + const response = await requestWithRetries( + async () => + makeRequest( + fetchFn, + url, + args.method, + headers, + requestBody, + args.timeoutMs, + args.abortSignal, + args.withCredentials, + args.duplex + ), + args.maxRetries + ); + let responseBody = await getResponseBody(response, args.responseType); + + if (response.status >= 200 && response.status < 400) { + return { + ok: true, + body: responseBody as R, + headers: response.headers, + }; + } else { + return { + ok: false, + error: { + reason: "status-code", + statusCode: response.status, + body: responseBody, + }, + }; + } + } catch (error) { + if (args.abortSignal != null && args.abortSignal.aborted) { + return { + ok: false, + error: { + reason: "unknown", + errorMessage: "The user aborted a request", + }, + }; + } else if (error instanceof Error && error.name === "AbortError") { + return { + ok: false, + error: { + reason: "timeout", + }, + }; + } else if (error instanceof Error) { + return { + ok: false, + error: { + reason: "unknown", + errorMessage: error.message, + }, + }; + } + + return { + ok: false, + error: { + reason: "unknown", + errorMessage: JSON.stringify(error), + }, + }; + } +} + +export const fetcher: FetchFunction = fetcherImpl; diff --git a/seed/ts-sdk/grpc-proto/src/core/fetcher/Supplier.ts b/seed/ts-sdk/grpc-proto/src/core/fetcher/Supplier.ts new file mode 100644 index 00000000000..867c931c02f --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/fetcher/Supplier.ts @@ -0,0 +1,11 @@ +export type Supplier = T | Promise | (() => T | Promise); + +export const Supplier = { + get: async (supplier: Supplier): Promise => { + if (typeof supplier === "function") { + return (supplier as () => T)(); + } else { + return supplier; + } + }, +}; diff --git a/seed/ts-sdk/grpc-proto/src/core/fetcher/createRequestUrl.ts b/seed/ts-sdk/grpc-proto/src/core/fetcher/createRequestUrl.ts new file mode 100644 index 00000000000..9288a99bb22 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/fetcher/createRequestUrl.ts @@ -0,0 +1,10 @@ +import qs from "qs"; + +export function createRequestUrl( + baseUrl: string, + queryParameters?: Record +): string { + return Object.keys(queryParameters ?? {}).length > 0 + ? `${baseUrl}?${qs.stringify(queryParameters, { arrayFormat: "repeat" })}` + : baseUrl; +} diff --git a/seed/ts-sdk/grpc-proto/src/core/fetcher/getFetchFn.ts b/seed/ts-sdk/grpc-proto/src/core/fetcher/getFetchFn.ts new file mode 100644 index 00000000000..9fd9bfc42bd --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/fetcher/getFetchFn.ts @@ -0,0 +1,25 @@ +import { RUNTIME } from "../runtime"; + +/** + * Returns a fetch function based on the runtime + */ +export async function getFetchFn(): Promise { + // In Node.js 18+ environments, use native fetch + if (RUNTIME.type === "node" && RUNTIME.parsedVersion != null && RUNTIME.parsedVersion >= 18) { + return fetch; + } + + // In Node.js 18 or lower environments, the SDK always uses`node-fetch`. + if (RUNTIME.type === "node") { + return (await import("node-fetch")).default as any; + } + + // Otherwise the SDK uses global fetch if available, + // and falls back to node-fetch. + if (typeof fetch == "function") { + return fetch; + } + + // Defaults to node `node-fetch` if global fetch isn't available + return (await import("node-fetch")).default as any; +} diff --git a/seed/ts-sdk/grpc-proto/src/core/fetcher/getHeader.ts b/seed/ts-sdk/grpc-proto/src/core/fetcher/getHeader.ts new file mode 100644 index 00000000000..50f922b0e87 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/fetcher/getHeader.ts @@ -0,0 +1,8 @@ +export function getHeader(headers: Record, header: string): string | undefined { + for (const [headerKey, headerValue] of Object.entries(headers)) { + if (headerKey.toLowerCase() === header.toLowerCase()) { + return headerValue; + } + } + return undefined; +} diff --git a/seed/ts-sdk/grpc-proto/src/core/fetcher/getRequestBody.ts b/seed/ts-sdk/grpc-proto/src/core/fetcher/getRequestBody.ts new file mode 100644 index 00000000000..1138414b1c2 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/fetcher/getRequestBody.ts @@ -0,0 +1,14 @@ +export declare namespace GetRequestBody { + interface Args { + body: unknown; + type: "json" | "file" | "bytes" | "other"; + } +} + +export async function getRequestBody({ body, type }: GetRequestBody.Args): Promise { + if (type.includes("json")) { + return JSON.stringify(body); + } else { + return body as BodyInit; + } +} diff --git a/seed/ts-sdk/grpc-proto/src/core/fetcher/getResponseBody.ts b/seed/ts-sdk/grpc-proto/src/core/fetcher/getResponseBody.ts new file mode 100644 index 00000000000..a7a9c508777 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/fetcher/getResponseBody.ts @@ -0,0 +1,32 @@ +import { chooseStreamWrapper } from "./stream-wrappers/chooseStreamWrapper"; + +export async function getResponseBody(response: Response, responseType?: string): Promise { + if (response.body != null && responseType === "blob") { + return await response.blob(); + } else if (response.body != null && responseType === "sse") { + return response.body; + } else if (response.body != null && responseType === "streaming") { + return chooseStreamWrapper(response.body); + } else if (response.body != null && responseType === "text") { + return await response.text(); + } else { + const text = await response.text(); + if (text.length > 0) { + try { + let responseBody = JSON.parse(text); + return responseBody; + } catch (err) { + return { + ok: false, + error: { + reason: "non-json", + statusCode: response.status, + rawBody: text, + }, + }; + } + } else { + return undefined; + } + } +} diff --git a/seed/ts-sdk/grpc-proto/src/core/fetcher/index.ts b/seed/ts-sdk/grpc-proto/src/core/fetcher/index.ts new file mode 100644 index 00000000000..2d658ca48f9 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/fetcher/index.ts @@ -0,0 +1,5 @@ +export type { APIResponse } from "./APIResponse"; +export { fetcher } from "./Fetcher"; +export type { Fetcher, FetchFunction } from "./Fetcher"; +export { getHeader } from "./getHeader"; +export { Supplier } from "./Supplier"; diff --git a/seed/ts-sdk/grpc-proto/src/core/fetcher/makeRequest.ts b/seed/ts-sdk/grpc-proto/src/core/fetcher/makeRequest.ts new file mode 100644 index 00000000000..8fb4bace466 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/fetcher/makeRequest.ts @@ -0,0 +1,44 @@ +import { anySignal, getTimeoutSignal } from "./signals"; + +export const makeRequest = async ( + fetchFn: (url: string, init: RequestInit) => Promise, + url: string, + method: string, + headers: Record, + requestBody: BodyInit | undefined, + timeoutMs?: number, + abortSignal?: AbortSignal, + withCredentials?: boolean, + duplex?: "half" +): Promise => { + const signals: AbortSignal[] = []; + + // Add timeout signal + let timeoutAbortId: NodeJS.Timeout | undefined = undefined; + if (timeoutMs != null) { + const { signal, abortId } = getTimeoutSignal(timeoutMs); + timeoutAbortId = abortId; + signals.push(signal); + } + + // Add arbitrary signal + if (abortSignal != null) { + signals.push(abortSignal); + } + let newSignals = anySignal(signals); + const response = await fetchFn(url, { + method: method, + headers, + body: requestBody, + signal: newSignals, + credentials: withCredentials ? "include" : undefined, + // @ts-ignore + duplex, + }); + + if (timeoutAbortId != null) { + clearTimeout(timeoutAbortId); + } + + return response; +}; diff --git a/seed/ts-sdk/grpc-proto/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/grpc-proto/src/core/fetcher/requestWithRetries.ts new file mode 100644 index 00000000000..ff5dc3bbabc --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/fetcher/requestWithRetries.ts @@ -0,0 +1,21 @@ +const INITIAL_RETRY_DELAY = 1; +const MAX_RETRY_DELAY = 60; +const DEFAULT_MAX_RETRIES = 2; + +export async function requestWithRetries( + requestFn: () => Promise, + maxRetries: number = DEFAULT_MAX_RETRIES +): Promise { + let response: Response = await requestFn(); + + for (let i = 0; i < maxRetries; ++i) { + if ([408, 409, 429].includes(response.status) || response.status >= 500) { + const delay = Math.min(INITIAL_RETRY_DELAY * Math.pow(2, i), MAX_RETRY_DELAY); + await new Promise((resolve) => setTimeout(resolve, delay)); + response = await requestFn(); + } else { + break; + } + } + return response!; +} diff --git a/seed/ts-sdk/grpc-proto/src/core/fetcher/signals.ts b/seed/ts-sdk/grpc-proto/src/core/fetcher/signals.ts new file mode 100644 index 00000000000..6c124ff7985 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/fetcher/signals.ts @@ -0,0 +1,38 @@ +const TIMEOUT = "timeout"; + +export function getTimeoutSignal(timeoutMs: number): { signal: AbortSignal; abortId: NodeJS.Timeout } { + const controller = new AbortController(); + const abortId = setTimeout(() => controller.abort(TIMEOUT), timeoutMs); + return { signal: controller.signal, abortId }; +} + +/** + * Returns an abort signal that is getting aborted when + * at least one of the specified abort signals is aborted. + * + * Requires at least node.js 18. + */ +export function anySignal(...args: AbortSignal[] | [AbortSignal[]]): AbortSignal { + // Allowing signals to be passed either as array + // of signals or as multiple arguments. + const signals = (args.length === 1 && Array.isArray(args[0]) ? args[0] : args); + + const controller = new AbortController(); + + for (const signal of signals) { + if (signal.aborted) { + // Exiting early if one of the signals + // is already aborted. + controller.abort((signal as any)?.reason); + break; + } + + // Listening for signals and removing the listeners + // when at least one symbol is aborted. + signal.addEventListener("abort", () => controller.abort((signal as any)?.reason), { + signal: controller.signal, + }); + } + + return controller.signal; +} diff --git a/seed/ts-sdk/grpc-proto/src/core/fetcher/stream-wrappers/Node18UniversalStreamWrapper.ts b/seed/ts-sdk/grpc-proto/src/core/fetcher/stream-wrappers/Node18UniversalStreamWrapper.ts new file mode 100644 index 00000000000..e5db8734c83 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/fetcher/stream-wrappers/Node18UniversalStreamWrapper.ts @@ -0,0 +1,252 @@ +import type { Writable } from "stream"; +import { EventCallback, StreamWrapper } from "./chooseStreamWrapper"; + +export class Node18UniversalStreamWrapper + implements + StreamWrapper | Writable | WritableStream, ReadFormat> +{ + private readableStream: ReadableStream; + private reader: ReadableStreamDefaultReader; + private events: Record; + private paused: boolean; + private resumeCallback: ((value?: unknown) => void) | null; + private encoding: string | null; + + constructor(readableStream: ReadableStream) { + this.readableStream = readableStream; + this.reader = this.readableStream.getReader(); + this.events = { + data: [], + end: [], + error: [], + readable: [], + close: [], + pause: [], + resume: [], + }; + this.paused = false; + this.resumeCallback = null; + this.encoding = null; + } + + public on(event: string, callback: EventCallback): void { + this.events[event]?.push(callback); + } + + public off(event: string, callback: EventCallback): void { + this.events[event] = this.events[event]?.filter((cb) => cb !== callback); + } + + public pipe( + dest: Node18UniversalStreamWrapper | Writable | WritableStream + ): Node18UniversalStreamWrapper | Writable | WritableStream { + this.on("data", async (chunk) => { + if (dest instanceof Node18UniversalStreamWrapper) { + dest._write(chunk); + } else if (dest instanceof WritableStream) { + const writer = dest.getWriter(); + writer.write(chunk).then(() => writer.releaseLock()); + } else { + dest.write(chunk); + } + }); + + this.on("end", async () => { + if (dest instanceof Node18UniversalStreamWrapper) { + dest._end(); + } else if (dest instanceof WritableStream) { + const writer = dest.getWriter(); + writer.close(); + } else { + dest.end(); + } + }); + + this.on("error", async (error) => { + if (dest instanceof Node18UniversalStreamWrapper) { + dest._error(error); + } else if (dest instanceof WritableStream) { + const writer = dest.getWriter(); + writer.abort(error); + } else { + dest.destroy(error); + } + }); + + this._startReading(); + + return dest; + } + + public pipeTo( + dest: Node18UniversalStreamWrapper | Writable | WritableStream + ): Node18UniversalStreamWrapper | Writable | WritableStream { + return this.pipe(dest); + } + + public unpipe(dest: Node18UniversalStreamWrapper | Writable | WritableStream): void { + this.off("data", async (chunk) => { + if (dest instanceof Node18UniversalStreamWrapper) { + dest._write(chunk); + } else if (dest instanceof WritableStream) { + const writer = dest.getWriter(); + writer.write(chunk).then(() => writer.releaseLock()); + } else { + dest.write(chunk); + } + }); + + this.off("end", async () => { + if (dest instanceof Node18UniversalStreamWrapper) { + dest._end(); + } else if (dest instanceof WritableStream) { + const writer = dest.getWriter(); + writer.close(); + } else { + dest.end(); + } + }); + + this.off("error", async (error) => { + if (dest instanceof Node18UniversalStreamWrapper) { + dest._error(error); + } else if (dest instanceof WritableStream) { + const writer = dest.getWriter(); + writer.abort(error); + } else { + dest.destroy(error); + } + }); + } + + public destroy(error?: Error): void { + this.reader + .cancel(error) + .then(() => { + this._emit("close"); + }) + .catch((err) => { + this._emit("error", err); + }); + } + + public pause(): void { + this.paused = true; + this._emit("pause"); + } + + public resume(): void { + if (this.paused) { + this.paused = false; + this._emit("resume"); + if (this.resumeCallback) { + this.resumeCallback(); + this.resumeCallback = null; + } + } + } + + public get isPaused(): boolean { + return this.paused; + } + + public async read(): Promise { + if (this.paused) { + await new Promise((resolve) => { + this.resumeCallback = resolve; + }); + } + const { done, value } = await this.reader.read(); + + if (done) { + return undefined; + } + return value; + } + + public setEncoding(encoding: string): void { + this.encoding = encoding; + } + + public async text(): Promise { + const chunks: ReadFormat[] = []; + + while (true) { + const { done, value } = await this.reader.read(); + if (done) break; + if (value) chunks.push(value); + } + + const decoder = new TextDecoder(this.encoding || "utf-8"); + return decoder.decode(await new Blob(chunks).arrayBuffer()); + } + + public async json(): Promise { + const text = await this.text(); + return JSON.parse(text); + } + + private _write(chunk: ReadFormat): void { + this._emit("data", chunk); + } + + private _end(): void { + this._emit("end"); + } + + private _error(error: any): void { + this._emit("error", error); + } + + private _emit(event: string, data?: any): void { + if (this.events[event]) { + for (const callback of this.events[event] || []) { + callback(data); + } + } + } + + private async _startReading(): Promise { + try { + this._emit("readable"); + while (true) { + if (this.paused) { + await new Promise((resolve) => { + this.resumeCallback = resolve; + }); + } + const { done, value } = await this.reader.read(); + if (done) { + this._emit("end"); + this._emit("close"); + break; + } + if (value) { + this._emit("data", value); + } + } + } catch (error) { + this._emit("error", error); + } + } + + [Symbol.asyncIterator](): AsyncIterableIterator { + return { + next: async () => { + if (this.paused) { + await new Promise((resolve) => { + this.resumeCallback = resolve; + }); + } + const { done, value } = await this.reader.read(); + if (done) { + return { done: true, value: undefined }; + } + return { done: false, value }; + }, + [Symbol.asyncIterator]() { + return this; + }, + }; + } +} diff --git a/seed/ts-sdk/grpc-proto/src/core/fetcher/stream-wrappers/NodePre18StreamWrapper.ts b/seed/ts-sdk/grpc-proto/src/core/fetcher/stream-wrappers/NodePre18StreamWrapper.ts new file mode 100644 index 00000000000..f9bead21841 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/fetcher/stream-wrappers/NodePre18StreamWrapper.ts @@ -0,0 +1,106 @@ +import type { Readable, Writable } from "stream"; +import { EventCallback, StreamWrapper } from "./chooseStreamWrapper"; + +export class NodePre18StreamWrapper implements StreamWrapper { + private readableStream: Readable; + private encoding: string | undefined; + + constructor(readableStream: Readable) { + this.readableStream = readableStream; + } + + public on(event: string, callback: EventCallback): void { + this.readableStream.on(event, callback); + } + + public off(event: string, callback: EventCallback): void { + this.readableStream.off(event, callback); + } + + public pipe(dest: Writable): Writable { + this.readableStream.pipe(dest); + return dest; + } + + public pipeTo(dest: Writable): Writable { + return this.pipe(dest); + } + + public unpipe(dest?: Writable): void { + if (dest) { + this.readableStream.unpipe(dest); + } else { + this.readableStream.unpipe(); + } + } + + public destroy(error?: Error): void { + this.readableStream.destroy(error); + } + + public pause(): void { + this.readableStream.pause(); + } + + public resume(): void { + this.readableStream.resume(); + } + + public get isPaused(): boolean { + return this.readableStream.isPaused(); + } + + public async read(): Promise { + return new Promise((resolve, reject) => { + const chunk = this.readableStream.read(); + if (chunk) { + resolve(chunk); + } else { + this.readableStream.once("readable", () => { + const chunk = this.readableStream.read(); + resolve(chunk); + }); + this.readableStream.once("error", reject); + } + }); + } + + public setEncoding(encoding?: string): void { + this.readableStream.setEncoding(encoding as BufferEncoding); + this.encoding = encoding; + } + + public async text(): Promise { + const chunks: Uint8Array[] = []; + const encoder = new TextEncoder(); + this.readableStream.setEncoding((this.encoding || "utf-8") as BufferEncoding); + + for await (const chunk of this.readableStream) { + chunks.push(encoder.encode(chunk)); + } + + const decoder = new TextDecoder(this.encoding || "utf-8"); + return decoder.decode(Buffer.concat(chunks)); + } + + public async json(): Promise { + const text = await this.text(); + return JSON.parse(text); + } + + public [Symbol.asyncIterator](): AsyncIterableIterator { + const readableStream = this.readableStream; + const iterator = readableStream[Symbol.asyncIterator](); + + // Create and return an async iterator that yields buffers + return { + async next(): Promise> { + const { value, done } = await iterator.next(); + return { value: value as Buffer, done }; + }, + [Symbol.asyncIterator]() { + return this; + }, + }; + } +} diff --git a/seed/ts-sdk/grpc-proto/src/core/fetcher/stream-wrappers/UndiciStreamWrapper.ts b/seed/ts-sdk/grpc-proto/src/core/fetcher/stream-wrappers/UndiciStreamWrapper.ts new file mode 100644 index 00000000000..7a52805de80 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/fetcher/stream-wrappers/UndiciStreamWrapper.ts @@ -0,0 +1,239 @@ +import { StreamWrapper } from "./chooseStreamWrapper"; + +type EventCallback = (data?: any) => void; + +export class UndiciStreamWrapper + implements StreamWrapper | WritableStream, ReadFormat> +{ + private readableStream: ReadableStream; + private reader: ReadableStreamDefaultReader; + private events: Record; + private paused: boolean; + private resumeCallback: ((value?: unknown) => void) | null; + private encoding: string | null; + + constructor(readableStream: ReadableStream) { + this.readableStream = readableStream; + this.reader = this.readableStream.getReader(); + this.events = { + data: [], + end: [], + error: [], + readable: [], + close: [], + pause: [], + resume: [], + }; + this.paused = false; + this.resumeCallback = null; + this.encoding = null; + } + + public on(event: string, callback: EventCallback): void { + this.events[event]?.push(callback); + } + + public off(event: string, callback: EventCallback): void { + this.events[event] = this.events[event]?.filter((cb) => cb !== callback); + } + + public pipe( + dest: UndiciStreamWrapper | WritableStream + ): UndiciStreamWrapper | WritableStream { + this.on("data", (chunk) => { + if (dest instanceof UndiciStreamWrapper) { + dest._write(chunk); + } else { + const writer = dest.getWriter(); + writer.write(chunk).then(() => writer.releaseLock()); + } + }); + + this.on("end", () => { + if (dest instanceof UndiciStreamWrapper) { + dest._end(); + } else { + const writer = dest.getWriter(); + writer.close(); + } + }); + + this.on("error", (error) => { + if (dest instanceof UndiciStreamWrapper) { + dest._error(error); + } else { + const writer = dest.getWriter(); + writer.abort(error); + } + }); + + this._startReading(); + + return dest; + } + + public pipeTo( + dest: UndiciStreamWrapper | WritableStream + ): UndiciStreamWrapper | WritableStream { + return this.pipe(dest); + } + + public unpipe(dest: UndiciStreamWrapper | WritableStream): void { + this.off("data", (chunk) => { + if (dest instanceof UndiciStreamWrapper) { + dest._write(chunk); + } else { + const writer = dest.getWriter(); + writer.write(chunk).then(() => writer.releaseLock()); + } + }); + + this.off("end", () => { + if (dest instanceof UndiciStreamWrapper) { + dest._end(); + } else { + const writer = dest.getWriter(); + writer.close(); + } + }); + + this.off("error", (error) => { + if (dest instanceof UndiciStreamWrapper) { + dest._error(error); + } else { + const writer = dest.getWriter(); + writer.abort(error); + } + }); + } + + public destroy(error?: Error): void { + this.reader + .cancel(error) + .then(() => { + this._emit("close"); + }) + .catch((err) => { + this._emit("error", err); + }); + } + + public pause(): void { + this.paused = true; + this._emit("pause"); + } + + public resume(): void { + if (this.paused) { + this.paused = false; + this._emit("resume"); + if (this.resumeCallback) { + this.resumeCallback(); + this.resumeCallback = null; + } + } + } + + public get isPaused(): boolean { + return this.paused; + } + + public async read(): Promise { + if (this.paused) { + await new Promise((resolve) => { + this.resumeCallback = resolve; + }); + } + const { done, value } = await this.reader.read(); + if (done) { + return undefined; + } + return value; + } + + public setEncoding(encoding: string): void { + this.encoding = encoding; + } + + public async text(): Promise { + const chunks: BlobPart[] = []; + + while (true) { + const { done, value } = await this.reader.read(); + if (done) break; + if (value) chunks.push(value); + } + + const decoder = new TextDecoder(this.encoding || "utf-8"); + return decoder.decode(await new Blob(chunks).arrayBuffer()); + } + + public async json(): Promise { + const text = await this.text(); + return JSON.parse(text); + } + + private _write(chunk: ReadFormat): void { + this._emit("data", chunk); + } + + private _end(): void { + this._emit("end"); + } + + private _error(error: any): void { + this._emit("error", error); + } + + private _emit(event: string, data?: any): void { + if (this.events[event]) { + for (const callback of this.events[event] || []) { + callback(data); + } + } + } + + private async _startReading(): Promise { + try { + this._emit("readable"); + while (true) { + if (this.paused) { + await new Promise((resolve) => { + this.resumeCallback = resolve; + }); + } + const { done, value } = await this.reader.read(); + if (done) { + this._emit("end"); + this._emit("close"); + break; + } + if (value) { + this._emit("data", value); + } + } + } catch (error) { + this._emit("error", error); + } + } + + [Symbol.asyncIterator](): AsyncIterableIterator { + return { + next: async () => { + if (this.paused) { + await new Promise((resolve) => { + this.resumeCallback = resolve; + }); + } + const { done, value } = await this.reader.read(); + if (done) { + return { done: true, value: undefined }; + } + return { done: false, value }; + }, + [Symbol.asyncIterator]() { + return this; + }, + }; + } +} diff --git a/seed/ts-sdk/grpc-proto/src/core/fetcher/stream-wrappers/chooseStreamWrapper.ts b/seed/ts-sdk/grpc-proto/src/core/fetcher/stream-wrappers/chooseStreamWrapper.ts new file mode 100644 index 00000000000..d60991da089 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/fetcher/stream-wrappers/chooseStreamWrapper.ts @@ -0,0 +1,33 @@ +import type { Readable } from "stream"; +import { RUNTIME } from "../../runtime"; + +export type EventCallback = (data?: any) => void; + +export interface StreamWrapper { + setEncoding(encoding?: string): void; + on(event: string, callback: EventCallback): void; + off(event: string, callback: EventCallback): void; + pipe(dest: WritableStream): WritableStream; + pipeTo(dest: WritableStream): WritableStream; + unpipe(dest?: WritableStream): void; + destroy(error?: Error): void; + pause(): void; + resume(): void; + get isPaused(): boolean; + read(): Promise; + text(): Promise; + json(): Promise; + [Symbol.asyncIterator](): AsyncIterableIterator; +} + +export async function chooseStreamWrapper(responseBody: any): Promise>> { + if (RUNTIME.type === "node" && RUNTIME.parsedVersion != null && RUNTIME.parsedVersion >= 18) { + return new (await import("./Node18UniversalStreamWrapper")).Node18UniversalStreamWrapper( + responseBody as ReadableStream + ); + } else if (RUNTIME.type !== "node" && typeof fetch == "function") { + return new (await import("./UndiciStreamWrapper")).UndiciStreamWrapper(responseBody as ReadableStream); + } else { + return new (await import("./NodePre18StreamWrapper")).NodePre18StreamWrapper(responseBody as Readable); + } +} diff --git a/seed/ts-sdk/grpc-proto/src/core/index.ts b/seed/ts-sdk/grpc-proto/src/core/index.ts new file mode 100644 index 00000000000..e3006860f4d --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/index.ts @@ -0,0 +1,3 @@ +export * from "./fetcher"; +export * from "./runtime"; +export * as serialization from "./schemas"; diff --git a/seed/ts-sdk/grpc-proto/src/core/runtime/index.ts b/seed/ts-sdk/grpc-proto/src/core/runtime/index.ts new file mode 100644 index 00000000000..5c76dbb133f --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/runtime/index.ts @@ -0,0 +1 @@ +export { RUNTIME } from "./runtime"; diff --git a/seed/ts-sdk/grpc-proto/src/core/runtime/runtime.ts b/seed/ts-sdk/grpc-proto/src/core/runtime/runtime.ts new file mode 100644 index 00000000000..4d0687e8eb4 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/runtime/runtime.ts @@ -0,0 +1,126 @@ +interface DenoGlobal { + version: { + deno: string; + }; +} + +interface BunGlobal { + version: string; +} + +declare const Deno: DenoGlobal; +declare const Bun: BunGlobal; + +/** + * A constant that indicates whether the environment the code is running is a Web Browser. + */ +const isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined"; + +/** + * A constant that indicates whether the environment the code is running is a Web Worker. + */ +const isWebWorker = + typeof self === "object" && + // @ts-ignore + typeof self?.importScripts === "function" && + (self.constructor?.name === "DedicatedWorkerGlobalScope" || + self.constructor?.name === "ServiceWorkerGlobalScope" || + self.constructor?.name === "SharedWorkerGlobalScope"); + +/** + * A constant that indicates whether the environment the code is running is Deno. + */ +const isDeno = + typeof Deno !== "undefined" && typeof Deno.version !== "undefined" && typeof Deno.version.deno !== "undefined"; + +/** + * A constant that indicates whether the environment the code is running is Bun.sh. + */ +const isBun = typeof Bun !== "undefined" && typeof Bun.version !== "undefined"; + +/** + * A constant that indicates whether the environment the code is running is Node.JS. + */ +const isNode = + typeof process !== "undefined" && + Boolean(process.version) && + Boolean(process.versions?.node) && + // Deno spoofs process.versions.node, see https://deno.land/std@0.177.0/node/process.ts?s=versions + !isDeno && + !isBun; + +/** + * A constant that indicates whether the environment the code is running is in React-Native. + * https://github.com/facebook/react-native/blob/main/packages/react-native/Libraries/Core/setUpNavigator.js + */ +const isReactNative = typeof navigator !== "undefined" && navigator?.product === "ReactNative"; + +/** + * A constant that indicates whether the environment the code is running is Cloudflare. + * https://developers.cloudflare.com/workers/runtime-apis/web-standards/#navigatoruseragent + */ +const isCloudflare = typeof globalThis !== "undefined" && globalThis?.navigator?.userAgent === "Cloudflare-Workers"; + +/** + * A constant that indicates which environment and version the SDK is running in. + */ +export const RUNTIME: Runtime = evaluateRuntime(); + +export interface Runtime { + type: "browser" | "web-worker" | "deno" | "bun" | "node" | "react-native" | "unknown" | "workerd"; + version?: string; + parsedVersion?: number; +} + +function evaluateRuntime(): Runtime { + if (isBrowser) { + return { + type: "browser", + version: window.navigator.userAgent, + }; + } + + if (isCloudflare) { + return { + type: "workerd", + }; + } + + if (isWebWorker) { + return { + type: "web-worker", + }; + } + + if (isDeno) { + return { + type: "deno", + version: Deno.version.deno, + }; + } + + if (isBun) { + return { + type: "bun", + version: Bun.version, + }; + } + + if (isNode) { + return { + type: "node", + version: process.versions.node, + parsedVersion: Number(process.versions.node.split(".")[0]), + }; + } + + if (isReactNative) { + return { + type: "react-native", + }; + } + + return { + type: "unknown", + }; +} diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/Schema.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/Schema.ts new file mode 100644 index 00000000000..19acc5dc44b --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/Schema.ts @@ -0,0 +1,98 @@ +import { SchemaUtils } from "./builders"; + +export type Schema = BaseSchema & SchemaUtils; + +export type inferRaw = S extends Schema ? Raw : never; +export type inferParsed = S extends Schema ? Parsed : never; + +export interface BaseSchema { + parse: (raw: unknown, opts?: SchemaOptions) => MaybeValid; + json: (parsed: unknown, opts?: SchemaOptions) => MaybeValid; + getType: () => SchemaType | SchemaType; +} + +export const SchemaType = { + DATE: "date", + ENUM: "enum", + LIST: "list", + STRING_LITERAL: "stringLiteral", + BOOLEAN_LITERAL: "booleanLiteral", + OBJECT: "object", + ANY: "any", + BOOLEAN: "boolean", + NUMBER: "number", + STRING: "string", + UNKNOWN: "unknown", + RECORD: "record", + SET: "set", + UNION: "union", + UNDISCRIMINATED_UNION: "undiscriminatedUnion", + OPTIONAL: "optional", +} as const; +export type SchemaType = typeof SchemaType[keyof typeof SchemaType]; + +export type MaybeValid = Valid | Invalid; + +export interface Valid { + ok: true; + value: T; +} + +export interface Invalid { + ok: false; + errors: ValidationError[]; +} + +export interface ValidationError { + path: string[]; + message: string; +} + +export interface SchemaOptions { + /** + * how to handle unrecognized keys in objects + * + * @default "fail" + */ + unrecognizedObjectKeys?: "fail" | "passthrough" | "strip"; + + /** + * whether to fail when an unrecognized discriminant value is + * encountered in a union + * + * @default false + */ + allowUnrecognizedUnionMembers?: boolean; + + /** + * whether to fail when an unrecognized enum value is encountered + * + * @default false + */ + allowUnrecognizedEnumValues?: boolean; + + /** + * whether to allow data that doesn't conform to the schema. + * invalid data is passed through without transformation. + * + * when this is enabled, .parse() and .json() will always + * return `ok: true`. `.parseOrThrow()` and `.jsonOrThrow()` + * will never fail. + * + * @default false + */ + skipValidation?: boolean; + + /** + * each validation failure contains a "path" property, which is + * the breadcrumbs to the offending node in the JSON. you can supply + * a prefix that is prepended to all the errors' paths. this can be + * helpful for zurg's internal debug logging. + */ + breadcrumbsPrefix?: string[]; + + /** + * whether to send 'null' for optional properties explicitly set to 'undefined'. + */ + omitUndefined?: boolean; +} diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/builders/date/date.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/date/date.ts new file mode 100644 index 00000000000..b70f24b045a --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/date/date.ts @@ -0,0 +1,65 @@ +import { BaseSchema, Schema, SchemaType } from "../../Schema"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { getSchemaUtils } from "../schema-utils"; + +// https://stackoverflow.com/questions/12756159/regex-and-iso8601-formatted-datetime +const ISO_8601_REGEX = + /^([+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24:?00)([.,]\d+(?!:))?)?(\17[0-5]\d([.,]\d+)?)?([zZ]|([+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/; + +export function date(): Schema { + const baseSchema: BaseSchema = { + parse: (raw, { breadcrumbsPrefix = [] } = {}) => { + if (typeof raw !== "string") { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(raw, "string"), + }, + ], + }; + } + if (!ISO_8601_REGEX.test(raw)) { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(raw, "ISO 8601 date string"), + }, + ], + }; + } + return { + ok: true, + value: new Date(raw), + }; + }, + json: (date, { breadcrumbsPrefix = [] } = {}) => { + if (date instanceof Date) { + return { + ok: true, + value: date.toISOString(), + }; + } else { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(date, "Date object"), + }, + ], + }; + } + }, + getType: () => SchemaType.DATE, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + }; +} diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/builders/date/index.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/date/index.ts new file mode 100644 index 00000000000..187b29040f6 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/date/index.ts @@ -0,0 +1 @@ +export { date } from "./date"; diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/builders/enum/enum.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/enum/enum.ts new file mode 100644 index 00000000000..c1e24d69dec --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/enum/enum.ts @@ -0,0 +1,43 @@ +import { Schema, SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; + +export function enum_(values: E): Schema { + const validValues = new Set(values); + + const schemaCreator = createIdentitySchemaCreator( + SchemaType.ENUM, + (value, { allowUnrecognizedEnumValues, breadcrumbsPrefix = [] } = {}) => { + if (typeof value !== "string") { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "string"), + }, + ], + }; + } + + if (!validValues.has(value) && !allowUnrecognizedEnumValues) { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "enum"), + }, + ], + }; + } + + return { + ok: true, + value: value as U, + }; + } + ); + + return schemaCreator(); +} diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/builders/enum/index.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/enum/index.ts new file mode 100644 index 00000000000..fe6faed93e3 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/enum/index.ts @@ -0,0 +1 @@ +export { enum_ } from "./enum"; diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/builders/index.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/index.ts new file mode 100644 index 00000000000..050cd2c4efb --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/index.ts @@ -0,0 +1,13 @@ +export * from "./date"; +export * from "./enum"; +export * from "./lazy"; +export * from "./list"; +export * from "./literals"; +export * from "./object"; +export * from "./object-like"; +export * from "./primitives"; +export * from "./record"; +export * from "./schema-utils"; +export * from "./set"; +export * from "./undiscriminated-union"; +export * from "./union"; diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/builders/lazy/index.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/lazy/index.ts new file mode 100644 index 00000000000..77420fb031c --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/lazy/index.ts @@ -0,0 +1,3 @@ +export { lazy } from "./lazy"; +export type { SchemaGetter } from "./lazy"; +export { lazyObject } from "./lazyObject"; diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/builders/lazy/lazy.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/lazy/lazy.ts new file mode 100644 index 00000000000..835c61f8a56 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/lazy/lazy.ts @@ -0,0 +1,32 @@ +import { BaseSchema, Schema } from "../../Schema"; +import { getSchemaUtils } from "../schema-utils"; + +export type SchemaGetter> = () => SchemaType; + +export function lazy(getter: SchemaGetter>): Schema { + const baseSchema = constructLazyBaseSchema(getter); + return { + ...baseSchema, + ...getSchemaUtils(baseSchema), + }; +} + +export function constructLazyBaseSchema( + getter: SchemaGetter> +): BaseSchema { + return { + parse: (raw, opts) => getMemoizedSchema(getter).parse(raw, opts), + json: (parsed, opts) => getMemoizedSchema(getter).json(parsed, opts), + getType: () => getMemoizedSchema(getter).getType(), + }; +} + +type MemoizedGetter> = SchemaGetter & { __zurg_memoized?: SchemaType }; + +export function getMemoizedSchema>(getter: SchemaGetter): SchemaType { + const castedGetter = getter as MemoizedGetter; + if (castedGetter.__zurg_memoized == null) { + castedGetter.__zurg_memoized = getter(); + } + return castedGetter.__zurg_memoized; +} diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/builders/lazy/lazyObject.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/lazy/lazyObject.ts new file mode 100644 index 00000000000..38c9e28404b --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/lazy/lazyObject.ts @@ -0,0 +1,20 @@ +import { getObjectUtils } from "../object"; +import { getObjectLikeUtils } from "../object-like"; +import { BaseObjectSchema, ObjectSchema } from "../object/types"; +import { getSchemaUtils } from "../schema-utils"; +import { constructLazyBaseSchema, getMemoizedSchema, SchemaGetter } from "./lazy"; + +export function lazyObject(getter: SchemaGetter>): ObjectSchema { + const baseSchema: BaseObjectSchema = { + ...constructLazyBaseSchema(getter), + _getRawProperties: () => getMemoizedSchema(getter)._getRawProperties(), + _getParsedProperties: () => getMemoizedSchema(getter)._getParsedProperties(), + }; + + return { + ...baseSchema, + ...getSchemaUtils(baseSchema), + ...getObjectLikeUtils(baseSchema), + ...getObjectUtils(baseSchema), + }; +} diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/builders/list/index.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/list/index.ts new file mode 100644 index 00000000000..25f4bcc1737 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/list/index.ts @@ -0,0 +1 @@ +export { list } from "./list"; diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/builders/list/list.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/list/list.ts new file mode 100644 index 00000000000..e4c5c4a4a99 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/list/list.ts @@ -0,0 +1,73 @@ +import { BaseSchema, MaybeValid, Schema, SchemaType, ValidationError } from "../../Schema"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { getSchemaUtils } from "../schema-utils"; + +export function list(schema: Schema): Schema { + const baseSchema: BaseSchema = { + parse: (raw, opts) => + validateAndTransformArray(raw, (item, index) => + schema.parse(item, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `[${index}]`], + }) + ), + json: (parsed, opts) => + validateAndTransformArray(parsed, (item, index) => + schema.json(item, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `[${index}]`], + }) + ), + getType: () => SchemaType.LIST, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + }; +} + +function validateAndTransformArray( + value: unknown, + transformItem: (item: Raw, index: number) => MaybeValid +): MaybeValid { + if (!Array.isArray(value)) { + return { + ok: false, + errors: [ + { + message: getErrorMessageForIncorrectType(value, "list"), + path: [], + }, + ], + }; + } + + const maybeValidItems = value.map((item, index) => transformItem(item, index)); + + return maybeValidItems.reduce>( + (acc, item) => { + if (acc.ok && item.ok) { + return { + ok: true, + value: [...acc.value, item.value], + }; + } + + const errors: ValidationError[] = []; + if (!acc.ok) { + errors.push(...acc.errors); + } + if (!item.ok) { + errors.push(...item.errors); + } + + return { + ok: false, + errors, + }; + }, + { ok: true, value: [] } + ); +} diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/builders/literals/booleanLiteral.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/literals/booleanLiteral.ts new file mode 100644 index 00000000000..a83d22cd48a --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/literals/booleanLiteral.ts @@ -0,0 +1,29 @@ +import { Schema, SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; + +export function booleanLiteral(literal: V): Schema { + const schemaCreator = createIdentitySchemaCreator( + SchemaType.BOOLEAN_LITERAL, + (value, { breadcrumbsPrefix = [] } = {}) => { + if (value === literal) { + return { + ok: true, + value: literal, + }; + } else { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, `${literal.toString()}`), + }, + ], + }; + } + } + ); + + return schemaCreator(); +} diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/builders/literals/index.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/literals/index.ts new file mode 100644 index 00000000000..d2bf08fc6ca --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/literals/index.ts @@ -0,0 +1,2 @@ +export { stringLiteral } from "./stringLiteral"; +export { booleanLiteral } from "./booleanLiteral"; diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/builders/literals/stringLiteral.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/literals/stringLiteral.ts new file mode 100644 index 00000000000..3939b76b48d --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/literals/stringLiteral.ts @@ -0,0 +1,29 @@ +import { Schema, SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; + +export function stringLiteral(literal: V): Schema { + const schemaCreator = createIdentitySchemaCreator( + SchemaType.STRING_LITERAL, + (value, { breadcrumbsPrefix = [] } = {}) => { + if (value === literal) { + return { + ok: true, + value: literal, + }; + } else { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, `"${literal}"`), + }, + ], + }; + } + } + ); + + return schemaCreator(); +} diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/builders/object-like/getObjectLikeUtils.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/object-like/getObjectLikeUtils.ts new file mode 100644 index 00000000000..8331d08da89 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/object-like/getObjectLikeUtils.ts @@ -0,0 +1,79 @@ +import { BaseSchema } from "../../Schema"; +import { filterObject } from "../../utils/filterObject"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { isPlainObject } from "../../utils/isPlainObject"; +import { getSchemaUtils } from "../schema-utils"; +import { ObjectLikeSchema, ObjectLikeUtils } from "./types"; + +export function getObjectLikeUtils(schema: BaseSchema): ObjectLikeUtils { + return { + withParsedProperties: (properties) => withParsedProperties(schema, properties), + }; +} + +/** + * object-like utils are defined in one file to resolve issues with circular imports + */ + +export function withParsedProperties( + objectLike: BaseSchema, + properties: { [K in keyof Properties]: Properties[K] | ((parsed: ParsedObjectShape) => Properties[K]) } +): ObjectLikeSchema { + const objectSchema: BaseSchema = { + parse: (raw, opts) => { + const parsedObject = objectLike.parse(raw, opts); + if (!parsedObject.ok) { + return parsedObject; + } + + const additionalProperties = Object.entries(properties).reduce>( + (processed, [key, value]) => { + return { + ...processed, + [key]: typeof value === "function" ? value(parsedObject.value) : value, + }; + }, + {} + ); + + return { + ok: true, + value: { + ...parsedObject.value, + ...(additionalProperties as Properties), + }, + }; + }, + + json: (parsed, opts) => { + if (!isPlainObject(parsed)) { + return { + ok: false, + errors: [ + { + path: opts?.breadcrumbsPrefix ?? [], + message: getErrorMessageForIncorrectType(parsed, "object"), + }, + ], + }; + } + + // strip out added properties + const addedPropertyKeys = new Set(Object.keys(properties)); + const parsedWithoutAddedProperties = filterObject( + parsed, + Object.keys(parsed).filter((key) => !addedPropertyKeys.has(key)) + ); + + return objectLike.json(parsedWithoutAddedProperties as ParsedObjectShape, opts); + }, + + getType: () => objectLike.getType(), + }; + + return { + ...objectSchema, + ...getSchemaUtils(objectSchema), + ...getObjectLikeUtils(objectSchema), + }; +} diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/builders/object-like/index.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/object-like/index.ts new file mode 100644 index 00000000000..c342e72cf9d --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/object-like/index.ts @@ -0,0 +1,2 @@ +export { getObjectLikeUtils, withParsedProperties } from "./getObjectLikeUtils"; +export type { ObjectLikeSchema, ObjectLikeUtils } from "./types"; diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/builders/object-like/types.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/object-like/types.ts new file mode 100644 index 00000000000..75b3698729c --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/object-like/types.ts @@ -0,0 +1,11 @@ +import { BaseSchema, Schema } from "../../Schema"; + +export type ObjectLikeSchema = Schema & + BaseSchema & + ObjectLikeUtils; + +export interface ObjectLikeUtils { + withParsedProperties: >(properties: { + [K in keyof T]: T[K] | ((parsed: Parsed) => T[K]); + }) => ObjectLikeSchema; +} diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/builders/object/index.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/object/index.ts new file mode 100644 index 00000000000..e3f4388db28 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/object/index.ts @@ -0,0 +1,22 @@ +export { getObjectUtils, object } from "./object"; +export { objectWithoutOptionalProperties } from "./objectWithoutOptionalProperties"; +export type { + inferObjectWithoutOptionalPropertiesSchemaFromPropertySchemas, + inferParsedObjectWithoutOptionalPropertiesFromPropertySchemas, +} from "./objectWithoutOptionalProperties"; +export { isProperty, property } from "./property"; +export type { Property } from "./property"; +export type { + BaseObjectSchema, + inferObjectSchemaFromPropertySchemas, + inferParsedObject, + inferParsedObjectFromPropertySchemas, + inferParsedPropertySchema, + inferRawKey, + inferRawObject, + inferRawObjectFromPropertySchemas, + inferRawPropertySchema, + ObjectSchema, + ObjectUtils, + PropertySchemas, +} from "./types"; diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/builders/object/object.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/object/object.ts new file mode 100644 index 00000000000..e00136d72fc --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/object/object.ts @@ -0,0 +1,324 @@ +import { MaybeValid, Schema, SchemaType, ValidationError } from "../../Schema"; +import { entries } from "../../utils/entries"; +import { filterObject } from "../../utils/filterObject"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { isPlainObject } from "../../utils/isPlainObject"; +import { keys } from "../../utils/keys"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { partition } from "../../utils/partition"; +import { getObjectLikeUtils } from "../object-like"; +import { getSchemaUtils } from "../schema-utils"; +import { isProperty } from "./property"; +import { + BaseObjectSchema, + inferObjectSchemaFromPropertySchemas, + inferParsedObjectFromPropertySchemas, + inferRawObjectFromPropertySchemas, + ObjectSchema, + ObjectUtils, + PropertySchemas, +} from "./types"; + +interface ObjectPropertyWithRawKey { + rawKey: string; + parsedKey: string; + valueSchema: Schema; +} + +export function object>( + schemas: T +): inferObjectSchemaFromPropertySchemas { + const baseSchema: BaseObjectSchema< + inferRawObjectFromPropertySchemas, + inferParsedObjectFromPropertySchemas + > = { + _getRawProperties: () => + Object.entries(schemas).map(([parsedKey, propertySchema]) => + isProperty(propertySchema) ? propertySchema.rawKey : parsedKey + ) as unknown as (keyof inferRawObjectFromPropertySchemas)[], + _getParsedProperties: () => keys(schemas) as unknown as (keyof inferParsedObjectFromPropertySchemas)[], + + parse: (raw, opts) => { + const rawKeyToProperty: Record = {}; + const requiredKeys: string[] = []; + + for (const [parsedKey, schemaOrObjectProperty] of entries(schemas)) { + const rawKey = isProperty(schemaOrObjectProperty) ? schemaOrObjectProperty.rawKey : parsedKey; + const valueSchema: Schema = isProperty(schemaOrObjectProperty) + ? schemaOrObjectProperty.valueSchema + : schemaOrObjectProperty; + + const property: ObjectPropertyWithRawKey = { + rawKey, + parsedKey: parsedKey as string, + valueSchema, + }; + + rawKeyToProperty[rawKey] = property; + + if (isSchemaRequired(valueSchema)) { + requiredKeys.push(rawKey); + } + } + + return validateAndTransformObject({ + value: raw, + requiredKeys, + getProperty: (rawKey) => { + const property = rawKeyToProperty[rawKey]; + if (property == null) { + return undefined; + } + return { + transformedKey: property.parsedKey, + transform: (propertyValue) => + property.valueSchema.parse(propertyValue, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), rawKey], + }), + }; + }, + unrecognizedObjectKeys: opts?.unrecognizedObjectKeys, + skipValidation: opts?.skipValidation, + breadcrumbsPrefix: opts?.breadcrumbsPrefix, + omitUndefined: opts?.omitUndefined, + }); + }, + + json: (parsed, opts) => { + const requiredKeys: string[] = []; + + for (const [parsedKey, schemaOrObjectProperty] of entries(schemas)) { + const valueSchema: Schema = isProperty(schemaOrObjectProperty) + ? schemaOrObjectProperty.valueSchema + : schemaOrObjectProperty; + + if (isSchemaRequired(valueSchema)) { + requiredKeys.push(parsedKey as string); + } + } + + return validateAndTransformObject({ + value: parsed, + requiredKeys, + getProperty: ( + parsedKey + ): { transformedKey: string; transform: (propertyValue: unknown) => MaybeValid } | undefined => { + const property = schemas[parsedKey as keyof T]; + + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (property == null) { + return undefined; + } + + if (isProperty(property)) { + return { + transformedKey: property.rawKey, + transform: (propertyValue) => + property.valueSchema.json(propertyValue, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), parsedKey], + }), + }; + } else { + return { + transformedKey: parsedKey, + transform: (propertyValue) => + property.json(propertyValue, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), parsedKey], + }), + }; + } + }, + unrecognizedObjectKeys: opts?.unrecognizedObjectKeys, + skipValidation: opts?.skipValidation, + breadcrumbsPrefix: opts?.breadcrumbsPrefix, + omitUndefined: opts?.omitUndefined, + }); + }, + + getType: () => SchemaType.OBJECT, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + ...getObjectLikeUtils(baseSchema), + ...getObjectUtils(baseSchema), + }; +} + +function validateAndTransformObject({ + value, + requiredKeys, + getProperty, + unrecognizedObjectKeys = "fail", + skipValidation = false, + breadcrumbsPrefix = [], +}: { + value: unknown; + requiredKeys: string[]; + getProperty: ( + preTransformedKey: string + ) => { transformedKey: string; transform: (propertyValue: unknown) => MaybeValid } | undefined; + unrecognizedObjectKeys: "fail" | "passthrough" | "strip" | undefined; + skipValidation: boolean | undefined; + breadcrumbsPrefix: string[] | undefined; + omitUndefined: boolean | undefined; +}): MaybeValid { + if (!isPlainObject(value)) { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "object"), + }, + ], + }; + } + + const missingRequiredKeys = new Set(requiredKeys); + const errors: ValidationError[] = []; + const transformed: Record = {}; + + for (const [preTransformedKey, preTransformedItemValue] of Object.entries(value)) { + const property = getProperty(preTransformedKey); + + if (property != null) { + missingRequiredKeys.delete(preTransformedKey); + + const value = property.transform(preTransformedItemValue); + if (value.ok) { + transformed[property.transformedKey] = value.value; + } else { + transformed[preTransformedKey] = preTransformedItemValue; + errors.push(...value.errors); + } + } else { + switch (unrecognizedObjectKeys) { + case "fail": + errors.push({ + path: [...breadcrumbsPrefix, preTransformedKey], + message: `Unexpected key "${preTransformedKey}"`, + }); + break; + case "strip": + break; + case "passthrough": + transformed[preTransformedKey] = preTransformedItemValue; + break; + } + } + } + + errors.push( + ...requiredKeys + .filter((key) => missingRequiredKeys.has(key)) + .map((key) => ({ + path: breadcrumbsPrefix, + message: `Missing required key "${key}"`, + })) + ); + + if (errors.length === 0 || skipValidation) { + return { + ok: true, + value: transformed as Transformed, + }; + } else { + return { + ok: false, + errors, + }; + } +} + +export function getObjectUtils(schema: BaseObjectSchema): ObjectUtils { + return { + extend: (extension: ObjectSchema) => { + const baseSchema: BaseObjectSchema = { + _getParsedProperties: () => [...schema._getParsedProperties(), ...extension._getParsedProperties()], + _getRawProperties: () => [...schema._getRawProperties(), ...extension._getRawProperties()], + parse: (raw, opts) => { + return validateAndTransformExtendedObject({ + extensionKeys: extension._getRawProperties(), + value: raw, + transformBase: (rawBase) => schema.parse(rawBase, opts), + transformExtension: (rawExtension) => extension.parse(rawExtension, opts), + }); + }, + json: (parsed, opts) => { + return validateAndTransformExtendedObject({ + extensionKeys: extension._getParsedProperties(), + value: parsed, + transformBase: (parsedBase) => schema.json(parsedBase, opts), + transformExtension: (parsedExtension) => extension.json(parsedExtension, opts), + }); + }, + getType: () => SchemaType.OBJECT, + }; + + return { + ...baseSchema, + ...getSchemaUtils(baseSchema), + ...getObjectLikeUtils(baseSchema), + ...getObjectUtils(baseSchema), + }; + }, + }; +} + +function validateAndTransformExtendedObject({ + extensionKeys, + value, + transformBase, + transformExtension, +}: { + extensionKeys: (keyof PreTransformedExtension)[]; + value: unknown; + transformBase: (value: unknown) => MaybeValid; + transformExtension: (value: unknown) => MaybeValid; +}): MaybeValid { + const extensionPropertiesSet = new Set(extensionKeys); + const [extensionProperties, baseProperties] = partition(keys(value), (key) => + extensionPropertiesSet.has(key as keyof PreTransformedExtension) + ); + + const transformedBase = transformBase(filterObject(value, baseProperties)); + const transformedExtension = transformExtension(filterObject(value, extensionProperties)); + + if (transformedBase.ok && transformedExtension.ok) { + return { + ok: true, + value: { + ...transformedBase.value, + ...transformedExtension.value, + }, + }; + } else { + return { + ok: false, + errors: [ + ...(transformedBase.ok ? [] : transformedBase.errors), + ...(transformedExtension.ok ? [] : transformedExtension.errors), + ], + }; + } +} + +function isSchemaRequired(schema: Schema): boolean { + return !isSchemaOptional(schema); +} + +function isSchemaOptional(schema: Schema): boolean { + switch (schema.getType()) { + case SchemaType.ANY: + case SchemaType.UNKNOWN: + case SchemaType.OPTIONAL: + return true; + default: + return false; + } +} diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/builders/object/objectWithoutOptionalProperties.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/object/objectWithoutOptionalProperties.ts new file mode 100644 index 00000000000..a0951f48efc --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/object/objectWithoutOptionalProperties.ts @@ -0,0 +1,18 @@ +import { object } from "./object"; +import { inferParsedPropertySchema, inferRawObjectFromPropertySchemas, ObjectSchema, PropertySchemas } from "./types"; + +export function objectWithoutOptionalProperties>( + schemas: T +): inferObjectWithoutOptionalPropertiesSchemaFromPropertySchemas { + return object(schemas) as unknown as inferObjectWithoutOptionalPropertiesSchemaFromPropertySchemas; +} + +export type inferObjectWithoutOptionalPropertiesSchemaFromPropertySchemas> = + ObjectSchema< + inferRawObjectFromPropertySchemas, + inferParsedObjectWithoutOptionalPropertiesFromPropertySchemas + >; + +export type inferParsedObjectWithoutOptionalPropertiesFromPropertySchemas> = { + [K in keyof T]: inferParsedPropertySchema; +}; diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/builders/object/property.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/object/property.ts new file mode 100644 index 00000000000..d245c4b193a --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/object/property.ts @@ -0,0 +1,23 @@ +import { Schema } from "../../Schema"; + +export function property( + rawKey: RawKey, + valueSchema: Schema +): Property { + return { + rawKey, + valueSchema, + isProperty: true, + }; +} + +export interface Property { + rawKey: RawKey; + valueSchema: Schema; + isProperty: true; +} + +export function isProperty>(maybeProperty: unknown): maybeProperty is O { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + return (maybeProperty as O).isProperty; +} diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/builders/object/types.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/object/types.ts new file mode 100644 index 00000000000..de9bb4074e5 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/object/types.ts @@ -0,0 +1,72 @@ +import { BaseSchema, inferParsed, inferRaw, Schema } from "../../Schema"; +import { addQuestionMarksToNullableProperties } from "../../utils/addQuestionMarksToNullableProperties"; +import { ObjectLikeUtils } from "../object-like"; +import { SchemaUtils } from "../schema-utils"; +import { Property } from "./property"; + +export type ObjectSchema = BaseObjectSchema & + ObjectLikeUtils & + ObjectUtils & + SchemaUtils; + +export interface BaseObjectSchema extends BaseSchema { + _getRawProperties: () => (keyof Raw)[]; + _getParsedProperties: () => (keyof Parsed)[]; +} + +export interface ObjectUtils { + extend: ( + schemas: ObjectSchema + ) => ObjectSchema; +} + +export type inferRawObject> = O extends ObjectSchema ? Raw : never; + +export type inferParsedObject> = O extends ObjectSchema + ? Parsed + : never; + +export type inferObjectSchemaFromPropertySchemas> = ObjectSchema< + inferRawObjectFromPropertySchemas, + inferParsedObjectFromPropertySchemas +>; + +export type inferRawObjectFromPropertySchemas> = + addQuestionMarksToNullableProperties<{ + [ParsedKey in keyof T as inferRawKey]: inferRawPropertySchema; + }>; + +export type inferParsedObjectFromPropertySchemas> = + addQuestionMarksToNullableProperties<{ + [K in keyof T]: inferParsedPropertySchema; + }>; + +export type PropertySchemas = Record< + ParsedKeys, + Property | Schema +>; + +export type inferRawPropertySchema

| Schema> = P extends Property< + any, + infer Raw, + any +> + ? Raw + : P extends Schema + ? inferRaw

+ : never; + +export type inferParsedPropertySchema

| Schema> = P extends Property< + any, + any, + infer Parsed +> + ? Parsed + : P extends Schema + ? inferParsed

+ : never; + +export type inferRawKey< + ParsedKey extends string | number | symbol, + P extends Property | Schema +> = P extends Property ? Raw : ParsedKey; diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/builders/primitives/any.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/primitives/any.ts new file mode 100644 index 00000000000..fcaeb04255a --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/primitives/any.ts @@ -0,0 +1,4 @@ +import { SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; + +export const any = createIdentitySchemaCreator(SchemaType.ANY, (value) => ({ ok: true, value })); diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/builders/primitives/boolean.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/primitives/boolean.ts new file mode 100644 index 00000000000..fad60562120 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/primitives/boolean.ts @@ -0,0 +1,25 @@ +import { SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; + +export const boolean = createIdentitySchemaCreator( + SchemaType.BOOLEAN, + (value, { breadcrumbsPrefix = [] } = {}) => { + if (typeof value === "boolean") { + return { + ok: true, + value, + }; + } else { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "boolean"), + }, + ], + }; + } + } +); diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/builders/primitives/index.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/primitives/index.ts new file mode 100644 index 00000000000..788f9416bfe --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/primitives/index.ts @@ -0,0 +1,5 @@ +export { any } from "./any"; +export { boolean } from "./boolean"; +export { number } from "./number"; +export { string } from "./string"; +export { unknown } from "./unknown"; diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/builders/primitives/number.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/primitives/number.ts new file mode 100644 index 00000000000..c2689456936 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/primitives/number.ts @@ -0,0 +1,25 @@ +import { SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; + +export const number = createIdentitySchemaCreator( + SchemaType.NUMBER, + (value, { breadcrumbsPrefix = [] } = {}) => { + if (typeof value === "number") { + return { + ok: true, + value, + }; + } else { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "number"), + }, + ], + }; + } + } +); diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/builders/primitives/string.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/primitives/string.ts new file mode 100644 index 00000000000..949f1f2a630 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/primitives/string.ts @@ -0,0 +1,25 @@ +import { SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; + +export const string = createIdentitySchemaCreator( + SchemaType.STRING, + (value, { breadcrumbsPrefix = [] } = {}) => { + if (typeof value === "string") { + return { + ok: true, + value, + }; + } else { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "string"), + }, + ], + }; + } + } +); diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/builders/primitives/unknown.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/primitives/unknown.ts new file mode 100644 index 00000000000..4d5249571f5 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/primitives/unknown.ts @@ -0,0 +1,4 @@ +import { SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; + +export const unknown = createIdentitySchemaCreator(SchemaType.UNKNOWN, (value) => ({ ok: true, value })); diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/builders/record/index.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/record/index.ts new file mode 100644 index 00000000000..82e25c5c2af --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/record/index.ts @@ -0,0 +1,2 @@ +export { record } from "./record"; +export type { BaseRecordSchema, RecordSchema } from "./types"; diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/builders/record/record.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/record/record.ts new file mode 100644 index 00000000000..6683ac3609f --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/record/record.ts @@ -0,0 +1,130 @@ +import { MaybeValid, Schema, SchemaType, ValidationError } from "../../Schema"; +import { entries } from "../../utils/entries"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { isPlainObject } from "../../utils/isPlainObject"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { getSchemaUtils } from "../schema-utils"; +import { BaseRecordSchema, RecordSchema } from "./types"; + +export function record( + keySchema: Schema, + valueSchema: Schema +): RecordSchema { + const baseSchema: BaseRecordSchema = { + parse: (raw, opts) => { + return validateAndTransformRecord({ + value: raw, + isKeyNumeric: keySchema.getType() === SchemaType.NUMBER, + transformKey: (key) => + keySchema.parse(key, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `${key} (key)`], + }), + transformValue: (value, key) => + valueSchema.parse(value, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `${key}`], + }), + breadcrumbsPrefix: opts?.breadcrumbsPrefix, + }); + }, + json: (parsed, opts) => { + return validateAndTransformRecord({ + value: parsed, + isKeyNumeric: keySchema.getType() === SchemaType.NUMBER, + transformKey: (key) => + keySchema.json(key, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `${key} (key)`], + }), + transformValue: (value, key) => + valueSchema.json(value, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `${key}`], + }), + breadcrumbsPrefix: opts?.breadcrumbsPrefix, + }); + }, + getType: () => SchemaType.RECORD, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + }; +} + +function validateAndTransformRecord({ + value, + isKeyNumeric, + transformKey, + transformValue, + breadcrumbsPrefix = [], +}: { + value: unknown; + isKeyNumeric: boolean; + transformKey: (key: string | number) => MaybeValid; + transformValue: (value: unknown, key: string | number) => MaybeValid; + breadcrumbsPrefix: string[] | undefined; +}): MaybeValid> { + if (!isPlainObject(value)) { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "object"), + }, + ], + }; + } + + return entries(value).reduce>>( + (accPromise, [stringKey, value]) => { + // skip nullish keys + if (value == null) { + return accPromise; + } + + const acc = accPromise; + + let key: string | number = stringKey; + if (isKeyNumeric) { + const numberKey = stringKey.length > 0 ? Number(stringKey) : NaN; + if (!isNaN(numberKey)) { + key = numberKey; + } + } + const transformedKey = transformKey(key); + + const transformedValue = transformValue(value, key); + + if (acc.ok && transformedKey.ok && transformedValue.ok) { + return { + ok: true, + value: { + ...acc.value, + [transformedKey.value]: transformedValue.value, + }, + }; + } + + const errors: ValidationError[] = []; + if (!acc.ok) { + errors.push(...acc.errors); + } + if (!transformedKey.ok) { + errors.push(...transformedKey.errors); + } + if (!transformedValue.ok) { + errors.push(...transformedValue.errors); + } + + return { + ok: false, + errors, + }; + }, + { ok: true, value: {} as Record } + ); +} diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/builders/record/types.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/record/types.ts new file mode 100644 index 00000000000..eb82cc7f65c --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/record/types.ts @@ -0,0 +1,17 @@ +import { BaseSchema } from "../../Schema"; +import { SchemaUtils } from "../schema-utils"; + +export type RecordSchema< + RawKey extends string | number, + RawValue, + ParsedKey extends string | number, + ParsedValue +> = BaseRecordSchema & + SchemaUtils, Record>; + +export type BaseRecordSchema< + RawKey extends string | number, + RawValue, + ParsedKey extends string | number, + ParsedValue +> = BaseSchema, Record>; diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/builders/schema-utils/JsonError.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/schema-utils/JsonError.ts new file mode 100644 index 00000000000..2b89ca0e7ad --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/schema-utils/JsonError.ts @@ -0,0 +1,9 @@ +import { ValidationError } from "../../Schema"; +import { stringifyValidationError } from "./stringifyValidationErrors"; + +export class JsonError extends Error { + constructor(public readonly errors: ValidationError[]) { + super(errors.map(stringifyValidationError).join("; ")); + Object.setPrototypeOf(this, JsonError.prototype); + } +} diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/builders/schema-utils/ParseError.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/schema-utils/ParseError.ts new file mode 100644 index 00000000000..d056eb45cf7 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/schema-utils/ParseError.ts @@ -0,0 +1,9 @@ +import { ValidationError } from "../../Schema"; +import { stringifyValidationError } from "./stringifyValidationErrors"; + +export class ParseError extends Error { + constructor(public readonly errors: ValidationError[]) { + super(errors.map(stringifyValidationError).join("; ")); + Object.setPrototypeOf(this, ParseError.prototype); + } +} diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/builders/schema-utils/getSchemaUtils.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/schema-utils/getSchemaUtils.ts new file mode 100644 index 00000000000..79ecad92132 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/schema-utils/getSchemaUtils.ts @@ -0,0 +1,105 @@ +import { BaseSchema, Schema, SchemaOptions, SchemaType } from "../../Schema"; +import { JsonError } from "./JsonError"; +import { ParseError } from "./ParseError"; + +export interface SchemaUtils { + optional: () => Schema; + transform: (transformer: SchemaTransformer) => Schema; + parseOrThrow: (raw: unknown, opts?: SchemaOptions) => Parsed; + jsonOrThrow: (raw: unknown, opts?: SchemaOptions) => Raw; +} + +export interface SchemaTransformer { + transform: (parsed: Parsed) => Transformed; + untransform: (transformed: any) => Parsed; +} + +export function getSchemaUtils(schema: BaseSchema): SchemaUtils { + return { + optional: () => optional(schema), + transform: (transformer) => transform(schema, transformer), + parseOrThrow: (raw, opts) => { + const parsed = schema.parse(raw, opts); + if (parsed.ok) { + return parsed.value; + } + throw new ParseError(parsed.errors); + }, + jsonOrThrow: (parsed, opts) => { + const raw = schema.json(parsed, opts); + if (raw.ok) { + return raw.value; + } + throw new JsonError(raw.errors); + }, + }; +} + +/** + * schema utils are defined in one file to resolve issues with circular imports + */ + +export function optional( + schema: BaseSchema +): Schema { + const baseSchema: BaseSchema = { + parse: (raw, opts) => { + if (raw == null) { + return { + ok: true, + value: undefined, + }; + } + return schema.parse(raw, opts); + }, + json: (parsed, opts) => { + if (opts?.omitUndefined && parsed === undefined) { + return { + ok: true, + value: undefined, + }; + } + if (parsed == null) { + return { + ok: true, + value: null, + }; + } + return schema.json(parsed, opts); + }, + getType: () => SchemaType.OPTIONAL, + }; + + return { + ...baseSchema, + ...getSchemaUtils(baseSchema), + }; +} + +export function transform( + schema: BaseSchema, + transformer: SchemaTransformer +): Schema { + const baseSchema: BaseSchema = { + parse: (raw, opts) => { + const parsed = schema.parse(raw, opts); + if (!parsed.ok) { + return parsed; + } + return { + ok: true, + value: transformer.transform(parsed.value), + }; + }, + json: (transformed, opts) => { + const parsed = transformer.untransform(transformed); + return schema.json(parsed, opts); + }, + getType: () => schema.getType(), + }; + + return { + ...baseSchema, + ...getSchemaUtils(baseSchema), + }; +} diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/builders/schema-utils/index.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/schema-utils/index.ts new file mode 100644 index 00000000000..aa04e051dfa --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/schema-utils/index.ts @@ -0,0 +1,4 @@ +export { getSchemaUtils, optional, transform } from "./getSchemaUtils"; +export type { SchemaUtils } from "./getSchemaUtils"; +export { JsonError } from "./JsonError"; +export { ParseError } from "./ParseError"; diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/builders/schema-utils/stringifyValidationErrors.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/schema-utils/stringifyValidationErrors.ts new file mode 100644 index 00000000000..4160f0a2617 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/schema-utils/stringifyValidationErrors.ts @@ -0,0 +1,8 @@ +import { ValidationError } from "../../Schema"; + +export function stringifyValidationError(error: ValidationError): string { + if (error.path.length === 0) { + return error.message; + } + return `${error.path.join(" -> ")}: ${error.message}`; +} diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/builders/set/index.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/set/index.ts new file mode 100644 index 00000000000..f3310e8bdad --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/set/index.ts @@ -0,0 +1 @@ +export { set } from "./set"; diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/builders/set/set.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/set/set.ts new file mode 100644 index 00000000000..e9e6bb7e539 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/set/set.ts @@ -0,0 +1,43 @@ +import { BaseSchema, Schema, SchemaType } from "../../Schema"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { list } from "../list"; +import { getSchemaUtils } from "../schema-utils"; + +export function set(schema: Schema): Schema> { + const listSchema = list(schema); + const baseSchema: BaseSchema> = { + parse: (raw, opts) => { + const parsedList = listSchema.parse(raw, opts); + if (parsedList.ok) { + return { + ok: true, + value: new Set(parsedList.value), + }; + } else { + return parsedList; + } + }, + json: (parsed, opts) => { + if (!(parsed instanceof Set)) { + return { + ok: false, + errors: [ + { + path: opts?.breadcrumbsPrefix ?? [], + message: getErrorMessageForIncorrectType(parsed, "Set"), + }, + ], + }; + } + const jsonList = listSchema.json([...parsed], opts); + return jsonList; + }, + getType: () => SchemaType.SET, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + }; +} diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/builders/undiscriminated-union/index.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/undiscriminated-union/index.ts new file mode 100644 index 00000000000..75b71cb3565 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/undiscriminated-union/index.ts @@ -0,0 +1,6 @@ +export type { + inferParsedUnidiscriminatedUnionSchema, + inferRawUnidiscriminatedUnionSchema, + UndiscriminatedUnionSchema, +} from "./types"; +export { undiscriminatedUnion } from "./undiscriminatedUnion"; diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/builders/undiscriminated-union/types.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/undiscriminated-union/types.ts new file mode 100644 index 00000000000..43e7108a060 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/undiscriminated-union/types.ts @@ -0,0 +1,10 @@ +import { inferParsed, inferRaw, Schema } from "../../Schema"; + +export type UndiscriminatedUnionSchema = Schema< + inferRawUnidiscriminatedUnionSchema, + inferParsedUnidiscriminatedUnionSchema +>; + +export type inferRawUnidiscriminatedUnionSchema = inferRaw; + +export type inferParsedUnidiscriminatedUnionSchema = inferParsed; diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/builders/undiscriminated-union/undiscriminatedUnion.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/undiscriminated-union/undiscriminatedUnion.ts new file mode 100644 index 00000000000..21ed3df0f40 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/undiscriminated-union/undiscriminatedUnion.ts @@ -0,0 +1,60 @@ +import { BaseSchema, MaybeValid, Schema, SchemaOptions, SchemaType, ValidationError } from "../../Schema"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { getSchemaUtils } from "../schema-utils"; +import { inferParsedUnidiscriminatedUnionSchema, inferRawUnidiscriminatedUnionSchema } from "./types"; + +export function undiscriminatedUnion, ...Schema[]]>( + schemas: Schemas +): Schema, inferParsedUnidiscriminatedUnionSchema> { + const baseSchema: BaseSchema< + inferRawUnidiscriminatedUnionSchema, + inferParsedUnidiscriminatedUnionSchema + > = { + parse: (raw, opts) => { + return validateAndTransformUndiscriminatedUnion>( + (schema, opts) => schema.parse(raw, opts), + schemas, + opts + ); + }, + json: (parsed, opts) => { + return validateAndTransformUndiscriminatedUnion>( + (schema, opts) => schema.json(parsed, opts), + schemas, + opts + ); + }, + getType: () => SchemaType.UNDISCRIMINATED_UNION, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + }; +} + +function validateAndTransformUndiscriminatedUnion( + transform: (schema: Schema, opts: SchemaOptions) => MaybeValid, + schemas: Schema[], + opts: SchemaOptions | undefined +): MaybeValid { + const errors: ValidationError[] = []; + for (const [index, schema] of schemas.entries()) { + const transformed = transform(schema, { ...opts, skipValidation: false }); + if (transformed.ok) { + return transformed; + } else { + for (const error of transformed.errors) { + errors.push({ + path: error.path, + message: `[Variant ${index}] ${error.message}`, + }); + } + } + } + + return { + ok: false, + errors, + }; +} diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/builders/union/discriminant.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/union/discriminant.ts new file mode 100644 index 00000000000..55065bc8946 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/union/discriminant.ts @@ -0,0 +1,14 @@ +export function discriminant( + parsedDiscriminant: ParsedDiscriminant, + rawDiscriminant: RawDiscriminant +): Discriminant { + return { + parsedDiscriminant, + rawDiscriminant, + }; +} + +export interface Discriminant { + parsedDiscriminant: ParsedDiscriminant; + rawDiscriminant: RawDiscriminant; +} diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/builders/union/index.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/union/index.ts new file mode 100644 index 00000000000..85fc008a2d8 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/union/index.ts @@ -0,0 +1,10 @@ +export { discriminant } from "./discriminant"; +export type { Discriminant } from "./discriminant"; +export type { + inferParsedDiscriminant, + inferParsedUnion, + inferRawDiscriminant, + inferRawUnion, + UnionSubtypes, +} from "./types"; +export { union } from "./union"; diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/builders/union/types.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/union/types.ts new file mode 100644 index 00000000000..6f82c868b2d --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/union/types.ts @@ -0,0 +1,26 @@ +import { inferParsedObject, inferRawObject, ObjectSchema } from "../object"; +import { Discriminant } from "./discriminant"; + +export type UnionSubtypes = { + [K in DiscriminantValues]: ObjectSchema; +}; + +export type inferRawUnion, U extends UnionSubtypes> = { + [K in keyof U]: Record, K> & inferRawObject; +}[keyof U]; + +export type inferParsedUnion, U extends UnionSubtypes> = { + [K in keyof U]: Record, K> & inferParsedObject; +}[keyof U]; + +export type inferRawDiscriminant> = D extends string + ? D + : D extends Discriminant + ? Raw + : never; + +export type inferParsedDiscriminant> = D extends string + ? D + : D extends Discriminant + ? Parsed + : never; diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/builders/union/union.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/union/union.ts new file mode 100644 index 00000000000..ab61475a572 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/builders/union/union.ts @@ -0,0 +1,170 @@ +import { BaseSchema, MaybeValid, SchemaType } from "../../Schema"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { isPlainObject } from "../../utils/isPlainObject"; +import { keys } from "../../utils/keys"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { enum_ } from "../enum"; +import { ObjectSchema } from "../object"; +import { getObjectLikeUtils, ObjectLikeSchema } from "../object-like"; +import { getSchemaUtils } from "../schema-utils"; +import { Discriminant } from "./discriminant"; +import { inferParsedDiscriminant, inferParsedUnion, inferRawDiscriminant, inferRawUnion, UnionSubtypes } from "./types"; + +export function union, U extends UnionSubtypes>( + discriminant: D, + union: U +): ObjectLikeSchema, inferParsedUnion> { + const rawDiscriminant = + typeof discriminant === "string" ? discriminant : (discriminant.rawDiscriminant as inferRawDiscriminant); + const parsedDiscriminant = + typeof discriminant === "string" + ? discriminant + : (discriminant.parsedDiscriminant as inferParsedDiscriminant); + + const discriminantValueSchema = enum_(keys(union) as string[]); + + const baseSchema: BaseSchema, inferParsedUnion> = { + parse: (raw, opts) => { + return transformAndValidateUnion({ + value: raw, + discriminant: rawDiscriminant, + transformedDiscriminant: parsedDiscriminant, + transformDiscriminantValue: (discriminantValue) => + discriminantValueSchema.parse(discriminantValue, { + allowUnrecognizedEnumValues: opts?.allowUnrecognizedUnionMembers, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), rawDiscriminant], + }), + getAdditionalPropertiesSchema: (discriminantValue) => union[discriminantValue], + allowUnrecognizedUnionMembers: opts?.allowUnrecognizedUnionMembers, + transformAdditionalProperties: (additionalProperties, additionalPropertiesSchema) => + additionalPropertiesSchema.parse(additionalProperties, opts), + breadcrumbsPrefix: opts?.breadcrumbsPrefix, + }); + }, + json: (parsed, opts) => { + return transformAndValidateUnion({ + value: parsed, + discriminant: parsedDiscriminant, + transformedDiscriminant: rawDiscriminant, + transformDiscriminantValue: (discriminantValue) => + discriminantValueSchema.json(discriminantValue, { + allowUnrecognizedEnumValues: opts?.allowUnrecognizedUnionMembers, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), parsedDiscriminant], + }), + getAdditionalPropertiesSchema: (discriminantValue) => union[discriminantValue], + allowUnrecognizedUnionMembers: opts?.allowUnrecognizedUnionMembers, + transformAdditionalProperties: (additionalProperties, additionalPropertiesSchema) => + additionalPropertiesSchema.json(additionalProperties, opts), + breadcrumbsPrefix: opts?.breadcrumbsPrefix, + }); + }, + getType: () => SchemaType.UNION, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + ...getObjectLikeUtils(baseSchema), + }; +} + +function transformAndValidateUnion< + TransformedDiscriminant extends string, + TransformedDiscriminantValue extends string, + TransformedAdditionalProperties +>({ + value, + discriminant, + transformedDiscriminant, + transformDiscriminantValue, + getAdditionalPropertiesSchema, + allowUnrecognizedUnionMembers = false, + transformAdditionalProperties, + breadcrumbsPrefix = [], +}: { + value: unknown; + discriminant: string; + transformedDiscriminant: TransformedDiscriminant; + transformDiscriminantValue: (discriminantValue: unknown) => MaybeValid; + getAdditionalPropertiesSchema: (discriminantValue: string) => ObjectSchema | undefined; + allowUnrecognizedUnionMembers: boolean | undefined; + transformAdditionalProperties: ( + additionalProperties: unknown, + additionalPropertiesSchema: ObjectSchema + ) => MaybeValid; + breadcrumbsPrefix: string[] | undefined; +}): MaybeValid & TransformedAdditionalProperties> { + if (!isPlainObject(value)) { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "object"), + }, + ], + }; + } + + const { [discriminant]: discriminantValue, ...additionalProperties } = value; + + if (discriminantValue == null) { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: `Missing discriminant ("${discriminant}")`, + }, + ], + }; + } + + const transformedDiscriminantValue = transformDiscriminantValue(discriminantValue); + if (!transformedDiscriminantValue.ok) { + return { + ok: false, + errors: transformedDiscriminantValue.errors, + }; + } + + const additionalPropertiesSchema = getAdditionalPropertiesSchema(transformedDiscriminantValue.value); + + if (additionalPropertiesSchema == null) { + if (allowUnrecognizedUnionMembers) { + return { + ok: true, + value: { + [transformedDiscriminant]: transformedDiscriminantValue.value, + ...additionalProperties, + } as Record & TransformedAdditionalProperties, + }; + } else { + return { + ok: false, + errors: [ + { + path: [...breadcrumbsPrefix, discriminant], + message: "Unexpected discriminant value", + }, + ], + }; + } + } + + const transformedAdditionalProperties = transformAdditionalProperties( + additionalProperties, + additionalPropertiesSchema + ); + if (!transformedAdditionalProperties.ok) { + return transformedAdditionalProperties; + } + + return { + ok: true, + value: { + [transformedDiscriminant]: discriminantValue, + ...transformedAdditionalProperties.value, + } as Record & TransformedAdditionalProperties, + }; +} diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/index.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/index.ts new file mode 100644 index 00000000000..5429d8b43eb --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/index.ts @@ -0,0 +1,2 @@ +export * from "./builders"; +export type { inferParsed, inferRaw, Schema, SchemaOptions } from "./Schema"; diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/utils/MaybePromise.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/utils/MaybePromise.ts new file mode 100644 index 00000000000..9cd354b3418 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/utils/MaybePromise.ts @@ -0,0 +1 @@ +export type MaybePromise = T | Promise; diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/utils/addQuestionMarksToNullableProperties.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/utils/addQuestionMarksToNullableProperties.ts new file mode 100644 index 00000000000..4111d703cd0 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/utils/addQuestionMarksToNullableProperties.ts @@ -0,0 +1,15 @@ +export type addQuestionMarksToNullableProperties = { + [K in OptionalKeys]?: T[K]; +} & Pick>; + +export type OptionalKeys = { + [K in keyof T]-?: undefined extends T[K] + ? K + : null extends T[K] + ? K + : 1 extends (any extends T[K] ? 0 : 1) + ? never + : K; +}[keyof T]; + +export type RequiredKeys = Exclude>; diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/utils/createIdentitySchemaCreator.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/utils/createIdentitySchemaCreator.ts new file mode 100644 index 00000000000..de107cf5ee1 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/utils/createIdentitySchemaCreator.ts @@ -0,0 +1,21 @@ +import { getSchemaUtils } from "../builders/schema-utils"; +import { BaseSchema, MaybeValid, Schema, SchemaOptions, SchemaType } from "../Schema"; +import { maybeSkipValidation } from "./maybeSkipValidation"; + +export function createIdentitySchemaCreator( + schemaType: SchemaType, + validate: (value: unknown, opts?: SchemaOptions) => MaybeValid +): () => Schema { + return () => { + const baseSchema: BaseSchema = { + parse: validate, + json: validate, + getType: () => schemaType, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + }; + }; +} diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/utils/entries.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/utils/entries.ts new file mode 100644 index 00000000000..e122952137d --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/utils/entries.ts @@ -0,0 +1,3 @@ +export function entries(object: T): [keyof T, T[keyof T]][] { + return Object.entries(object) as [keyof T, T[keyof T]][]; +} diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/utils/filterObject.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/utils/filterObject.ts new file mode 100644 index 00000000000..2c25a3455bc --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/utils/filterObject.ts @@ -0,0 +1,10 @@ +export function filterObject(obj: T, keysToInclude: K[]): Pick { + const keysToIncludeSet = new Set(keysToInclude); + return Object.entries(obj).reduce((acc, [key, value]) => { + if (keysToIncludeSet.has(key as K)) { + acc[key as K] = value; + } + return acc; + // eslint-disable-next-line @typescript-eslint/prefer-reduce-type-parameter + }, {} as Pick); +} diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/utils/getErrorMessageForIncorrectType.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/utils/getErrorMessageForIncorrectType.ts new file mode 100644 index 00000000000..438012df418 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/utils/getErrorMessageForIncorrectType.ts @@ -0,0 +1,21 @@ +export function getErrorMessageForIncorrectType(value: unknown, expectedType: string): string { + return `Expected ${expectedType}. Received ${getTypeAsString(value)}.`; +} + +function getTypeAsString(value: unknown): string { + if (Array.isArray(value)) { + return "list"; + } + if (value === null) { + return "null"; + } + switch (typeof value) { + case "string": + return `"${value}"`; + case "number": + case "boolean": + case "undefined": + return `${value}`; + } + return typeof value; +} diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/utils/isPlainObject.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/utils/isPlainObject.ts new file mode 100644 index 00000000000..db82a722c35 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/utils/isPlainObject.ts @@ -0,0 +1,17 @@ +// borrowed from https://github.com/lodash/lodash/blob/master/isPlainObject.js +export function isPlainObject(value: unknown): value is Record { + if (typeof value !== "object" || value === null) { + return false; + } + + if (Object.getPrototypeOf(value) === null) { + return true; + } + + let proto = value; + while (Object.getPrototypeOf(proto) !== null) { + proto = Object.getPrototypeOf(proto); + } + + return Object.getPrototypeOf(value) === proto; +} diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/utils/keys.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/utils/keys.ts new file mode 100644 index 00000000000..01867098287 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/utils/keys.ts @@ -0,0 +1,3 @@ +export function keys(object: T): (keyof T)[] { + return Object.keys(object) as (keyof T)[]; +} diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/utils/maybeSkipValidation.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/utils/maybeSkipValidation.ts new file mode 100644 index 00000000000..86c07abf2b4 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/utils/maybeSkipValidation.ts @@ -0,0 +1,38 @@ +import { BaseSchema, MaybeValid, SchemaOptions } from "../Schema"; + +export function maybeSkipValidation, Raw, Parsed>(schema: S): S { + return { + ...schema, + json: transformAndMaybeSkipValidation(schema.json), + parse: transformAndMaybeSkipValidation(schema.parse), + }; +} + +function transformAndMaybeSkipValidation( + transform: (value: unknown, opts?: SchemaOptions) => MaybeValid +): (value: unknown, opts?: SchemaOptions) => MaybeValid { + return (value, opts): MaybeValid => { + const transformed = transform(value, opts); + const { skipValidation = false } = opts ?? {}; + if (!transformed.ok && skipValidation) { + // eslint-disable-next-line no-console + console.warn( + [ + "Failed to validate.", + ...transformed.errors.map( + (error) => + " - " + + (error.path.length > 0 ? `${error.path.join(".")}: ${error.message}` : error.message) + ), + ].join("\n") + ); + + return { + ok: true, + value: value as T, + }; + } else { + return transformed; + } + }; +} diff --git a/seed/ts-sdk/grpc-proto/src/core/schemas/utils/partition.ts b/seed/ts-sdk/grpc-proto/src/core/schemas/utils/partition.ts new file mode 100644 index 00000000000..f58d6f3d35f --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/core/schemas/utils/partition.ts @@ -0,0 +1,12 @@ +export function partition(items: readonly T[], predicate: (item: T) => boolean): [T[], T[]] { + const trueItems: T[] = [], + falseItems: T[] = []; + for (const item of items) { + if (predicate(item)) { + trueItems.push(item); + } else { + falseItems.push(item); + } + } + return [trueItems, falseItems]; +} diff --git a/seed/ts-sdk/grpc-proto/src/errors/SeedApiError.ts b/seed/ts-sdk/grpc-proto/src/errors/SeedApiError.ts new file mode 100644 index 00000000000..4e591f80194 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/errors/SeedApiError.ts @@ -0,0 +1,45 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +export class SeedApiError extends Error { + readonly statusCode?: number; + readonly body?: unknown; + + constructor({ message, statusCode, body }: { message?: string; statusCode?: number; body?: unknown }) { + super(buildMessage({ message, statusCode, body })); + Object.setPrototypeOf(this, SeedApiError.prototype); + if (statusCode != null) { + this.statusCode = statusCode; + } + + if (body !== undefined) { + this.body = body; + } + } +} + +function buildMessage({ + message, + statusCode, + body, +}: { + message: string | undefined; + statusCode: number | undefined; + body: unknown | undefined; +}): string { + let lines: string[] = []; + if (message != null) { + lines.push(message); + } + + if (statusCode != null) { + lines.push(`Status code: ${statusCode.toString()}`); + } + + if (body != null) { + lines.push(`Body: ${JSON.stringify(body, undefined, 2)}`); + } + + return lines.join("\n"); +} diff --git a/seed/ts-sdk/grpc-proto/src/errors/SeedApiTimeoutError.ts b/seed/ts-sdk/grpc-proto/src/errors/SeedApiTimeoutError.ts new file mode 100644 index 00000000000..79e2569aac3 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/errors/SeedApiTimeoutError.ts @@ -0,0 +1,10 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +export class SeedApiTimeoutError extends Error { + constructor() { + super("Timeout"); + Object.setPrototypeOf(this, SeedApiTimeoutError.prototype); + } +} diff --git a/seed/ts-sdk/grpc-proto/src/errors/index.ts b/seed/ts-sdk/grpc-proto/src/errors/index.ts new file mode 100644 index 00000000000..83870aebd43 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/errors/index.ts @@ -0,0 +1,2 @@ +export { SeedApiError } from "./SeedApiError"; +export { SeedApiTimeoutError } from "./SeedApiTimeoutError"; diff --git a/seed/ts-sdk/grpc-proto/src/index.ts b/seed/ts-sdk/grpc-proto/src/index.ts new file mode 100644 index 00000000000..bed9941cdaa --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/index.ts @@ -0,0 +1,3 @@ +export * as SeedApi from "./api"; +export { SeedApiClient } from "./Client"; +export { SeedApiError, SeedApiTimeoutError } from "./errors"; diff --git a/seed/ts-sdk/grpc-proto/src/serialization/index.ts b/seed/ts-sdk/grpc-proto/src/serialization/index.ts new file mode 100644 index 00000000000..3ce0a3e38e8 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/serialization/index.ts @@ -0,0 +1,2 @@ +export * from "./types"; +export * from "./resources"; diff --git a/seed/ts-sdk/grpc-proto/src/serialization/resources/index.ts b/seed/ts-sdk/grpc-proto/src/serialization/resources/index.ts new file mode 100644 index 00000000000..7b5adc8b2ff --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/serialization/resources/index.ts @@ -0,0 +1,2 @@ +export * as user from "./user"; +export * from "./user/client/requests"; diff --git a/seed/ts-sdk/grpc-proto/src/serialization/resources/user/client/index.ts b/seed/ts-sdk/grpc-proto/src/serialization/resources/user/client/index.ts new file mode 100644 index 00000000000..415726b7fea --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/serialization/resources/user/client/index.ts @@ -0,0 +1 @@ +export * from "./requests"; diff --git a/seed/ts-sdk/grpc-proto/src/serialization/resources/user/client/requests/CreateRequest.ts b/seed/ts-sdk/grpc-proto/src/serialization/resources/user/client/requests/CreateRequest.ts new file mode 100644 index 00000000000..45950a76bed --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/serialization/resources/user/client/requests/CreateRequest.ts @@ -0,0 +1,26 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../../index"; +import * as SeedApi from "../../../../../api/index"; +import * as core from "../../../../../core"; + +export const CreateRequest: core.serialization.Schema = + core.serialization.object({ + username: core.serialization.string().optional(), + email: core.serialization.string().optional(), + age: core.serialization.number().optional(), + weight: core.serialization.number().optional(), + metadata: core.serialization.record(core.serialization.string(), core.serialization.unknown()).optional(), + }); + +export declare namespace CreateRequest { + interface Raw { + username?: string | null; + email?: string | null; + age?: number | null; + weight?: number | null; + metadata?: Record | null; + } +} diff --git a/seed/ts-sdk/grpc-proto/src/serialization/resources/user/client/requests/index.ts b/seed/ts-sdk/grpc-proto/src/serialization/resources/user/client/requests/index.ts new file mode 100644 index 00000000000..4f8adcb4e83 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/serialization/resources/user/client/requests/index.ts @@ -0,0 +1 @@ +export { CreateRequest } from "./CreateRequest"; diff --git a/seed/ts-sdk/grpc-proto/src/serialization/resources/user/index.ts b/seed/ts-sdk/grpc-proto/src/serialization/resources/user/index.ts new file mode 100644 index 00000000000..5ec76921e18 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/serialization/resources/user/index.ts @@ -0,0 +1 @@ +export * from "./client"; diff --git a/seed/ts-sdk/grpc-proto/src/serialization/types/CreateResponse.ts b/seed/ts-sdk/grpc-proto/src/serialization/types/CreateResponse.ts new file mode 100644 index 00000000000..fd914fae992 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/serialization/types/CreateResponse.ts @@ -0,0 +1,19 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../index"; +import * as SeedApi from "../../api/index"; +import * as core from "../../core"; +import { UserModel } from "./UserModel"; + +export const CreateResponse: core.serialization.ObjectSchema = + core.serialization.object({ + user: UserModel.optional(), + }); + +export declare namespace CreateResponse { + interface Raw { + user?: UserModel.Raw | null; + } +} diff --git a/seed/ts-sdk/grpc-proto/src/serialization/types/UserModel.ts b/seed/ts-sdk/grpc-proto/src/serialization/types/UserModel.ts new file mode 100644 index 00000000000..d34237fabad --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/serialization/types/UserModel.ts @@ -0,0 +1,26 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../index"; +import * as SeedApi from "../../api/index"; +import * as core from "../../core"; + +export const UserModel: core.serialization.ObjectSchema = + core.serialization.object({ + username: core.serialization.string().optional(), + email: core.serialization.string().optional(), + age: core.serialization.number().optional(), + weight: core.serialization.number().optional(), + metadata: core.serialization.record(core.serialization.string(), core.serialization.unknown()).optional(), + }); + +export declare namespace UserModel { + interface Raw { + username?: string | null; + email?: string | null; + age?: number | null; + weight?: number | null; + metadata?: Record | null; + } +} diff --git a/seed/ts-sdk/grpc-proto/src/serialization/types/index.ts b/seed/ts-sdk/grpc-proto/src/serialization/types/index.ts new file mode 100644 index 00000000000..8685532c76b --- /dev/null +++ b/seed/ts-sdk/grpc-proto/src/serialization/types/index.ts @@ -0,0 +1,2 @@ +export * from "./CreateResponse"; +export * from "./UserModel"; diff --git a/seed/ts-sdk/grpc-proto/tests/custom.test.ts b/seed/ts-sdk/grpc-proto/tests/custom.test.ts new file mode 100644 index 00000000000..7f5e031c839 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/tests/custom.test.ts @@ -0,0 +1,13 @@ +/** + * This is a custom test file, if you wish to add more tests + * to your SDK. + * Be sure to mark this file in `.fernignore`. + * + * If you include example requests/responses in your fern definition, + * you will have tests automatically generated for you. + */ +describe("test", () => { + it("default", () => { + expect(true).toBe(true); + }); +}); diff --git a/seed/ts-sdk/grpc-proto/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/grpc-proto/tests/unit/fetcher/Fetcher.test.ts new file mode 100644 index 00000000000..0e14a8c77f8 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/tests/unit/fetcher/Fetcher.test.ts @@ -0,0 +1,25 @@ +import fetchMock from "fetch-mock-jest"; +import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; + +describe("Test fetcherImpl", () => { + it("should handle successful request", async () => { + const mockArgs: Fetcher.Args = { + url: "https://httpbin.org/post", + method: "POST", + headers: { "X-Test": "x-test-header" }, + body: { data: "test" }, + contentType: "application/json", + requestType: "json", + }; + + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); + + const result = await fetcherImpl(mockArgs); + expect(result.ok).toBe(true); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } + }); +}); diff --git a/seed/ts-sdk/grpc-proto/tests/unit/fetcher/createRequestUrl.test.ts b/seed/ts-sdk/grpc-proto/tests/unit/fetcher/createRequestUrl.test.ts new file mode 100644 index 00000000000..f2cd24b6721 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/tests/unit/fetcher/createRequestUrl.test.ts @@ -0,0 +1,51 @@ +import { createRequestUrl } from "../../../src/core/fetcher/createRequestUrl"; + +describe("Test createRequestUrl", () => { + it("should return the base URL when no query parameters are provided", () => { + const baseUrl = "https://api.example.com"; + expect(createRequestUrl(baseUrl)).toBe(baseUrl); + }); + + it("should append simple query parameters", () => { + const baseUrl = "https://api.example.com"; + const queryParams = { key: "value", another: "param" }; + expect(createRequestUrl(baseUrl, queryParams)).toBe("https://api.example.com?key=value&another=param"); + }); + + it("should handle array query parameters", () => { + const baseUrl = "https://api.example.com"; + const queryParams = { items: ["a", "b", "c"] }; + expect(createRequestUrl(baseUrl, queryParams)).toBe("https://api.example.com?items=a&items=b&items=c"); + }); + + it("should handle object query parameters", () => { + const baseUrl = "https://api.example.com"; + const queryParams = { filter: { name: "John", age: 30 } }; + expect(createRequestUrl(baseUrl, queryParams)).toBe( + "https://api.example.com?filter%5Bname%5D=John&filter%5Bage%5D=30" + ); + }); + + it("should handle mixed types of query parameters", () => { + const baseUrl = "https://api.example.com"; + const queryParams = { + simple: "value", + array: ["x", "y"], + object: { key: "value" }, + }; + expect(createRequestUrl(baseUrl, queryParams)).toBe( + "https://api.example.com?simple=value&array=x&array=y&object%5Bkey%5D=value" + ); + }); + + it("should handle empty query parameters object", () => { + const baseUrl = "https://api.example.com"; + expect(createRequestUrl(baseUrl, {})).toBe(baseUrl); + }); + + it("should encode special characters in query parameters", () => { + const baseUrl = "https://api.example.com"; + const queryParams = { special: "a&b=c d" }; + expect(createRequestUrl(baseUrl, queryParams)).toBe("https://api.example.com?special=a%26b%3Dc%20d"); + }); +}); diff --git a/seed/ts-sdk/grpc-proto/tests/unit/fetcher/getFetchFn.test.ts b/seed/ts-sdk/grpc-proto/tests/unit/fetcher/getFetchFn.test.ts new file mode 100644 index 00000000000..9b315ad095a --- /dev/null +++ b/seed/ts-sdk/grpc-proto/tests/unit/fetcher/getFetchFn.test.ts @@ -0,0 +1,22 @@ +import { RUNTIME } from "../../../src/core/runtime"; +import { getFetchFn } from "../../../src/core/fetcher/getFetchFn"; + +describe("Test for getFetchFn", () => { + it("should get node-fetch function", async () => { + if (RUNTIME.type == "node") { + if (RUNTIME.parsedVersion != null && RUNTIME.parsedVersion >= 18) { + expect(await getFetchFn()).toBe(fetch); + } else { + expect(await getFetchFn()).toEqual((await import("node-fetch")).default as any); + } + } + }); + + it("should get fetch function", async () => { + if (RUNTIME.type == "browser") { + const fetchFn = await getFetchFn(); + expect(typeof fetchFn).toBe("function"); + expect(fetchFn.name).toBe("fetch"); + } + }); +}); diff --git a/seed/ts-sdk/grpc-proto/tests/unit/fetcher/getRequestBody.test.ts b/seed/ts-sdk/grpc-proto/tests/unit/fetcher/getRequestBody.test.ts new file mode 100644 index 00000000000..1b1462c51bd --- /dev/null +++ b/seed/ts-sdk/grpc-proto/tests/unit/fetcher/getRequestBody.test.ts @@ -0,0 +1,81 @@ +import { RUNTIME } from "../../../src/core/runtime"; +import { getRequestBody } from "../../../src/core/fetcher/getRequestBody"; + +if (RUNTIME.type === "browser") { + require("jest-fetch-mock").enableMocks(); +} + +describe("Test getRequestBody", () => { + it("should return FormData as is in Node environment", async () => { + if (RUNTIME.type === "node") { + const formData = new (await import("formdata-node")).FormData(); + formData.append("key", "value"); + const result = await getRequestBody({ + body: formData, + type: "file", + }); + expect(result).toBe(formData); + } + }); + + it("should stringify body if not FormData in Node environment", async () => { + if (RUNTIME.type === "node") { + const body = { key: "value" }; + const result = await getRequestBody({ + body, + type: "json", + }); + expect(result).toBe('{"key":"value"}'); + } + }); + + it("should return FormData in browser environment", async () => { + if (RUNTIME.type === "browser") { + const formData = new (await import("form-data")).default(); + formData.append("key", "value"); + const result = await getRequestBody({ + body: formData, + type: "file", + }); + expect(result).toBe(formData); + } + }); + + it("should stringify body if not FormData in browser environment", async () => { + if (RUNTIME.type === "browser") { + const body = { key: "value" }; + const result = await getRequestBody({ + body, + type: "json", + }); + expect(result).toBe('{"key":"value"}'); + } + }); + + it("should return the Uint8Array", async () => { + const input = new Uint8Array([1, 2, 3]); + const result = await getRequestBody({ + body: input, + type: "bytes", + }); + expect(result).toBe(input); + }); + + it("should return the input for content-type 'application/x-www-form-urlencoded'", async () => { + const input = "key=value&another=param"; + const result = await getRequestBody({ + body: input, + type: "other", + }); + expect(result).toBe(input); + }); + + it("should JSON stringify objects", async () => { + const input = { key: "value" }; + const result = await getRequestBody({ + body: input, + type: "json", + }); + expect(result).toBe('{"key":"value"}'); + }); +}); diff --git a/seed/ts-sdk/grpc-proto/tests/unit/fetcher/getResponseBody.test.ts b/seed/ts-sdk/grpc-proto/tests/unit/fetcher/getResponseBody.test.ts new file mode 100644 index 00000000000..3510779e3f9 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/tests/unit/fetcher/getResponseBody.test.ts @@ -0,0 +1,68 @@ +import { RUNTIME } from "../../../src/core/runtime"; +import { getResponseBody } from "../../../src/core/fetcher/getResponseBody"; +import { chooseStreamWrapper } from "../../../src/core/fetcher/stream-wrappers/chooseStreamWrapper"; + +if (RUNTIME.type === "browser") { + require("jest-fetch-mock").enableMocks(); +} + +describe("Test getResponseBody", () => { + it("should handle blob response type", async () => { + const mockBlob = new Blob(["test"], { type: "text/plain" }); + const mockResponse = new Response(mockBlob); + const result = await getResponseBody(mockResponse, "blob"); + // @ts-expect-error + expect(result.constructor.name).toBe("Blob"); + }); + + it("should handle sse response type", async () => { + if (RUNTIME.type === "node") { + const mockStream = new ReadableStream(); + const mockResponse = new Response(mockStream); + const result = await getResponseBody(mockResponse, "sse"); + expect(result).toBe(mockStream); + } + }); + + it("should handle streaming response type", async () => { + if (RUNTIME.type === "node") { + const mockStream = new ReadableStream(); + const mockResponse = new Response(mockStream); + const result = await getResponseBody(mockResponse, "streaming"); + // need to reinstantiate string as a result of locked state in Readable Stream after registration with Response + expect(JSON.stringify(result)).toBe(JSON.stringify(await chooseStreamWrapper(new ReadableStream()))); + } + }); + + it("should handle text response type", async () => { + const mockResponse = new Response("test text"); + const result = await getResponseBody(mockResponse, "text"); + expect(result).toBe("test text"); + }); + + it("should handle JSON response", async () => { + const mockJson = { key: "value" }; + const mockResponse = new Response(JSON.stringify(mockJson)); + const result = await getResponseBody(mockResponse); + expect(result).toEqual(mockJson); + }); + + it("should handle empty response", async () => { + const mockResponse = new Response(""); + const result = await getResponseBody(mockResponse); + expect(result).toBeUndefined(); + }); + + it("should handle non-JSON response", async () => { + const mockResponse = new Response("invalid json"); + const result = await getResponseBody(mockResponse); + expect(result).toEqual({ + ok: false, + error: { + reason: "non-json", + statusCode: 200, + rawBody: "invalid json", + }, + }); + }); +}); diff --git a/seed/ts-sdk/grpc-proto/tests/unit/fetcher/makeRequest.test.ts b/seed/ts-sdk/grpc-proto/tests/unit/fetcher/makeRequest.test.ts new file mode 100644 index 00000000000..5969d5155ac --- /dev/null +++ b/seed/ts-sdk/grpc-proto/tests/unit/fetcher/makeRequest.test.ts @@ -0,0 +1,58 @@ +import { RUNTIME } from "../../../src/core/runtime"; +import { makeRequest } from "../../../src/core/fetcher/makeRequest"; + +if (RUNTIME.type === "browser") { + require("jest-fetch-mock").enableMocks(); +} + +describe("Test makeRequest", () => { + const mockPostUrl = "https://httpbin.org/post"; + const mockGetUrl = "https://httpbin.org/get"; + const mockHeaders = { "Content-Type": "application/json" }; + const mockBody = JSON.stringify({ key: "value" }); + + let mockFetch: jest.Mock; + + beforeEach(() => { + mockFetch = jest.fn(); + mockFetch.mockResolvedValue(new Response(JSON.stringify({ test: "successful" }), { status: 200 })); + }); + + it("should handle POST request correctly", async () => { + const response = await makeRequest(mockFetch, mockPostUrl, "POST", mockHeaders, mockBody); + const responseBody = await response.json(); + expect(responseBody).toEqual({ test: "successful" }); + expect(mockFetch).toHaveBeenCalledTimes(1); + const [calledUrl, calledOptions] = mockFetch.mock.calls[0]; + expect(calledUrl).toBe(mockPostUrl); + expect(calledOptions).toEqual( + expect.objectContaining({ + method: "POST", + headers: mockHeaders, + body: mockBody, + credentials: undefined, + }) + ); + expect(calledOptions.signal).toBeDefined(); + expect(calledOptions.signal).toBeInstanceOf(AbortSignal); + }); + + it("should handle GET request correctly", async () => { + const response = await makeRequest(mockFetch, mockGetUrl, "GET", mockHeaders, undefined); + const responseBody = await response.json(); + expect(responseBody).toEqual({ test: "successful" }); + expect(mockFetch).toHaveBeenCalledTimes(1); + const [calledUrl, calledOptions] = mockFetch.mock.calls[0]; + expect(calledUrl).toBe(mockGetUrl); + expect(calledOptions).toEqual( + expect.objectContaining({ + method: "GET", + headers: mockHeaders, + body: undefined, + credentials: undefined, + }) + ); + expect(calledOptions.signal).toBeDefined(); + expect(calledOptions.signal).toBeInstanceOf(AbortSignal); + }); +}); diff --git a/seed/ts-sdk/grpc-proto/tests/unit/fetcher/requestWithRetries.test.ts b/seed/ts-sdk/grpc-proto/tests/unit/fetcher/requestWithRetries.test.ts new file mode 100644 index 00000000000..b53e04367c5 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/tests/unit/fetcher/requestWithRetries.test.ts @@ -0,0 +1,85 @@ +import { RUNTIME } from "../../../src/core/runtime"; +import { requestWithRetries } from "../../../src/core/fetcher/requestWithRetries"; + +if (RUNTIME.type === "browser") { + require("jest-fetch-mock").enableMocks(); +} + +describe("Test exponential backoff", () => { + let mockFetch: jest.Mock; + let originalSetTimeout: typeof setTimeout; + + beforeEach(() => { + mockFetch = jest.fn(); + originalSetTimeout = global.setTimeout; + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + global.setTimeout = originalSetTimeout; + }); + + it("should retry on 408, 409, 429, 500+", async () => { + mockFetch + .mockResolvedValueOnce(new Response("", { status: 408 })) + .mockResolvedValueOnce(new Response("", { status: 409 })) + .mockResolvedValueOnce(new Response("", { status: 429 })) + .mockResolvedValueOnce(new Response("", { status: 500 })) + .mockResolvedValueOnce(new Response("", { status: 502 })) + .mockResolvedValueOnce(new Response("", { status: 200 })) + .mockResolvedValueOnce(new Response("", { status: 408 })); + + const responsePromise = requestWithRetries(() => mockFetch(), 10); + + await jest.advanceTimersByTimeAsync(10000); + const response = await responsePromise; + + expect(mockFetch).toHaveBeenCalledTimes(6); + expect(response.status).toBe(200); + }); + + it("should retry max 3 times", async () => { + mockFetch + .mockResolvedValueOnce(new Response("", { status: 408 })) + .mockResolvedValueOnce(new Response("", { status: 409 })) + .mockResolvedValueOnce(new Response("", { status: 429 })) + .mockResolvedValueOnce(new Response("", { status: 429 })); + + const responsePromise = requestWithRetries(() => mockFetch(), 3); + + await jest.advanceTimersByTimeAsync(10000); + const response = await responsePromise; + + expect(mockFetch).toHaveBeenCalledTimes(4); + expect(response.status).toBe(429); + }); + it("should not retry on 200", async () => { + mockFetch + .mockResolvedValueOnce(new Response("", { status: 200 })) + .mockResolvedValueOnce(new Response("", { status: 409 })); + + const responsePromise = requestWithRetries(() => mockFetch(), 3); + + await jest.advanceTimersByTimeAsync(10000); + const response = await responsePromise; + + expect(mockFetch).toHaveBeenCalledTimes(1); + expect(response.status).toBe(200); + }); + + it("should retry with exponential backoff timing", async () => { + mockFetch.mockResolvedValue(new Response("", { status: 500 })); + const maxRetries = 7; + const responsePromise = requestWithRetries(() => mockFetch(), maxRetries); + expect(mockFetch).toHaveBeenCalledTimes(1); + + const delays = [1, 2, 4, 8, 16, 32, 64]; + for (let i = 0; i < delays.length; i++) { + await jest.advanceTimersByTimeAsync(delays[i] as number); + expect(mockFetch).toHaveBeenCalledTimes(Math.min(i + 2, maxRetries + 1)); + } + const response = await responsePromise; + expect(response.status).toBe(500); + }); +}); diff --git a/seed/ts-sdk/grpc-proto/tests/unit/fetcher/signals.test.ts b/seed/ts-sdk/grpc-proto/tests/unit/fetcher/signals.test.ts new file mode 100644 index 00000000000..9cabfa07447 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/tests/unit/fetcher/signals.test.ts @@ -0,0 +1,69 @@ +import { anySignal, getTimeoutSignal } from "../../../src/core/fetcher/signals"; + +describe("Test getTimeoutSignal", () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it("should return an object with signal and abortId", () => { + const { signal, abortId } = getTimeoutSignal(1000); + + expect(signal).toBeDefined(); + expect(abortId).toBeDefined(); + expect(signal).toBeInstanceOf(AbortSignal); + expect(signal.aborted).toBe(false); + }); + + it("should create a signal that aborts after the specified timeout", () => { + const timeoutMs = 5000; + const { signal } = getTimeoutSignal(timeoutMs); + + expect(signal.aborted).toBe(false); + + jest.advanceTimersByTime(timeoutMs - 1); + expect(signal.aborted).toBe(false); + + jest.advanceTimersByTime(1); + expect(signal.aborted).toBe(true); + }); +}); + +describe("Test anySignal", () => { + it("should return an AbortSignal", () => { + const signal = anySignal(new AbortController().signal); + expect(signal).toBeInstanceOf(AbortSignal); + }); + + it("should abort when any of the input signals is aborted", () => { + const controller1 = new AbortController(); + const controller2 = new AbortController(); + const signal = anySignal(controller1.signal, controller2.signal); + + expect(signal.aborted).toBe(false); + controller1.abort(); + expect(signal.aborted).toBe(true); + }); + + it("should handle an array of signals", () => { + const controller1 = new AbortController(); + const controller2 = new AbortController(); + const signal = anySignal([controller1.signal, controller2.signal]); + + expect(signal.aborted).toBe(false); + controller2.abort(); + expect(signal.aborted).toBe(true); + }); + + it("should abort immediately if one of the input signals is already aborted", () => { + const controller1 = new AbortController(); + const controller2 = new AbortController(); + controller1.abort(); + + const signal = anySignal(controller1.signal, controller2.signal); + expect(signal.aborted).toBe(true); + }); +}); diff --git a/seed/ts-sdk/grpc-proto/tests/unit/fetcher/stream-wrappers/Node18UniversalStreamWrapper.test.ts b/seed/ts-sdk/grpc-proto/tests/unit/fetcher/stream-wrappers/Node18UniversalStreamWrapper.test.ts new file mode 100644 index 00000000000..e307b1589a7 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/tests/unit/fetcher/stream-wrappers/Node18UniversalStreamWrapper.test.ts @@ -0,0 +1,178 @@ +import { Node18UniversalStreamWrapper } from "../../../../src/core/fetcher/stream-wrappers/Node18UniversalStreamWrapper"; + +describe("Node18UniversalStreamWrapper", () => { + it("should set encoding to utf-8", async () => { + const rawStream = new ReadableStream(); + const stream = new Node18UniversalStreamWrapper(rawStream); + const setEncodingSpy = jest.spyOn(stream, "setEncoding"); + + stream.setEncoding("utf-8"); + + expect(setEncodingSpy).toHaveBeenCalledWith("utf-8"); + }); + + it("should register an event listener for readable", async () => { + const rawStream = new ReadableStream(); + const stream = new Node18UniversalStreamWrapper(rawStream); + const onSpy = jest.spyOn(stream, "on"); + + stream.on("readable", () => {}); + + expect(onSpy).toHaveBeenCalledWith("readable", expect.any(Function)); + }); + + it("should remove an event listener for data", async () => { + const rawStream = new ReadableStream(); + const stream = new Node18UniversalStreamWrapper(rawStream); + const offSpy = jest.spyOn(stream, "off"); + + const fn = () => {}; + stream.on("data", fn); + stream.off("data", fn); + + expect(offSpy).toHaveBeenCalledWith("data", expect.any(Function)); + }); + + it("should write to dest when calling pipe to writable stream", async () => { + const rawStream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode("test")); + controller.enqueue(new TextEncoder().encode("test")); + controller.close(); + }, + }); + const stream = new Node18UniversalStreamWrapper(rawStream); + const dest = new WritableStream({ + write(chunk) { + expect(chunk).toEqual(new TextEncoder().encode("test")); + }, + }); + + stream.pipe(dest); + }); + + it("should write to dest when calling pipe to node writable stream", async () => { + const rawStream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode("test")); + controller.enqueue(new TextEncoder().encode("test")); + controller.close(); + }, + }); + const stream = new Node18UniversalStreamWrapper(rawStream); + const dest = new (await import("stream")).Writable({ + write(chunk, encoding, callback) { + expect(chunk.toString()).toEqual("test"); + callback(); + }, + }); + + stream.pipe(dest); + }); + + it("should write nothing when calling pipe and unpipe", async () => { + const rawStream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode("test")); + controller.enqueue(new TextEncoder().encode("test")); + controller.close(); + }, + }); + const stream = new Node18UniversalStreamWrapper(rawStream); + const buffer: Uint8Array[] = []; + const dest = new WritableStream({ + write(chunk) { + buffer.push(chunk); + }, + }); + + stream.pipe(dest); + stream.unpipe(dest); + expect(buffer).toEqual([]); + }); + + it("should destroy the stream", async () => { + const rawStream = new ReadableStream(); + const stream = new Node18UniversalStreamWrapper(rawStream); + const destroySpy = jest.spyOn(stream, "destroy"); + + stream.destroy(); + + expect(destroySpy).toHaveBeenCalled(); + }); + + it("should pause and resume the stream", async () => { + const rawStream = new ReadableStream(); + const stream = new Node18UniversalStreamWrapper(rawStream); + const pauseSpy = jest.spyOn(stream, "pause"); + const resumeSpy = jest.spyOn(stream, "resume"); + + expect(stream.isPaused).toBe(false); + stream.pause(); + expect(stream.isPaused).toBe(true); + stream.resume(); + + expect(pauseSpy).toHaveBeenCalled(); + expect(resumeSpy).toHaveBeenCalled(); + }); + + it("should read the stream", async () => { + const rawStream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode("test")); + controller.enqueue(new TextEncoder().encode("test")); + controller.close(); + }, + }); + const stream = new Node18UniversalStreamWrapper(rawStream); + + expect(await stream.read()).toEqual(new TextEncoder().encode("test")); + expect(await stream.read()).toEqual(new TextEncoder().encode("test")); + }); + + it("should read the stream as text", async () => { + const rawStream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode("test")); + controller.enqueue(new TextEncoder().encode("test")); + controller.close(); + }, + }); + const stream = new Node18UniversalStreamWrapper(rawStream); + + const data = await stream.text(); + + expect(data).toEqual("testtest"); + }); + + it("should read the stream as json", async () => { + const rawStream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode(JSON.stringify({ test: "test" }))); + controller.close(); + }, + }); + const stream = new Node18UniversalStreamWrapper(rawStream); + + const data = await stream.json(); + + expect(data).toEqual({ test: "test" }); + }); + + it("should allow use with async iteratable stream", async () => { + const rawStream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode("test")); + controller.enqueue(new TextEncoder().encode("test")); + controller.close(); + }, + }); + let data = ""; + const stream = new Node18UniversalStreamWrapper(rawStream); + for await (const chunk of stream) { + data += new TextDecoder().decode(chunk); + } + + expect(data).toEqual("testtest"); + }); +}); diff --git a/seed/ts-sdk/grpc-proto/tests/unit/fetcher/stream-wrappers/NodePre18StreamWrapper.test.ts b/seed/ts-sdk/grpc-proto/tests/unit/fetcher/stream-wrappers/NodePre18StreamWrapper.test.ts new file mode 100644 index 00000000000..861142a08b0 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/tests/unit/fetcher/stream-wrappers/NodePre18StreamWrapper.test.ts @@ -0,0 +1,124 @@ +import { NodePre18StreamWrapper } from "../../../../src/core/fetcher/stream-wrappers/NodePre18StreamWrapper"; + +describe("NodePre18StreamWrapper", () => { + it("should set encoding to utf-8", async () => { + const rawStream = (await import("stream")).Readable.from(["test", "test"]); + const stream = new NodePre18StreamWrapper(rawStream); + const setEncodingSpy = jest.spyOn(stream, "setEncoding"); + + stream.setEncoding("utf-8"); + + expect(setEncodingSpy).toHaveBeenCalledWith("utf-8"); + }); + + it("should register an event listener for readable", async () => { + const rawStream = (await import("stream")).Readable.from(["test", "test"]); + const stream = new NodePre18StreamWrapper(rawStream); + const onSpy = jest.spyOn(stream, "on"); + + stream.on("readable", () => {}); + + expect(onSpy).toHaveBeenCalledWith("readable", expect.any(Function)); + }); + + it("should remove an event listener for data", async () => { + const rawStream = (await import("stream")).Readable.from(["test", "test"]); + const stream = new NodePre18StreamWrapper(rawStream); + const offSpy = jest.spyOn(stream, "off"); + + const fn = () => {}; + stream.on("data", fn); + stream.off("data", fn); + + expect(offSpy).toHaveBeenCalledWith("data", expect.any(Function)); + }); + + it("should write to dest when calling pipe to node writable stream", async () => { + const rawStream = (await import("stream")).Readable.from(["test", "test"]); + const stream = new NodePre18StreamWrapper(rawStream); + const dest = new (await import("stream")).Writable({ + write(chunk, encoding, callback) { + expect(chunk.toString()).toEqual("test"); + callback(); + }, + }); + + stream.pipe(dest); + }); + + it("should write nothing when calling pipe and unpipe", async () => { + const rawStream = (await import("stream")).Readable.from(["test", "test"]); + const stream = new NodePre18StreamWrapper(rawStream); + const buffer: Uint8Array[] = []; + const dest = new (await import("stream")).Writable({ + write(chunk, encoding, callback) { + buffer.push(chunk); + callback(); + }, + }); + stream.pipe(dest); + stream.unpipe(); + + expect(buffer).toEqual([]); + }); + + it("should destroy the stream", async () => { + const rawStream = (await import("stream")).Readable.from(["test", "test"]); + const stream = new NodePre18StreamWrapper(rawStream); + const destroySpy = jest.spyOn(stream, "destroy"); + + stream.destroy(); + + expect(destroySpy).toHaveBeenCalledWith(); + }); + + it("should pause the stream and resume", async () => { + const rawStream = (await import("stream")).Readable.from(["test", "test"]); + const stream = new NodePre18StreamWrapper(rawStream); + const pauseSpy = jest.spyOn(stream, "pause"); + + stream.pause(); + expect(stream.isPaused).toBe(true); + stream.resume(); + expect(stream.isPaused).toBe(false); + + expect(pauseSpy).toHaveBeenCalledWith(); + }); + + it("should read the stream", async () => { + const rawStream = (await import("stream")).Readable.from(["test", "test"]); + const stream = new NodePre18StreamWrapper(rawStream); + + expect(await stream.read()).toEqual("test"); + expect(await stream.read()).toEqual("test"); + }); + + it("should read the stream as text", async () => { + const rawStream = (await import("stream")).Readable.from(["test", "test"]); + const stream = new NodePre18StreamWrapper(rawStream); + + const data = await stream.text(); + + expect(data).toEqual("testtest"); + }); + + it("should read the stream as json", async () => { + const rawStream = (await import("stream")).Readable.from([JSON.stringify({ test: "test" })]); + const stream = new NodePre18StreamWrapper(rawStream); + + const data = await stream.json(); + + expect(data).toEqual({ test: "test" }); + }); + + it("should allow use with async iteratable stream", async () => { + const rawStream = (await import("stream")).Readable.from(["test", "test"]); + let data = ""; + const stream = new NodePre18StreamWrapper(rawStream); + for await (const chunk of stream) { + data += chunk; + } + + expect(data).toEqual("testtest"); + }); +}); diff --git a/seed/ts-sdk/grpc-proto/tests/unit/fetcher/stream-wrappers/UndiciStreamWrapper.test.ts b/seed/ts-sdk/grpc-proto/tests/unit/fetcher/stream-wrappers/UndiciStreamWrapper.test.ts new file mode 100644 index 00000000000..1d171ce6c67 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/tests/unit/fetcher/stream-wrappers/UndiciStreamWrapper.test.ts @@ -0,0 +1,153 @@ +import { UndiciStreamWrapper } from "../../../../src/core/fetcher/stream-wrappers/UndiciStreamWrapper"; + +describe("UndiciStreamWrapper", () => { + it("should set encoding to utf-8", async () => { + const rawStream = new ReadableStream(); + const stream = new UndiciStreamWrapper(rawStream); + const setEncodingSpy = jest.spyOn(stream, "setEncoding"); + + stream.setEncoding("utf-8"); + + expect(setEncodingSpy).toHaveBeenCalledWith("utf-8"); + }); + + it("should register an event listener for readable", async () => { + const rawStream = new ReadableStream(); + const stream = new UndiciStreamWrapper(rawStream); + const onSpy = jest.spyOn(stream, "on"); + + stream.on("readable", () => {}); + + expect(onSpy).toHaveBeenCalledWith("readable", expect.any(Function)); + }); + + it("should remove an event listener for data", async () => { + const rawStream = new ReadableStream(); + const stream = new UndiciStreamWrapper(rawStream); + const offSpy = jest.spyOn(stream, "off"); + + const fn = () => {}; + stream.on("data", fn); + stream.off("data", fn); + + expect(offSpy).toHaveBeenCalledWith("data", expect.any(Function)); + }); + + it("should write to dest when calling pipe to writable stream", async () => { + const rawStream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode("test")); + controller.enqueue(new TextEncoder().encode("test")); + controller.close(); + }, + }); + const stream = new UndiciStreamWrapper(rawStream); + const dest = new WritableStream({ + write(chunk) { + expect(chunk).toEqual(new TextEncoder().encode("test")); + }, + }); + + stream.pipe(dest); + }); + + it("should write nothing when calling pipe and unpipe", async () => { + const rawStream = new ReadableStream(); + const stream = new UndiciStreamWrapper(rawStream); + const buffer: Uint8Array[] = []; + const dest = new WritableStream({ + write(chunk) { + buffer.push(chunk); + }, + }); + stream.pipe(dest); + stream.unpipe(dest); + + expect(buffer).toEqual([]); + }); + + it("should destroy the stream", async () => { + const rawStream = new ReadableStream(); + const stream = new UndiciStreamWrapper(rawStream); + const destroySpy = jest.spyOn(stream, "destroy"); + + stream.destroy(); + + expect(destroySpy).toHaveBeenCalled(); + }); + + it("should pause and resume the stream", async () => { + const rawStream = new ReadableStream(); + const stream = new UndiciStreamWrapper(rawStream); + const pauseSpy = jest.spyOn(stream, "pause"); + const resumeSpy = jest.spyOn(stream, "resume"); + + expect(stream.isPaused).toBe(false); + stream.pause(); + expect(stream.isPaused).toBe(true); + stream.resume(); + + expect(pauseSpy).toHaveBeenCalled(); + expect(resumeSpy).toHaveBeenCalled(); + }); + + it("should read the stream", async () => { + const rawStream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode("test")); + controller.enqueue(new TextEncoder().encode("test")); + controller.close(); + }, + }); + const stream = new UndiciStreamWrapper(rawStream); + + expect(await stream.read()).toEqual(new TextEncoder().encode("test")); + expect(await stream.read()).toEqual(new TextEncoder().encode("test")); + }); + + it("should read the stream as text", async () => { + const rawStream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode("test")); + controller.enqueue(new TextEncoder().encode("test")); + controller.close(); + }, + }); + const stream = new UndiciStreamWrapper(rawStream); + + const data = await stream.text(); + + expect(data).toEqual("testtest"); + }); + + it("should read the stream as json", async () => { + const rawStream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode(JSON.stringify({ test: "test" }))); + controller.close(); + }, + }); + const stream = new UndiciStreamWrapper(rawStream); + + const data = await stream.json(); + + expect(data).toEqual({ test: "test" }); + }); + + it("should allow use with async iteratable stream", async () => { + const rawStream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode("test")); + controller.enqueue(new TextEncoder().encode("test")); + controller.close(); + }, + }); + let data = ""; + const stream = new UndiciStreamWrapper(rawStream); + for await (const chunk of stream) { + data += new TextDecoder().decode(chunk); + } + + expect(data).toEqual("testtest"); + }); +}); diff --git a/seed/ts-sdk/grpc-proto/tests/unit/fetcher/stream-wrappers/chooseStreamWrapper.test.ts b/seed/ts-sdk/grpc-proto/tests/unit/fetcher/stream-wrappers/chooseStreamWrapper.test.ts new file mode 100644 index 00000000000..aff7579e47a --- /dev/null +++ b/seed/ts-sdk/grpc-proto/tests/unit/fetcher/stream-wrappers/chooseStreamWrapper.test.ts @@ -0,0 +1,43 @@ +import { RUNTIME } from "../../../../src/core/runtime"; +import { chooseStreamWrapper } from "../../../../src/core/fetcher/stream-wrappers/chooseStreamWrapper"; +import { Node18UniversalStreamWrapper } from "../../../../src/core/fetcher/stream-wrappers/Node18UniversalStreamWrapper"; +import { NodePre18StreamWrapper } from "../../../../src/core/fetcher/stream-wrappers/NodePre18StreamWrapper"; +import { UndiciStreamWrapper } from "../../../../src/core/fetcher/stream-wrappers/UndiciStreamWrapper"; + +describe("chooseStreamWrapper", () => { + beforeEach(() => { + RUNTIME.type = "unknown"; + RUNTIME.parsedVersion = 0; + }); + + it('should return a Node18UniversalStreamWrapper when RUNTIME.type is "node" and RUNTIME.parsedVersion is not null and RUNTIME.parsedVersion is greater than or equal to 18', async () => { + const expected = new Node18UniversalStreamWrapper(new ReadableStream()); + RUNTIME.type = "node"; + RUNTIME.parsedVersion = 18; + + const result = await chooseStreamWrapper(new ReadableStream()); + + expect(JSON.stringify(result)).toBe(JSON.stringify(expected)); + }); + + it('should return a NodePre18StreamWrapper when RUNTIME.type is "node" and RUNTIME.parsedVersion is not null and RUNTIME.parsedVersion is less than 18', async () => { + const stream = await import("stream"); + const expected = new NodePre18StreamWrapper(new stream.Readable()); + + RUNTIME.type = "node"; + RUNTIME.parsedVersion = 16; + + const result = await chooseStreamWrapper(new stream.Readable()); + + expect(JSON.stringify(result)).toEqual(JSON.stringify(expected)); + }); + + it('should return a Undici when RUNTIME.type is not "node"', async () => { + const expected = new UndiciStreamWrapper(new ReadableStream()); + RUNTIME.type = "browser"; + + const result = await chooseStreamWrapper(new ReadableStream()); + + expect(JSON.stringify(result)).toEqual(JSON.stringify(expected)); + }); +}); diff --git a/seed/ts-sdk/grpc-proto/tests/unit/zurg/date/date.test.ts b/seed/ts-sdk/grpc-proto/tests/unit/zurg/date/date.test.ts new file mode 100644 index 00000000000..2790268a09c --- /dev/null +++ b/seed/ts-sdk/grpc-proto/tests/unit/zurg/date/date.test.ts @@ -0,0 +1,31 @@ +import { date } from "../../../../src/core/schemas/builders/date"; +import { itSchema } from "../utils/itSchema"; +import { itValidateJson, itValidateParse } from "../utils/itValidate"; + +describe("date", () => { + itSchema("converts between raw ISO string and parsed Date", date(), { + raw: "2022-09-29T05:41:21.939Z", + parsed: new Date("2022-09-29T05:41:21.939Z"), + }); + + itValidateParse("non-string", date(), 42, [ + { + message: "Expected string. Received 42.", + path: [], + }, + ]); + + itValidateParse("non-ISO", date(), "hello world", [ + { + message: 'Expected ISO 8601 date string. Received "hello world".', + path: [], + }, + ]); + + itValidateJson("non-Date", date(), "hello", [ + { + message: 'Expected Date object. Received "hello".', + path: [], + }, + ]); +}); diff --git a/seed/ts-sdk/grpc-proto/tests/unit/zurg/enum/enum.test.ts b/seed/ts-sdk/grpc-proto/tests/unit/zurg/enum/enum.test.ts new file mode 100644 index 00000000000..ab0df0285cd --- /dev/null +++ b/seed/ts-sdk/grpc-proto/tests/unit/zurg/enum/enum.test.ts @@ -0,0 +1,30 @@ +import { enum_ } from "../../../../src/core/schemas/builders/enum"; +import { itSchemaIdentity } from "../utils/itSchema"; +import { itValidate } from "../utils/itValidate"; + +describe("enum", () => { + itSchemaIdentity(enum_(["A", "B", "C"]), "A"); + + itSchemaIdentity(enum_(["A", "B", "C"]), "D" as any, { + opts: { allowUnrecognizedEnumValues: true }, + }); + + itValidate("invalid enum", enum_(["A", "B", "C"]), "D", [ + { + message: 'Expected enum. Received "D".', + path: [], + }, + ]); + + itValidate( + "non-string", + enum_(["A", "B", "C"]), + [], + [ + { + message: "Expected string. Received list.", + path: [], + }, + ] + ); +}); diff --git a/seed/ts-sdk/grpc-proto/tests/unit/zurg/lazy/lazy.test.ts b/seed/ts-sdk/grpc-proto/tests/unit/zurg/lazy/lazy.test.ts new file mode 100644 index 00000000000..6906bf4cf91 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/tests/unit/zurg/lazy/lazy.test.ts @@ -0,0 +1,57 @@ +import { Schema } from "../../../../src/core/schemas/Schema"; +import { lazy, list, object, string } from "../../../../src/core/schemas/builders"; +import { itSchemaIdentity } from "../utils/itSchema"; + +describe("lazy", () => { + it("doesn't run immediately", () => { + let wasRun = false; + lazy(() => { + wasRun = true; + return string(); + }); + expect(wasRun).toBe(false); + }); + + it("only runs first time", async () => { + let count = 0; + const schema = lazy(() => { + count++; + return string(); + }); + await schema.parse("hello"); + await schema.json("world"); + expect(count).toBe(1); + }); + + itSchemaIdentity( + lazy(() => object({})), + { foo: "hello" }, + { + title: "passes opts through", + opts: { unrecognizedObjectKeys: "passthrough" }, + } + ); + + itSchemaIdentity( + lazy(() => object({ foo: string() })), + { foo: "hello" } + ); + + // eslint-disable-next-line jest/expect-expect + it("self-referencial schema doesn't compile", () => { + () => { + // @ts-expect-error + const a = lazy(() => object({ foo: a })); + }; + }); + + // eslint-disable-next-line jest/expect-expect + it("self-referencial compiles with explicit type", () => { + () => { + interface TreeNode { + children: TreeNode[]; + } + const TreeNode: Schema = lazy(() => object({ children: list(TreeNode) })); + }; + }); +}); diff --git a/seed/ts-sdk/grpc-proto/tests/unit/zurg/lazy/lazyObject.test.ts b/seed/ts-sdk/grpc-proto/tests/unit/zurg/lazy/lazyObject.test.ts new file mode 100644 index 00000000000..8813cc9fbb4 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/tests/unit/zurg/lazy/lazyObject.test.ts @@ -0,0 +1,18 @@ +import { lazyObject, number, object, string } from "../../../../src/core/schemas/builders"; +import { itSchemaIdentity } from "../utils/itSchema"; + +describe("lazy", () => { + itSchemaIdentity( + lazyObject(() => object({ foo: string() })), + { foo: "hello" } + ); + + itSchemaIdentity( + lazyObject(() => object({ foo: string() })).extend(object({ bar: number() })), + { + foo: "hello", + bar: 42, + }, + { title: "returned schema has object utils" } + ); +}); diff --git a/seed/ts-sdk/grpc-proto/tests/unit/zurg/lazy/recursive/a.ts b/seed/ts-sdk/grpc-proto/tests/unit/zurg/lazy/recursive/a.ts new file mode 100644 index 00000000000..8b7d5e40cfa --- /dev/null +++ b/seed/ts-sdk/grpc-proto/tests/unit/zurg/lazy/recursive/a.ts @@ -0,0 +1,7 @@ +import { object } from "../../../../../src/core/schemas/builders/object"; +import { schemaB } from "./b"; + +// @ts-expect-error +export const schemaA = object({ + b: schemaB, +}); diff --git a/seed/ts-sdk/grpc-proto/tests/unit/zurg/lazy/recursive/b.ts b/seed/ts-sdk/grpc-proto/tests/unit/zurg/lazy/recursive/b.ts new file mode 100644 index 00000000000..fb219d54c8e --- /dev/null +++ b/seed/ts-sdk/grpc-proto/tests/unit/zurg/lazy/recursive/b.ts @@ -0,0 +1,8 @@ +import { object } from "../../../../../src/core/schemas/builders/object"; +import { optional } from "../../../../../src/core/schemas/builders/schema-utils"; +import { schemaA } from "./a"; + +// @ts-expect-error +export const schemaB = object({ + a: optional(schemaA), +}); diff --git a/seed/ts-sdk/grpc-proto/tests/unit/zurg/list/list.test.ts b/seed/ts-sdk/grpc-proto/tests/unit/zurg/list/list.test.ts new file mode 100644 index 00000000000..424ed642db2 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/tests/unit/zurg/list/list.test.ts @@ -0,0 +1,41 @@ +import { list, object, property, string } from "../../../../src/core/schemas/builders"; +import { itSchema, itSchemaIdentity } from "../utils/itSchema"; +import { itValidate } from "../utils/itValidate"; + +describe("list", () => { + itSchemaIdentity(list(string()), ["hello", "world"], { + title: "functions as identity when item type is primitive", + }); + + itSchema( + "converts objects correctly", + list( + object({ + helloWorld: property("hello_world", string()), + }) + ), + { + raw: [{ hello_world: "123" }], + parsed: [{ helloWorld: "123" }], + } + ); + + itValidate("not a list", list(string()), 42, [ + { + path: [], + message: "Expected list. Received 42.", + }, + ]); + + itValidate( + "invalid item type", + list(string()), + [42], + [ + { + path: ["[0]"], + message: "Expected string. Received 42.", + }, + ] + ); +}); diff --git a/seed/ts-sdk/grpc-proto/tests/unit/zurg/literals/stringLiteral.test.ts b/seed/ts-sdk/grpc-proto/tests/unit/zurg/literals/stringLiteral.test.ts new file mode 100644 index 00000000000..fa6c88873c6 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/tests/unit/zurg/literals/stringLiteral.test.ts @@ -0,0 +1,21 @@ +import { stringLiteral } from "../../../../src/core/schemas/builders"; +import { itSchemaIdentity } from "../utils/itSchema"; +import { itValidate } from "../utils/itValidate"; + +describe("stringLiteral", () => { + itSchemaIdentity(stringLiteral("A"), "A"); + + itValidate("incorrect string", stringLiteral("A"), "B", [ + { + path: [], + message: 'Expected "A". Received "B".', + }, + ]); + + itValidate("non-string", stringLiteral("A"), 42, [ + { + path: [], + message: 'Expected "A". Received 42.', + }, + ]); +}); diff --git a/seed/ts-sdk/grpc-proto/tests/unit/zurg/object-like/withParsedProperties.test.ts b/seed/ts-sdk/grpc-proto/tests/unit/zurg/object-like/withParsedProperties.test.ts new file mode 100644 index 00000000000..9f5dd0ed39b --- /dev/null +++ b/seed/ts-sdk/grpc-proto/tests/unit/zurg/object-like/withParsedProperties.test.ts @@ -0,0 +1,57 @@ +import { object, property, string, stringLiteral } from "../../../../src/core/schemas/builders"; + +describe("withParsedProperties", () => { + it("Added properties included on parsed object", async () => { + const schema = object({ + foo: property("raw_foo", string()), + bar: stringLiteral("bar"), + }).withParsedProperties({ + printFoo: (parsed) => () => parsed.foo, + printHelloWorld: () => () => "Hello world", + helloWorld: "Hello world", + }); + + const parsed = await schema.parse({ raw_foo: "value of foo", bar: "bar" }); + if (!parsed.ok) { + throw new Error("Failed to parse"); + } + expect(parsed.value.printFoo()).toBe("value of foo"); + expect(parsed.value.printHelloWorld()).toBe("Hello world"); + expect(parsed.value.helloWorld).toBe("Hello world"); + }); + + it("Added property is removed on raw object", async () => { + const schema = object({ + foo: property("raw_foo", string()), + bar: stringLiteral("bar"), + }).withParsedProperties({ + printFoo: (parsed) => () => parsed.foo, + }); + + const original = { raw_foo: "value of foo", bar: "bar" } as const; + const parsed = await schema.parse(original); + if (!parsed.ok) { + throw new Error("Failed to parse()"); + } + + const raw = await schema.json(parsed.value); + + if (!raw.ok) { + throw new Error("Failed to json()"); + } + + expect(raw.value).toEqual(original); + }); + + describe("compile", () => { + // eslint-disable-next-line jest/expect-expect + it("doesn't compile with non-object schema", () => { + () => + object({ + foo: string(), + }) + // @ts-expect-error + .withParsedProperties(42); + }); + }); +}); diff --git a/seed/ts-sdk/grpc-proto/tests/unit/zurg/object/extend.test.ts b/seed/ts-sdk/grpc-proto/tests/unit/zurg/object/extend.test.ts new file mode 100644 index 00000000000..54fc8c4ebf8 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/tests/unit/zurg/object/extend.test.ts @@ -0,0 +1,89 @@ +import { boolean, object, property, string, stringLiteral } from "../../../../src/core/schemas/builders"; +import { itSchema, itSchemaIdentity } from "../utils/itSchema"; + +describe("extend", () => { + itSchemaIdentity( + object({ + foo: string(), + }).extend( + object({ + bar: stringLiteral("bar"), + }) + ), + { + foo: "", + bar: "bar", + } as const, + { + title: "extended properties are included in schema", + } + ); + + itSchemaIdentity( + object({ + foo: string(), + }) + .extend( + object({ + bar: stringLiteral("bar"), + }) + ) + .extend( + object({ + baz: boolean(), + }) + ), + { + foo: "", + bar: "bar", + baz: true, + } as const, + { + title: "extensions can be extended", + } + ); + + itSchema( + "converts nested object", + object({ + item: object({ + helloWorld: property("hello_world", string()), + }), + }).extend( + object({ + goodbye: property("goodbye_raw", string()), + }) + ), + { + raw: { item: { hello_world: "yo" }, goodbye_raw: "peace" }, + parsed: { item: { helloWorld: "yo" }, goodbye: "peace" }, + } + ); + + itSchema( + "extensions work with raw/parsed property name conversions", + object({ + item: property("item_raw", string()), + }).extend( + object({ + goodbye: property("goodbye_raw", string()), + }) + ), + { + raw: { item_raw: "hi", goodbye_raw: "peace" }, + parsed: { item: "hi", goodbye: "peace" }, + } + ); + + describe("compile", () => { + // eslint-disable-next-line jest/expect-expect + it("doesn't compile with non-object schema", () => { + () => + object({ + foo: string(), + }) + // @ts-expect-error + .extend([]); + }); + }); +}); diff --git a/seed/ts-sdk/grpc-proto/tests/unit/zurg/object/object.test.ts b/seed/ts-sdk/grpc-proto/tests/unit/zurg/object/object.test.ts new file mode 100644 index 00000000000..0acf0e240f6 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/tests/unit/zurg/object/object.test.ts @@ -0,0 +1,255 @@ +import { any, number, object, property, string, stringLiteral, unknown } from "../../../../src/core/schemas/builders"; +import { itJson, itParse, itSchema, itSchemaIdentity } from "../utils/itSchema"; +import { itValidate } from "../utils/itValidate"; + +describe("object", () => { + itSchemaIdentity( + object({ + foo: string(), + bar: stringLiteral("bar"), + }), + { + foo: "", + bar: "bar", + }, + { + title: "functions as identity when values are primitives and property() isn't used", + } + ); + + itSchema( + "uses raw key from property()", + object({ + foo: property("raw_foo", string()), + bar: stringLiteral("bar"), + }), + { + raw: { raw_foo: "foo", bar: "bar" }, + parsed: { foo: "foo", bar: "bar" }, + } + ); + + itSchema( + "keys with unknown type can be omitted", + object({ + foo: unknown(), + }), + { + raw: {}, + parsed: {}, + } + ); + + itSchema( + "keys with any type can be omitted", + object({ + foo: any(), + }), + { + raw: {}, + parsed: {}, + } + ); + + describe("unrecognizedObjectKeys", () => { + describe("parse", () => { + itParse( + 'includes unknown values when unrecognizedObjectKeys === "passthrough"', + object({ + foo: property("raw_foo", string()), + bar: stringLiteral("bar"), + }), + { + raw: { + raw_foo: "foo", + bar: "bar", + // @ts-expect-error + baz: "yoyo", + }, + parsed: { + foo: "foo", + bar: "bar", + // @ts-expect-error + baz: "yoyo", + }, + opts: { + unrecognizedObjectKeys: "passthrough", + }, + } + ); + + itParse( + 'strips unknown values when unrecognizedObjectKeys === "strip"', + object({ + foo: property("raw_foo", string()), + bar: stringLiteral("bar"), + }), + { + raw: { + raw_foo: "foo", + bar: "bar", + // @ts-expect-error + baz: "yoyo", + }, + parsed: { + foo: "foo", + bar: "bar", + }, + opts: { + unrecognizedObjectKeys: "strip", + }, + } + ); + }); + + describe("json", () => { + itJson( + 'includes unknown values when unrecognizedObjectKeys === "passthrough"', + object({ + foo: property("raw_foo", string()), + bar: stringLiteral("bar"), + }), + { + raw: { + raw_foo: "foo", + bar: "bar", + // @ts-expect-error + baz: "yoyo", + }, + parsed: { + foo: "foo", + bar: "bar", + // @ts-expect-error + baz: "yoyo", + }, + opts: { + unrecognizedObjectKeys: "passthrough", + }, + } + ); + + itJson( + 'strips unknown values when unrecognizedObjectKeys === "strip"', + object({ + foo: property("raw_foo", string()), + bar: stringLiteral("bar"), + }), + { + raw: { + raw_foo: "foo", + bar: "bar", + }, + parsed: { + foo: "foo", + bar: "bar", + // @ts-expect-error + baz: "yoyo", + }, + opts: { + unrecognizedObjectKeys: "strip", + }, + } + ); + }); + }); + + describe("nullish properties", () => { + itSchema("missing properties are not added", object({ foo: property("raw_foo", string().optional()) }), { + raw: {}, + parsed: {}, + }); + + itSchema("undefined properties are not dropped", object({ foo: property("raw_foo", string().optional()) }), { + raw: { raw_foo: null }, + parsed: { foo: undefined }, + }); + + itSchema("null properties are not dropped", object({ foo: property("raw_foo", string().optional()) }), { + raw: { raw_foo: null }, + parsed: { foo: undefined }, + }); + + describe("extensions", () => { + itSchema( + "undefined properties are not dropped", + object({}).extend(object({ foo: property("raw_foo", string().optional()) })), + { + raw: { raw_foo: null }, + parsed: { foo: undefined }, + } + ); + + describe("parse()", () => { + itParse( + "null properties are not dropped", + object({}).extend(object({ foo: property("raw_foo", string().optional()) })), + { + raw: { raw_foo: null }, + parsed: { foo: undefined }, + } + ); + }); + }); + }); + + itValidate( + "missing property", + object({ + foo: string(), + bar: stringLiteral("bar"), + }), + { foo: "hello" }, + [ + { + path: [], + message: 'Missing required key "bar"', + }, + ] + ); + + itValidate( + "extra property", + object({ + foo: string(), + bar: stringLiteral("bar"), + }), + { foo: "hello", bar: "bar", baz: 42 }, + [ + { + path: ["baz"], + message: 'Unexpected key "baz"', + }, + ] + ); + + itValidate( + "not an object", + object({ + foo: string(), + bar: stringLiteral("bar"), + }), + [], + [ + { + path: [], + message: "Expected object. Received list.", + }, + ] + ); + + itValidate( + "nested validation error", + object({ + foo: object({ + bar: number(), + }), + }), + { foo: { bar: "hello" } }, + [ + { + path: ["foo", "bar"], + message: 'Expected number. Received "hello".', + }, + ] + ); +}); diff --git a/seed/ts-sdk/grpc-proto/tests/unit/zurg/object/objectWithoutOptionalProperties.test.ts b/seed/ts-sdk/grpc-proto/tests/unit/zurg/object/objectWithoutOptionalProperties.test.ts new file mode 100644 index 00000000000..d87a65febfd --- /dev/null +++ b/seed/ts-sdk/grpc-proto/tests/unit/zurg/object/objectWithoutOptionalProperties.test.ts @@ -0,0 +1,21 @@ +import { objectWithoutOptionalProperties, string, stringLiteral } from "../../../../src/core/schemas/builders"; +import { itSchema } from "../utils/itSchema"; + +describe("objectWithoutOptionalProperties", () => { + itSchema( + "all properties are required", + objectWithoutOptionalProperties({ + foo: string(), + bar: stringLiteral("bar").optional(), + }), + { + raw: { + foo: "hello", + }, + // @ts-expect-error + parsed: { + foo: "hello", + }, + } + ); +}); diff --git a/seed/ts-sdk/grpc-proto/tests/unit/zurg/primitives/any.test.ts b/seed/ts-sdk/grpc-proto/tests/unit/zurg/primitives/any.test.ts new file mode 100644 index 00000000000..1adbbe2a838 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/tests/unit/zurg/primitives/any.test.ts @@ -0,0 +1,6 @@ +import { any } from "../../../../src/core/schemas/builders"; +import { itSchemaIdentity } from "../utils/itSchema"; + +describe("any", () => { + itSchemaIdentity(any(), true); +}); diff --git a/seed/ts-sdk/grpc-proto/tests/unit/zurg/primitives/boolean.test.ts b/seed/ts-sdk/grpc-proto/tests/unit/zurg/primitives/boolean.test.ts new file mode 100644 index 00000000000..897a8295dca --- /dev/null +++ b/seed/ts-sdk/grpc-proto/tests/unit/zurg/primitives/boolean.test.ts @@ -0,0 +1,14 @@ +import { boolean } from "../../../../src/core/schemas/builders"; +import { itSchemaIdentity } from "../utils/itSchema"; +import { itValidate } from "../utils/itValidate"; + +describe("boolean", () => { + itSchemaIdentity(boolean(), true); + + itValidate("non-boolean", boolean(), {}, [ + { + path: [], + message: "Expected boolean. Received object.", + }, + ]); +}); diff --git a/seed/ts-sdk/grpc-proto/tests/unit/zurg/primitives/number.test.ts b/seed/ts-sdk/grpc-proto/tests/unit/zurg/primitives/number.test.ts new file mode 100644 index 00000000000..2d01415a60b --- /dev/null +++ b/seed/ts-sdk/grpc-proto/tests/unit/zurg/primitives/number.test.ts @@ -0,0 +1,14 @@ +import { number } from "../../../../src/core/schemas/builders"; +import { itSchemaIdentity } from "../utils/itSchema"; +import { itValidate } from "../utils/itValidate"; + +describe("number", () => { + itSchemaIdentity(number(), 42); + + itValidate("non-number", number(), "hello", [ + { + path: [], + message: 'Expected number. Received "hello".', + }, + ]); +}); diff --git a/seed/ts-sdk/grpc-proto/tests/unit/zurg/primitives/string.test.ts b/seed/ts-sdk/grpc-proto/tests/unit/zurg/primitives/string.test.ts new file mode 100644 index 00000000000..57b2368784a --- /dev/null +++ b/seed/ts-sdk/grpc-proto/tests/unit/zurg/primitives/string.test.ts @@ -0,0 +1,14 @@ +import { string } from "../../../../src/core/schemas/builders"; +import { itSchemaIdentity } from "../utils/itSchema"; +import { itValidate } from "../utils/itValidate"; + +describe("string", () => { + itSchemaIdentity(string(), "hello"); + + itValidate("non-string", string(), 42, [ + { + path: [], + message: "Expected string. Received 42.", + }, + ]); +}); diff --git a/seed/ts-sdk/grpc-proto/tests/unit/zurg/primitives/unknown.test.ts b/seed/ts-sdk/grpc-proto/tests/unit/zurg/primitives/unknown.test.ts new file mode 100644 index 00000000000..4d17a7dbd00 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/tests/unit/zurg/primitives/unknown.test.ts @@ -0,0 +1,6 @@ +import { unknown } from "../../../../src/core/schemas/builders"; +import { itSchemaIdentity } from "../utils/itSchema"; + +describe("unknown", () => { + itSchemaIdentity(unknown(), true); +}); diff --git a/seed/ts-sdk/grpc-proto/tests/unit/zurg/record/record.test.ts b/seed/ts-sdk/grpc-proto/tests/unit/zurg/record/record.test.ts new file mode 100644 index 00000000000..7e4ba39cc55 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/tests/unit/zurg/record/record.test.ts @@ -0,0 +1,34 @@ +import { number, record, string } from "../../../../src/core/schemas/builders"; +import { itSchemaIdentity } from "../utils/itSchema"; +import { itValidate } from "../utils/itValidate"; + +describe("record", () => { + itSchemaIdentity(record(string(), string()), { hello: "world" }); + itSchemaIdentity(record(number(), string()), { 42: "world" }); + + itValidate( + "non-record", + record(number(), string()), + [], + [ + { + path: [], + message: "Expected object. Received list.", + }, + ] + ); + + itValidate("invalid key type", record(number(), string()), { hello: "world" }, [ + { + path: ["hello (key)"], + message: 'Expected number. Received "hello".', + }, + ]); + + itValidate("invalid value type", record(string(), number()), { hello: "world" }, [ + { + path: ["hello"], + message: 'Expected number. Received "world".', + }, + ]); +}); diff --git a/seed/ts-sdk/grpc-proto/tests/unit/zurg/schema-utils/getSchemaUtils.test.ts b/seed/ts-sdk/grpc-proto/tests/unit/zurg/schema-utils/getSchemaUtils.test.ts new file mode 100644 index 00000000000..da10086bc1d --- /dev/null +++ b/seed/ts-sdk/grpc-proto/tests/unit/zurg/schema-utils/getSchemaUtils.test.ts @@ -0,0 +1,83 @@ +import { object, string } from "../../../../src/core/schemas/builders"; +import { itSchema } from "../utils/itSchema"; + +describe("getSchemaUtils", () => { + describe("optional()", () => { + itSchema("optional fields allow original schema", string().optional(), { + raw: "hello", + parsed: "hello", + }); + + itSchema("optional fields are not required", string().optional(), { + raw: null, + parsed: undefined, + }); + }); + + describe("transform()", () => { + itSchema( + "transorm and untransform run correctly", + string().transform({ + transform: (x) => x + "X", + untransform: (x) => (x as string).slice(0, -1), + }), + { + raw: "hello", + parsed: "helloX", + } + ); + }); + + describe("parseOrThrow()", () => { + it("parses valid value", async () => { + const value = string().parseOrThrow("hello"); + expect(value).toBe("hello"); + }); + + it("throws on invalid value", async () => { + const value = () => object({ a: string(), b: string() }).parseOrThrow({ a: 24 }); + expect(value).toThrowError(new Error('a: Expected string. Received 24.; Missing required key "b"')); + }); + }); + + describe("jsonOrThrow()", () => { + it("serializes valid value", async () => { + const value = string().jsonOrThrow("hello"); + expect(value).toBe("hello"); + }); + + it("throws on invalid value", async () => { + const value = () => object({ a: string(), b: string() }).jsonOrThrow({ a: 24 }); + expect(value).toThrowError(new Error('a: Expected string. Received 24.; Missing required key "b"')); + }); + }); + + describe("omitUndefined", () => { + it("serializes undefined as null", async () => { + const value = object({ + a: string().optional(), + b: string().optional(), + }).jsonOrThrow({ + a: "hello", + b: undefined, + }); + expect(value).toEqual({ a: "hello", b: null }); + }); + + it("omits undefined values", async () => { + const value = object({ + a: string().optional(), + b: string().optional(), + }).jsonOrThrow( + { + a: "hello", + b: undefined, + }, + { + omitUndefined: true, + } + ); + expect(value).toEqual({ a: "hello" }); + }); + }); +}); diff --git a/seed/ts-sdk/grpc-proto/tests/unit/zurg/schema.test.ts b/seed/ts-sdk/grpc-proto/tests/unit/zurg/schema.test.ts new file mode 100644 index 00000000000..94089a9a91b --- /dev/null +++ b/seed/ts-sdk/grpc-proto/tests/unit/zurg/schema.test.ts @@ -0,0 +1,78 @@ +import { + boolean, + discriminant, + list, + number, + object, + string, + stringLiteral, + union, +} from "../../../src/core/schemas/builders"; +import { booleanLiteral } from "../../../src/core/schemas/builders/literals/booleanLiteral"; +import { property } from "../../../src/core/schemas/builders/object/property"; +import { itSchema } from "./utils/itSchema"; + +describe("Schema", () => { + itSchema( + "large nested object", + object({ + a: string(), + b: stringLiteral("b value"), + c: property( + "raw_c", + list( + object({ + animal: union(discriminant("type", "_type"), { + dog: object({ value: boolean() }), + cat: object({ value: property("raw_cat", number()) }), + }), + }) + ) + ), + d: property("raw_d", boolean()), + e: booleanLiteral(true), + }), + { + raw: { + a: "hello", + b: "b value", + raw_c: [ + { + animal: { + _type: "dog", + value: true, + }, + }, + { + animal: { + _type: "cat", + raw_cat: 42, + }, + }, + ], + raw_d: false, + e: true, + }, + parsed: { + a: "hello", + b: "b value", + c: [ + { + animal: { + type: "dog", + value: true, + }, + }, + { + animal: { + type: "cat", + value: 42, + }, + }, + ], + d: false, + e: true, + }, + } + ); +}); diff --git a/seed/ts-sdk/grpc-proto/tests/unit/zurg/set/set.test.ts b/seed/ts-sdk/grpc-proto/tests/unit/zurg/set/set.test.ts new file mode 100644 index 00000000000..e17f908c80e --- /dev/null +++ b/seed/ts-sdk/grpc-proto/tests/unit/zurg/set/set.test.ts @@ -0,0 +1,48 @@ +import { set, string } from "../../../../src/core/schemas/builders"; +import { itSchema } from "../utils/itSchema"; +import { itValidateJson, itValidateParse } from "../utils/itValidate"; + +describe("set", () => { + itSchema("converts between raw list and parsed Set", set(string()), { + raw: ["A", "B"], + parsed: new Set(["A", "B"]), + }); + + itValidateParse("not a list", set(string()), 42, [ + { + path: [], + message: "Expected list. Received 42.", + }, + ]); + + itValidateJson( + "not a Set", + set(string()), + [], + [ + { + path: [], + message: "Expected Set. Received list.", + }, + ] + ); + + itValidateParse( + "invalid item type", + set(string()), + [42], + [ + { + path: ["[0]"], + message: "Expected string. Received 42.", + }, + ] + ); + + itValidateJson("invalid item type", set(string()), new Set([42]), [ + { + path: ["[0]"], + message: "Expected string. Received 42.", + }, + ]); +}); diff --git a/seed/ts-sdk/grpc-proto/tests/unit/zurg/skipValidation.test.ts b/seed/ts-sdk/grpc-proto/tests/unit/zurg/skipValidation.test.ts new file mode 100644 index 00000000000..5dc88096a9f --- /dev/null +++ b/seed/ts-sdk/grpc-proto/tests/unit/zurg/skipValidation.test.ts @@ -0,0 +1,45 @@ +/* eslint-disable no-console */ + +import { boolean, number, object, property, string, undiscriminatedUnion } from "../../../src/core/schemas/builders"; + +describe("skipValidation", () => { + it("allows data that doesn't conform to the schema", async () => { + const warningLogs: string[] = []; + const originalConsoleWarn = console.warn; + console.warn = (...args) => warningLogs.push(args.join(" ")); + + const schema = object({ + camelCase: property("snake_case", string()), + numberProperty: number(), + requiredProperty: boolean(), + anyPrimitive: undiscriminatedUnion([string(), number(), boolean()]), + }); + + const parsed = await schema.parse( + { + snake_case: "hello", + numberProperty: "oops", + anyPrimitive: true, + }, + { + skipValidation: true, + } + ); + + expect(parsed).toEqual({ + ok: true, + value: { + camelCase: "hello", + numberProperty: "oops", + anyPrimitive: true, + }, + }); + + expect(warningLogs).toEqual([ + `Failed to validate. + - numberProperty: Expected number. Received "oops".`, + ]); + + console.warn = originalConsoleWarn; + }); +}); diff --git a/seed/ts-sdk/grpc-proto/tests/unit/zurg/undiscriminated-union/undiscriminatedUnion.test.ts b/seed/ts-sdk/grpc-proto/tests/unit/zurg/undiscriminated-union/undiscriminatedUnion.test.ts new file mode 100644 index 00000000000..0e66433371c --- /dev/null +++ b/seed/ts-sdk/grpc-proto/tests/unit/zurg/undiscriminated-union/undiscriminatedUnion.test.ts @@ -0,0 +1,44 @@ +import { number, object, property, string, undiscriminatedUnion } from "../../../../src/core/schemas/builders"; +import { itSchema, itSchemaIdentity } from "../utils/itSchema"; + +describe("undiscriminatedUnion", () => { + itSchemaIdentity(undiscriminatedUnion([string(), number()]), "hello world"); + + itSchemaIdentity(undiscriminatedUnion([object({ hello: string() }), object({ goodbye: string() })]), { + goodbye: "foo", + }); + + itSchema( + "Correctly transforms", + undiscriminatedUnion([object({ hello: string() }), object({ helloWorld: property("hello_world", string()) })]), + { + raw: { hello_world: "foo " }, + parsed: { helloWorld: "foo " }, + } + ); + + it("Returns errors for all variants", async () => { + const result = await undiscriminatedUnion([string(), number()]).parse(true); + if (result.ok) { + throw new Error("Unexpectedly passed validation"); + } + expect(result.errors).toEqual([ + { + message: "[Variant 0] Expected string. Received true.", + path: [], + }, + { + message: "[Variant 1] Expected number. Received true.", + path: [], + }, + ]); + }); + + describe("compile", () => { + // eslint-disable-next-line jest/expect-expect + it("doesn't compile with zero members", () => { + // @ts-expect-error + () => undiscriminatedUnion([]); + }); + }); +}); diff --git a/seed/ts-sdk/grpc-proto/tests/unit/zurg/union/union.test.ts b/seed/ts-sdk/grpc-proto/tests/unit/zurg/union/union.test.ts new file mode 100644 index 00000000000..790184603ac --- /dev/null +++ b/seed/ts-sdk/grpc-proto/tests/unit/zurg/union/union.test.ts @@ -0,0 +1,113 @@ +import { boolean, discriminant, number, object, string, union } from "../../../../src/core/schemas/builders"; +import { itSchema, itSchemaIdentity } from "../utils/itSchema"; +import { itValidate } from "../utils/itValidate"; + +describe("union", () => { + itSchemaIdentity( + union("type", { + lion: object({ + meows: boolean(), + }), + giraffe: object({ + heightInInches: number(), + }), + }), + { type: "lion", meows: true }, + { title: "doesn't transform discriminant when it's a string" } + ); + + itSchema( + "transforms discriminant when it's a discriminant()", + union(discriminant("type", "_type"), { + lion: object({ meows: boolean() }), + giraffe: object({ heightInInches: number() }), + }), + { + raw: { _type: "lion", meows: true }, + parsed: { type: "lion", meows: true }, + } + ); + + describe("allowUnrecognizedUnionMembers", () => { + itSchema( + "transforms discriminant & passes through values when discriminant value is unrecognized", + union(discriminant("type", "_type"), { + lion: object({ meows: boolean() }), + giraffe: object({ heightInInches: number() }), + }), + { + // @ts-expect-error + raw: { _type: "moose", isAMoose: true }, + // @ts-expect-error + parsed: { type: "moose", isAMoose: true }, + opts: { + allowUnrecognizedUnionMembers: true, + }, + } + ); + }); + + describe("withParsedProperties", () => { + it("Added property is included on parsed object", async () => { + const schema = union("type", { + lion: object({}), + tiger: object({ value: string() }), + }).withParsedProperties({ + printType: (parsed) => () => parsed.type, + }); + + const parsed = await schema.parse({ type: "lion" }); + if (!parsed.ok) { + throw new Error("Failed to parse"); + } + expect(parsed.value.printType()).toBe("lion"); + }); + }); + + itValidate( + "non-object", + union("type", { + lion: object({}), + tiger: object({ value: string() }), + }), + [], + [ + { + path: [], + message: "Expected object. Received list.", + }, + ] + ); + + itValidate( + "missing discriminant", + union("type", { + lion: object({}), + tiger: object({ value: string() }), + }), + {}, + [ + { + path: [], + message: 'Missing discriminant ("type")', + }, + ] + ); + + itValidate( + "unrecognized discriminant value", + union("type", { + lion: object({}), + tiger: object({ value: string() }), + }), + { + type: "bear", + }, + [ + { + path: ["type"], + message: 'Expected enum. Received "bear".', + }, + ] + ); +}); diff --git a/seed/ts-sdk/grpc-proto/tests/unit/zurg/utils/itSchema.ts b/seed/ts-sdk/grpc-proto/tests/unit/zurg/utils/itSchema.ts new file mode 100644 index 00000000000..67b6c928175 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/tests/unit/zurg/utils/itSchema.ts @@ -0,0 +1,78 @@ +/* eslint-disable jest/no-export */ +import { Schema, SchemaOptions } from "../../../../src/core/schemas/Schema"; + +export function itSchemaIdentity( + schema: Schema, + value: T, + { title = "functions as identity", opts }: { title?: string; opts?: SchemaOptions } = {} +): void { + itSchema(title, schema, { raw: value, parsed: value, opts }); +} + +export function itSchema( + title: string, + schema: Schema, + { + raw, + parsed, + opts, + only = false, + }: { + raw: Raw; + parsed: Parsed; + opts?: SchemaOptions; + only?: boolean; + } +): void { + // eslint-disable-next-line jest/valid-title + (only ? describe.only : describe)(title, () => { + itParse("parse()", schema, { raw, parsed, opts }); + itJson("json()", schema, { raw, parsed, opts }); + }); +} + +export function itParse( + title: string, + schema: Schema, + { + raw, + parsed, + opts, + }: { + raw: Raw; + parsed: Parsed; + opts?: SchemaOptions; + } +): void { + // eslint-disable-next-line jest/valid-title + it(title, () => { + const maybeValid = schema.parse(raw, opts); + if (!maybeValid.ok) { + throw new Error("Failed to parse() " + JSON.stringify(maybeValid.errors, undefined, 4)); + } + expect(maybeValid.value).toStrictEqual(parsed); + }); +} + +export function itJson( + title: string, + schema: Schema, + { + raw, + parsed, + opts, + }: { + raw: Raw; + parsed: Parsed; + opts?: SchemaOptions; + } +): void { + // eslint-disable-next-line jest/valid-title + it(title, () => { + const maybeValid = schema.json(parsed, opts); + if (!maybeValid.ok) { + throw new Error("Failed to json() " + JSON.stringify(maybeValid.errors, undefined, 4)); + } + expect(maybeValid.value).toStrictEqual(raw); + }); +} diff --git a/seed/ts-sdk/grpc-proto/tests/unit/zurg/utils/itValidate.ts b/seed/ts-sdk/grpc-proto/tests/unit/zurg/utils/itValidate.ts new file mode 100644 index 00000000000..75b2c08b036 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/tests/unit/zurg/utils/itValidate.ts @@ -0,0 +1,56 @@ +/* eslint-disable jest/no-export */ +import { Schema, SchemaOptions, ValidationError } from "../../../../src/core/schemas/Schema"; + +export function itValidate( + title: string, + schema: Schema, + input: unknown, + errors: ValidationError[], + opts?: SchemaOptions +): void { + // eslint-disable-next-line jest/valid-title + describe("parse()", () => { + itValidateParse(title, schema, input, errors, opts); + }); + describe("json()", () => { + itValidateJson(title, schema, input, errors, opts); + }); +} + +export function itValidateParse( + title: string, + schema: Schema, + raw: unknown, + errors: ValidationError[], + opts?: SchemaOptions +): void { + describe("parse", () => { + // eslint-disable-next-line jest/valid-title + it(title, async () => { + const maybeValid = await schema.parse(raw, opts); + if (maybeValid.ok) { + throw new Error("Value passed validation"); + } + expect(maybeValid.errors).toStrictEqual(errors); + }); + }); +} + +export function itValidateJson( + title: string, + schema: Schema, + parsed: unknown, + errors: ValidationError[], + opts?: SchemaOptions +): void { + describe("json", () => { + // eslint-disable-next-line jest/valid-title + it(title, async () => { + const maybeValid = await schema.json(parsed, opts); + if (maybeValid.ok) { + throw new Error("Value passed validation"); + } + expect(maybeValid.errors).toStrictEqual(errors); + }); + }); +} diff --git a/seed/ts-sdk/grpc-proto/tsconfig.json b/seed/ts-sdk/grpc-proto/tsconfig.json new file mode 100644 index 00000000000..538c94fe015 --- /dev/null +++ b/seed/ts-sdk/grpc-proto/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "extendedDiagnostics": true, + "strict": true, + "target": "ES6", + "module": "CommonJS", + "moduleResolution": "node", + "esModuleInterop": true, + "skipLibCheck": true, + "declaration": true, + "outDir": "dist", + "rootDir": "src", + "baseUrl": "src" + }, + "include": ["src"], + "exclude": [] +} diff --git a/seed/ts-sdk/grpc/.github/workflows/ci.yml b/seed/ts-sdk/grpc/.github/workflows/ci.yml new file mode 100644 index 00000000000..b64a6cbbb4a --- /dev/null +++ b/seed/ts-sdk/grpc/.github/workflows/ci.yml @@ -0,0 +1,57 @@ +name: ci + +on: [push] + +jobs: + compile: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up node + uses: actions/setup-node@v3 + + - name: Compile + run: yarn && yarn build + + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up node + uses: actions/setup-node@v3 + + - name: Compile + run: yarn && yarn test + + publish: + needs: [ compile, test ] + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v3 + - name: Set up node + uses: actions/setup-node@v3 + - name: Install dependencies + run: yarn install + - name: Build + run: yarn build + + - name: Publish to npm + run: | + npm config set //registry.npmjs.org/:_authToken ${NPM_TOKEN} + if [[ ${GITHUB_REF} == *alpha* ]]; then + npm publish --access public --tag alpha + elif [[ ${GITHUB_REF} == *beta* ]]; then + npm publish --access public --tag beta + else + npm publish --access public + fi + env: + NPM_TOKEN: ${{ secrets. }} \ No newline at end of file diff --git a/seed/ts-sdk/grpc/.gitignore b/seed/ts-sdk/grpc/.gitignore new file mode 100644 index 00000000000..72271e049c0 --- /dev/null +++ b/seed/ts-sdk/grpc/.gitignore @@ -0,0 +1,3 @@ +node_modules +.DS_Store +/dist \ No newline at end of file diff --git a/seed/ts-sdk/grpc/.mock/definition/api.yml b/seed/ts-sdk/grpc/.mock/definition/api.yml new file mode 100644 index 00000000000..15a9f823724 --- /dev/null +++ b/seed/ts-sdk/grpc/.mock/definition/api.yml @@ -0,0 +1,4 @@ +name: api + +error-discrimination: + strategy: status-code \ No newline at end of file diff --git a/seed/ts-sdk/grpc/.mock/definition/user.yml b/seed/ts-sdk/grpc/.mock/definition/user.yml new file mode 100644 index 00000000000..80223300afc --- /dev/null +++ b/seed/ts-sdk/grpc/.mock/definition/user.yml @@ -0,0 +1,61 @@ +types: + Metadata: + type: map> + encoding: + proto: + type: google.protobuf.Struct + + MetadataValue: + discriminated: false + union: + - double + - string + - boolean + - list + encoding: + proto: + type: google.protobuf.Value + + User: + properties: + id: string + username: string + email: optional + age: optional + weight: optional + metadata: optional + + CreateUserResponse: + properties: + user: User + +service: + auth: false + base-path: / + transport: + grpc: + service-name: UserService + endpoints: + createUser: + method: POST + path: /users + request: + name: CreateUserRequest + body: + properties: + username: string + email: optional + age: optional + weight: optional + response: CreateUserResponse + + getUser: + method: GET + path: /users + request: + name: GetUserRequest + query-parameters: + username: optional + age: optional + weight: optional + response: User diff --git a/seed/ts-sdk/grpc/.mock/fern.config.json b/seed/ts-sdk/grpc/.mock/fern.config.json new file mode 100644 index 00000000000..4c8e54ac313 --- /dev/null +++ b/seed/ts-sdk/grpc/.mock/fern.config.json @@ -0,0 +1 @@ +{"organization": "fern-test", "version": "*"} \ No newline at end of file diff --git a/seed/ts-sdk/grpc/.mock/generators.yml b/seed/ts-sdk/grpc/.mock/generators.yml new file mode 100644 index 00000000000..f62dfba5843 --- /dev/null +++ b/seed/ts-sdk/grpc/.mock/generators.yml @@ -0,0 +1,4 @@ +api: + - proto: + root: proto + target: proto/user/v1/user.proto \ No newline at end of file diff --git a/seed/ts-sdk/grpc/.npmignore b/seed/ts-sdk/grpc/.npmignore new file mode 100644 index 00000000000..6db0876c41c --- /dev/null +++ b/seed/ts-sdk/grpc/.npmignore @@ -0,0 +1,9 @@ +node_modules +src +tests +.gitignore +.github +.fernignore +.prettierrc.yml +tsconfig.json +yarn.lock \ No newline at end of file diff --git a/seed/ts-sdk/grpc/.prettierrc.yml b/seed/ts-sdk/grpc/.prettierrc.yml new file mode 100644 index 00000000000..0c06786bf53 --- /dev/null +++ b/seed/ts-sdk/grpc/.prettierrc.yml @@ -0,0 +1,2 @@ +tabWidth: 4 +printWidth: 120 diff --git a/seed/ts-sdk/grpc/README.md b/seed/ts-sdk/grpc/README.md new file mode 100644 index 00000000000..6490bc695c0 --- /dev/null +++ b/seed/ts-sdk/grpc/README.md @@ -0,0 +1,140 @@ +# Seed TypeScript Library + +[![fern shield](https://img.shields.io/badge/%F0%9F%8C%BF-SDK%20generated%20by%20Fern-brightgreen)](https://github.com/fern-api/fern) +[![npm shield](https://img.shields.io/npm/v/@fern/grpc)](https://www.npmjs.com/package/@fern/grpc) + +The Seed TypeScript library provides convenient access to the Seed API from TypeScript. + +## Installation + +```sh +npm i -s @fern/grpc +``` + +## Usage + +Instantiate and use the client with the following: + +```typescript +import { SeedApiClient } from "@fern/grpc"; + +const client = new SeedApiClient({ environment: "YOUR_BASE_URL" }); +await client.user.createUser({ + username: "string", + email: "string", + age: 1, + weight: 1.1, +}); +``` + +## Request And Response Types + +The SDK exports all request and response types as TypeScript interfaces. Simply import them with the +following namespace: + +```typescript +import { SeedApi } from "@fern/grpc"; + +const request: SeedApi.CreateUserRequest = { + ... +}; +``` + +## Exception Handling + +When the API returns a non-success status code (4xx or 5xx response), a subclass of the following error +will be thrown. + +```typescript +import { SeedApiError } from "@fern/grpc"; + +try { + await client.user.createUser(...); +} catch (err) { + if (err instanceof SeedApiError) { + console.log(err.statusCode); + console.log(err.message); + console.log(err.body); + } +} +``` + +## Advanced + +### Retries + +The SDK is instrumented with automatic retries with exponential backoff. A request will be retried as long +as the request is deemed retriable and the number of retry attempts has not grown larger than the configured +retry limit (default: 2). + +A request is deemed retriable when any of the following HTTP status codes is returned: + +- [408](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) (Timeout) +- [429](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) (Too Many Requests) +- [5XX](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) (Internal Server Errors) + +Use the `maxRetries` request option to configure this behavior. + +```typescript +const response = await client.user.createUser(..., { + maxRetries: 0 // override maxRetries at the request level +}); +``` + +### Timeouts + +The SDK defaults to a 60 second timeout. Use the `timeoutInSeconds` option to configure this behavior. + +```typescript +const response = await client.user.createUser(..., { + timeoutInSeconds: 30 // override timeout to 30s +}); +``` + +### Aborting Requests + +The SDK allows users to abort requests at any point by passing in an abort signal. + +```typescript +const controller = new AbortController(); +const response = await client.user.createUser(..., { + abortSignal: controller.signal +}); +controller.abort(); // aborts the request +``` + +### Runtime Compatibility + +The SDK defaults to `node-fetch` but will use the global fetch client if present. The SDK works in the following +runtimes: + +- Node.js 18+ +- Vercel +- Cloudflare Workers +- Deno v1.25+ +- Bun 1.0+ +- React Native + +### Customizing Fetch Client + +The SDK provides a way for your to customize the underlying HTTP client / Fetch function. If you're running in an +unsupported environment, this provides a way for you to break glass and ensure the SDK works. + +```typescript +import { SeedApiClient } from "@fern/grpc"; + +const client = new SeedApiClient({ + ... + fetcher: // provide your implementation here +}); +``` + +## Contributing + +While we value open-source contributions to this SDK, this library is generated programmatically. +Additions made directly to this library would have to be moved over to our generation code, +otherwise they would be overwritten upon the next generated release. Feel free to open a PR as +a proof of concept, but know that we will not be able to merge it as-is. We suggest opening +an issue first to discuss with us! + +On the other hand, contributions to the README are always very welcome! diff --git a/seed/ts-sdk/grpc/jest.config.js b/seed/ts-sdk/grpc/jest.config.js new file mode 100644 index 00000000000..35d6e65bf93 --- /dev/null +++ b/seed/ts-sdk/grpc/jest.config.js @@ -0,0 +1,5 @@ +/** @type {import('jest').Config} */ +module.exports = { + preset: "ts-jest", + testEnvironment: "node", +}; diff --git a/seed/ts-sdk/grpc/package.json b/seed/ts-sdk/grpc/package.json new file mode 100644 index 00000000000..510a683a125 --- /dev/null +++ b/seed/ts-sdk/grpc/package.json @@ -0,0 +1,39 @@ +{ + "name": "@fern/grpc", + "version": "0.0.1", + "private": false, + "repository": "https://github.com/grpc/fern", + "main": "./index.js", + "types": "./index.d.ts", + "scripts": { + "format": "prettier . --write --ignore-unknown", + "build": "tsc", + "prepack": "cp -rv dist/. .", + "test": "jest" + }, + "dependencies": { + "url-join": "4.0.1", + "form-data": "^4.0.0", + "formdata-node": "^6.0.3", + "node-fetch": "2.7.0", + "qs": "6.11.2" + }, + "devDependencies": { + "@types/url-join": "4.0.1", + "@types/qs": "6.9.8", + "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", + "jest": "29.7.0", + "@types/jest": "29.5.5", + "ts-jest": "29.1.1", + "jest-environment-jsdom": "29.7.0", + "@types/node": "17.0.33", + "prettier": "2.7.1", + "typescript": "4.6.4" + }, + "browser": { + "fs": false, + "os": false, + "path": false + } +} diff --git a/seed/ts-sdk/grpc/reference.md b/seed/ts-sdk/grpc/reference.md new file mode 100644 index 00000000000..fdd5976aaca --- /dev/null +++ b/seed/ts-sdk/grpc/reference.md @@ -0,0 +1,108 @@ +# Reference + +## User + +

client.user.createUser({ ...params }) -> SeedApi.CreateUserResponse +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```typescript +await client.user.createUser({ + username: "string", + email: "string", + age: 1, + weight: 1.1, +}); +``` + +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request:** `SeedApi.CreateUserRequest` + +
+
+ +
+
+ +**requestOptions:** `User.RequestOptions` + +
+
+
+
+ +
+
+
+ +
client.user.getUser({ ...params }) -> SeedApi.User +
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```typescript +await client.user.getUser({ + username: "string", + age: 1, + weight: 1.1, +}); +``` + +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**request:** `SeedApi.GetUserRequest` + +
+
+ +
+
+ +**requestOptions:** `User.RequestOptions` + +
+
+
+
+ +
+
+
diff --git a/seed/ts-sdk/grpc/snippet-templates.json b/seed/ts-sdk/grpc/snippet-templates.json new file mode 100644 index 00000000000..fcc7f380fc0 --- /dev/null +++ b/seed/ts-sdk/grpc/snippet-templates.json @@ -0,0 +1,250 @@ +[ + { + "sdk": { + "package": "@fern/grpc", + "version": "0.0.1", + "type": "typescript" + }, + "endpointId": { + "path": "/users", + "method": "POST", + "identifierOverride": "endpoint_user.createUser" + }, + "snippetTemplate": { + "clientInstantiation": { + "imports": [ + "import { SeedApiClient } from \"@fern/grpc\";" + ], + "templateString": "const client = new SeedApiClient($FERN_INPUT);", + "isOptional": false, + "inputDelimiter": ",", + "templateInputs": [ + { + "value": { + "imports": [], + "templateString": "{ $FERN_INPUT }", + "isOptional": true, + "templateInputs": [ + { + "value": { + "imports": [], + "templateString": "environment: \"YOUR_BASE_URL\"", + "isOptional": false, + "templateInputs": [], + "type": "generic" + }, + "type": "template" + } + ], + "type": "generic" + }, + "type": "template" + } + ], + "type": "generic" + }, + "functionInvocation": { + "imports": [], + "templateString": "await client.user.createUser(\n\t$FERN_INPUT\n)", + "isOptional": false, + "inputDelimiter": ",\n\t", + "templateInputs": [ + { + "value": { + "imports": [], + "templateString": "{\n\t\t$FERN_INPUT\n\t}", + "isOptional": true, + "inputDelimiter": ",\n\t\t", + "templateInputs": [ + { + "value": { + "imports": [], + "templateString": "username: $FERN_INPUT", + "isOptional": true, + "templateInputs": [ + { + "location": "BODY", + "path": "username", + "type": "payload" + } + ], + "type": "generic" + }, + "type": "template" + }, + { + "value": { + "imports": [], + "templateString": "email: $FERN_INPUT", + "isOptional": true, + "templateInputs": [ + { + "location": "BODY", + "path": "email", + "type": "payload" + } + ], + "type": "generic" + }, + "type": "template" + }, + { + "value": { + "imports": [], + "templateString": "age: $FERN_INPUT", + "isOptional": true, + "templateInputs": [ + { + "location": "BODY", + "path": "age", + "type": "payload" + } + ], + "type": "generic" + }, + "type": "template" + }, + { + "value": { + "imports": [], + "templateString": "weight: $FERN_INPUT", + "isOptional": true, + "templateInputs": [ + { + "location": "BODY", + "path": "weight", + "type": "payload" + } + ], + "type": "generic" + }, + "type": "template" + } + ], + "type": "generic" + }, + "type": "template" + } + ], + "type": "generic" + }, + "type": "v1" + } + }, + { + "sdk": { + "package": "@fern/grpc", + "version": "0.0.1", + "type": "typescript" + }, + "endpointId": { + "path": "/users", + "method": "GET", + "identifierOverride": "endpoint_user.getUser" + }, + "snippetTemplate": { + "clientInstantiation": { + "imports": [ + "import { SeedApiClient } from \"@fern/grpc\";" + ], + "templateString": "const client = new SeedApiClient($FERN_INPUT);", + "isOptional": false, + "inputDelimiter": ",", + "templateInputs": [ + { + "value": { + "imports": [], + "templateString": "{ $FERN_INPUT }", + "isOptional": true, + "templateInputs": [ + { + "value": { + "imports": [], + "templateString": "environment: \"YOUR_BASE_URL\"", + "isOptional": false, + "templateInputs": [], + "type": "generic" + }, + "type": "template" + } + ], + "type": "generic" + }, + "type": "template" + } + ], + "type": "generic" + }, + "functionInvocation": { + "imports": [], + "templateString": "await client.user.getUser(\n\t$FERN_INPUT\n)", + "isOptional": false, + "inputDelimiter": ",\n\t", + "templateInputs": [ + { + "value": { + "imports": [], + "templateString": "{\n\t\t$FERN_INPUT\n\t}", + "isOptional": true, + "inputDelimiter": ",\n\t\t", + "templateInputs": [ + { + "value": { + "imports": [], + "templateString": "username: $FERN_INPUT", + "isOptional": true, + "templateInputs": [ + { + "location": "QUERY", + "path": "username", + "type": "payload" + } + ], + "type": "generic" + }, + "type": "template" + }, + { + "value": { + "imports": [], + "templateString": "age: $FERN_INPUT", + "isOptional": true, + "templateInputs": [ + { + "location": "QUERY", + "path": "age", + "type": "payload" + } + ], + "type": "generic" + }, + "type": "template" + }, + { + "value": { + "imports": [], + "templateString": "weight: $FERN_INPUT", + "isOptional": true, + "templateInputs": [ + { + "location": "QUERY", + "path": "weight", + "type": "payload" + } + ], + "type": "generic" + }, + "type": "template" + } + ], + "type": "generic" + }, + "type": "template" + } + ], + "type": "generic" + }, + "type": "v1" + } + } +] \ No newline at end of file diff --git a/seed/ts-sdk/grpc/snippet.json b/seed/ts-sdk/grpc/snippet.json new file mode 100644 index 00000000000..8b01025ae59 --- /dev/null +++ b/seed/ts-sdk/grpc/snippet.json @@ -0,0 +1,27 @@ +{ + "endpoints": [ + { + "id": { + "path": "/users", + "method": "POST", + "identifier_override": "endpoint_user.createUser" + }, + "snippet": { + "type": "typescript", + "client": "import { SeedApiClient } from \"@fern/grpc\";\n\nconst client = new SeedApiClient({ environment: \"YOUR_BASE_URL\" });\nawait client.user.createUser({\n username: \"string\",\n email: \"string\",\n age: 1,\n weight: 1.1\n});\n" + } + }, + { + "id": { + "path": "/users", + "method": "GET", + "identifier_override": "endpoint_user.getUser" + }, + "snippet": { + "type": "typescript", + "client": "import { SeedApiClient } from \"@fern/grpc\";\n\nconst client = new SeedApiClient({ environment: \"YOUR_BASE_URL\" });\nawait client.user.getUser({\n username: \"string\",\n age: 1,\n weight: 1.1\n});\n" + } + } + ], + "types": {} +} \ No newline at end of file diff --git a/seed/ts-sdk/grpc/src/Client.ts b/seed/ts-sdk/grpc/src/Client.ts new file mode 100644 index 00000000000..96d0608351d --- /dev/null +++ b/seed/ts-sdk/grpc/src/Client.ts @@ -0,0 +1,31 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as core from "./core"; +import { User } from "./api/resources/user/client/Client"; + +export declare namespace SeedApiClient { + interface Options { + environment: core.Supplier; + } + + interface RequestOptions { + /** The maximum time to wait for a response in seconds. */ + timeoutInSeconds?: number; + /** The number of times to retry the request. Defaults to 2. */ + maxRetries?: number; + /** A hook to abort the request. */ + abortSignal?: AbortSignal; + } +} + +export class SeedApiClient { + constructor(protected readonly _options: SeedApiClient.Options) {} + + protected _user: User | undefined; + + public get user(): User { + return (this._user ??= new User(this._options)); + } +} diff --git a/seed/ts-sdk/grpc/src/api/index.ts b/seed/ts-sdk/grpc/src/api/index.ts new file mode 100644 index 00000000000..3e5335fe421 --- /dev/null +++ b/seed/ts-sdk/grpc/src/api/index.ts @@ -0,0 +1 @@ +export * from "./resources"; diff --git a/seed/ts-sdk/grpc/src/api/resources/index.ts b/seed/ts-sdk/grpc/src/api/resources/index.ts new file mode 100644 index 00000000000..0671bd81e84 --- /dev/null +++ b/seed/ts-sdk/grpc/src/api/resources/index.ts @@ -0,0 +1,3 @@ +export * as user from "./user"; +export * from "./user/types"; +export * from "./user/client/requests"; diff --git a/seed/ts-sdk/grpc/src/api/resources/user/client/Client.ts b/seed/ts-sdk/grpc/src/api/resources/user/client/Client.ts new file mode 100644 index 00000000000..45c4d90cad3 --- /dev/null +++ b/seed/ts-sdk/grpc/src/api/resources/user/client/Client.ts @@ -0,0 +1,171 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as core from "../../../../core"; +import * as SeedApi from "../../../index"; +import * as serializers from "../../../../serialization/index"; +import urlJoin from "url-join"; +import * as errors from "../../../../errors/index"; + +export declare namespace User { + interface Options { + environment: core.Supplier; + } + + interface RequestOptions { + /** The maximum time to wait for a response in seconds. */ + timeoutInSeconds?: number; + /** The number of times to retry the request. Defaults to 2. */ + maxRetries?: number; + /** A hook to abort the request. */ + abortSignal?: AbortSignal; + } +} + +export class User { + constructor(protected readonly _options: User.Options) {} + + /** + * @param {SeedApi.CreateUserRequest} request + * @param {User.RequestOptions} requestOptions - Request-specific configuration. + * + * @example + * await client.user.createUser({ + * username: "string", + * email: "string", + * age: 1, + * weight: 1.1 + * }) + */ + public async createUser( + request: SeedApi.CreateUserRequest, + requestOptions?: User.RequestOptions + ): Promise { + const _response = await core.fetcher({ + url: urlJoin(await core.Supplier.get(this._options.environment), "/users"), + method: "POST", + headers: { + "X-Fern-Language": "JavaScript", + "X-Fern-SDK-Name": "@fern/grpc", + "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/grpc/0.0.1", + "X-Fern-Runtime": core.RUNTIME.type, + "X-Fern-Runtime-Version": core.RUNTIME.version, + }, + contentType: "application/json", + requestType: "json", + body: serializers.CreateUserRequest.jsonOrThrow(request, { unrecognizedObjectKeys: "strip" }), + timeoutMs: requestOptions?.timeoutInSeconds != null ? requestOptions.timeoutInSeconds * 1000 : 60000, + maxRetries: requestOptions?.maxRetries, + abortSignal: requestOptions?.abortSignal, + }); + if (_response.ok) { + return serializers.CreateUserResponse.parseOrThrow(_response.body, { + unrecognizedObjectKeys: "passthrough", + allowUnrecognizedUnionMembers: true, + allowUnrecognizedEnumValues: true, + breadcrumbsPrefix: ["response"], + }); + } + + if (_response.error.reason === "status-code") { + throw new errors.SeedApiError({ + statusCode: _response.error.statusCode, + body: _response.error.body, + }); + } + + switch (_response.error.reason) { + case "non-json": + throw new errors.SeedApiError({ + statusCode: _response.error.statusCode, + body: _response.error.rawBody, + }); + case "timeout": + throw new errors.SeedApiTimeoutError(); + case "unknown": + throw new errors.SeedApiError({ + message: _response.error.errorMessage, + }); + } + } + + /** + * @param {SeedApi.GetUserRequest} request + * @param {User.RequestOptions} requestOptions - Request-specific configuration. + * + * @example + * await client.user.getUser({ + * username: "string", + * age: 1, + * weight: 1.1 + * }) + */ + public async getUser( + request: SeedApi.GetUserRequest = {}, + requestOptions?: User.RequestOptions + ): Promise { + const { username, age, weight } = request; + const _queryParams: Record = {}; + if (username != null) { + _queryParams["username"] = username; + } + + if (age != null) { + _queryParams["age"] = age.toString(); + } + + if (weight != null) { + _queryParams["weight"] = weight.toString(); + } + + const _response = await core.fetcher({ + url: urlJoin(await core.Supplier.get(this._options.environment), "/users"), + method: "GET", + headers: { + "X-Fern-Language": "JavaScript", + "X-Fern-SDK-Name": "@fern/grpc", + "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/grpc/0.0.1", + "X-Fern-Runtime": core.RUNTIME.type, + "X-Fern-Runtime-Version": core.RUNTIME.version, + }, + contentType: "application/json", + queryParameters: _queryParams, + requestType: "json", + timeoutMs: requestOptions?.timeoutInSeconds != null ? requestOptions.timeoutInSeconds * 1000 : 60000, + maxRetries: requestOptions?.maxRetries, + abortSignal: requestOptions?.abortSignal, + }); + if (_response.ok) { + return serializers.User.parseOrThrow(_response.body, { + unrecognizedObjectKeys: "passthrough", + allowUnrecognizedUnionMembers: true, + allowUnrecognizedEnumValues: true, + breadcrumbsPrefix: ["response"], + }); + } + + if (_response.error.reason === "status-code") { + throw new errors.SeedApiError({ + statusCode: _response.error.statusCode, + body: _response.error.body, + }); + } + + switch (_response.error.reason) { + case "non-json": + throw new errors.SeedApiError({ + statusCode: _response.error.statusCode, + body: _response.error.rawBody, + }); + case "timeout": + throw new errors.SeedApiTimeoutError(); + case "unknown": + throw new errors.SeedApiError({ + message: _response.error.errorMessage, + }); + } + } +} diff --git a/seed/ts-sdk/grpc/src/api/resources/user/client/index.ts b/seed/ts-sdk/grpc/src/api/resources/user/client/index.ts new file mode 100644 index 00000000000..415726b7fea --- /dev/null +++ b/seed/ts-sdk/grpc/src/api/resources/user/client/index.ts @@ -0,0 +1 @@ +export * from "./requests"; diff --git a/seed/ts-sdk/grpc/src/api/resources/user/client/requests/CreateUserRequest.ts b/seed/ts-sdk/grpc/src/api/resources/user/client/requests/CreateUserRequest.ts new file mode 100644 index 00000000000..eb1ccd0ef16 --- /dev/null +++ b/seed/ts-sdk/grpc/src/api/resources/user/client/requests/CreateUserRequest.ts @@ -0,0 +1,19 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +/** + * @example + * { + * username: "string", + * email: "string", + * age: 1, + * weight: 1.1 + * } + */ +export interface CreateUserRequest { + username: string; + email?: string; + age?: number; + weight?: number; +} diff --git a/seed/ts-sdk/grpc/src/api/resources/user/client/requests/GetUserRequest.ts b/seed/ts-sdk/grpc/src/api/resources/user/client/requests/GetUserRequest.ts new file mode 100644 index 00000000000..85c866015b4 --- /dev/null +++ b/seed/ts-sdk/grpc/src/api/resources/user/client/requests/GetUserRequest.ts @@ -0,0 +1,17 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +/** + * @example + * { + * username: "string", + * age: 1, + * weight: 1.1 + * } + */ +export interface GetUserRequest { + username?: string; + age?: number; + weight?: number; +} diff --git a/seed/ts-sdk/grpc/src/api/resources/user/client/requests/index.ts b/seed/ts-sdk/grpc/src/api/resources/user/client/requests/index.ts new file mode 100644 index 00000000000..e61af973b31 --- /dev/null +++ b/seed/ts-sdk/grpc/src/api/resources/user/client/requests/index.ts @@ -0,0 +1,2 @@ +export { type CreateUserRequest } from "./CreateUserRequest"; +export { type GetUserRequest } from "./GetUserRequest"; diff --git a/seed/ts-sdk/grpc/src/api/resources/user/index.ts b/seed/ts-sdk/grpc/src/api/resources/user/index.ts new file mode 100644 index 00000000000..c9240f83b48 --- /dev/null +++ b/seed/ts-sdk/grpc/src/api/resources/user/index.ts @@ -0,0 +1,2 @@ +export * from "./types"; +export * from "./client"; diff --git a/seed/ts-sdk/grpc/src/api/resources/user/types/CreateUserResponse.ts b/seed/ts-sdk/grpc/src/api/resources/user/types/CreateUserResponse.ts new file mode 100644 index 00000000000..3e470a184f5 --- /dev/null +++ b/seed/ts-sdk/grpc/src/api/resources/user/types/CreateUserResponse.ts @@ -0,0 +1,9 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as SeedApi from "../../../index"; + +export interface CreateUserResponse { + user: SeedApi.User; +} diff --git a/seed/ts-sdk/grpc/src/api/resources/user/types/Metadata.ts b/seed/ts-sdk/grpc/src/api/resources/user/types/Metadata.ts new file mode 100644 index 00000000000..1b5e687c6f5 --- /dev/null +++ b/seed/ts-sdk/grpc/src/api/resources/user/types/Metadata.ts @@ -0,0 +1,7 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as SeedApi from "../../../index"; + +export type Metadata = Record; diff --git a/seed/ts-sdk/grpc/src/api/resources/user/types/MetadataValue.ts b/seed/ts-sdk/grpc/src/api/resources/user/types/MetadataValue.ts new file mode 100644 index 00000000000..bd7a484bfdc --- /dev/null +++ b/seed/ts-sdk/grpc/src/api/resources/user/types/MetadataValue.ts @@ -0,0 +1,7 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as SeedApi from "../../../index"; + +export type MetadataValue = number | string | boolean | SeedApi.MetadataValue[]; diff --git a/seed/ts-sdk/grpc/src/api/resources/user/types/User.ts b/seed/ts-sdk/grpc/src/api/resources/user/types/User.ts new file mode 100644 index 00000000000..0b9f9850fe2 --- /dev/null +++ b/seed/ts-sdk/grpc/src/api/resources/user/types/User.ts @@ -0,0 +1,14 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as SeedApi from "../../../index"; + +export interface User { + id: string; + username: string; + email?: string; + age?: number; + weight?: number; + metadata?: SeedApi.Metadata; +} diff --git a/seed/ts-sdk/grpc/src/api/resources/user/types/index.ts b/seed/ts-sdk/grpc/src/api/resources/user/types/index.ts new file mode 100644 index 00000000000..aff31139192 --- /dev/null +++ b/seed/ts-sdk/grpc/src/api/resources/user/types/index.ts @@ -0,0 +1,4 @@ +export * from "./Metadata"; +export * from "./MetadataValue"; +export * from "./User"; +export * from "./CreateUserResponse"; diff --git a/seed/ts-sdk/grpc/src/core/fetcher/APIResponse.ts b/seed/ts-sdk/grpc/src/core/fetcher/APIResponse.ts new file mode 100644 index 00000000000..3664d09e168 --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/fetcher/APIResponse.ts @@ -0,0 +1,12 @@ +export type APIResponse = SuccessfulResponse | FailedResponse; + +export interface SuccessfulResponse { + ok: true; + body: T; + headers?: Record; +} + +export interface FailedResponse { + ok: false; + error: T; +} diff --git a/seed/ts-sdk/grpc/src/core/fetcher/Fetcher.ts b/seed/ts-sdk/grpc/src/core/fetcher/Fetcher.ts new file mode 100644 index 00000000000..d67bc042107 --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/fetcher/Fetcher.ts @@ -0,0 +1,143 @@ +import { APIResponse } from "./APIResponse"; +import { createRequestUrl } from "./createRequestUrl"; +import { getFetchFn } from "./getFetchFn"; +import { getRequestBody } from "./getRequestBody"; +import { getResponseBody } from "./getResponseBody"; +import { makeRequest } from "./makeRequest"; +import { requestWithRetries } from "./requestWithRetries"; + +export type FetchFunction = (args: Fetcher.Args) => Promise>; + +export declare namespace Fetcher { + export interface Args { + url: string; + method: string; + contentType?: string; + headers?: Record; + queryParameters?: Record; + body?: unknown; + timeoutMs?: number; + maxRetries?: number; + withCredentials?: boolean; + abortSignal?: AbortSignal; + requestType?: "json" | "file" | "bytes"; + responseType?: "json" | "blob" | "sse" | "streaming" | "text"; + duplex?: "half"; + } + + export type Error = FailedStatusCodeError | NonJsonError | TimeoutError | UnknownError; + + export interface FailedStatusCodeError { + reason: "status-code"; + statusCode: number; + body: unknown; + } + + export interface NonJsonError { + reason: "non-json"; + statusCode: number; + rawBody: string; + } + + export interface TimeoutError { + reason: "timeout"; + } + + export interface UnknownError { + reason: "unknown"; + errorMessage: string; + } +} + +export async function fetcherImpl(args: Fetcher.Args): Promise> { + const headers: Record = {}; + if (args.body !== undefined && args.contentType != null) { + headers["Content-Type"] = args.contentType; + } + + if (args.headers != null) { + for (const [key, value] of Object.entries(args.headers)) { + if (value != null) { + headers[key] = value; + } + } + } + + const url = createRequestUrl(args.url, args.queryParameters); + let requestBody: BodyInit | undefined = await getRequestBody({ + body: args.body, + type: args.requestType === "json" ? "json" : "other", + }); + const fetchFn = await getFetchFn(); + + try { + const response = await requestWithRetries( + async () => + makeRequest( + fetchFn, + url, + args.method, + headers, + requestBody, + args.timeoutMs, + args.abortSignal, + args.withCredentials, + args.duplex + ), + args.maxRetries + ); + let responseBody = await getResponseBody(response, args.responseType); + + if (response.status >= 200 && response.status < 400) { + return { + ok: true, + body: responseBody as R, + headers: response.headers, + }; + } else { + return { + ok: false, + error: { + reason: "status-code", + statusCode: response.status, + body: responseBody, + }, + }; + } + } catch (error) { + if (args.abortSignal != null && args.abortSignal.aborted) { + return { + ok: false, + error: { + reason: "unknown", + errorMessage: "The user aborted a request", + }, + }; + } else if (error instanceof Error && error.name === "AbortError") { + return { + ok: false, + error: { + reason: "timeout", + }, + }; + } else if (error instanceof Error) { + return { + ok: false, + error: { + reason: "unknown", + errorMessage: error.message, + }, + }; + } + + return { + ok: false, + error: { + reason: "unknown", + errorMessage: JSON.stringify(error), + }, + }; + } +} + +export const fetcher: FetchFunction = fetcherImpl; diff --git a/seed/ts-sdk/grpc/src/core/fetcher/Supplier.ts b/seed/ts-sdk/grpc/src/core/fetcher/Supplier.ts new file mode 100644 index 00000000000..867c931c02f --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/fetcher/Supplier.ts @@ -0,0 +1,11 @@ +export type Supplier = T | Promise | (() => T | Promise); + +export const Supplier = { + get: async (supplier: Supplier): Promise => { + if (typeof supplier === "function") { + return (supplier as () => T)(); + } else { + return supplier; + } + }, +}; diff --git a/seed/ts-sdk/grpc/src/core/fetcher/createRequestUrl.ts b/seed/ts-sdk/grpc/src/core/fetcher/createRequestUrl.ts new file mode 100644 index 00000000000..9288a99bb22 --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/fetcher/createRequestUrl.ts @@ -0,0 +1,10 @@ +import qs from "qs"; + +export function createRequestUrl( + baseUrl: string, + queryParameters?: Record +): string { + return Object.keys(queryParameters ?? {}).length > 0 + ? `${baseUrl}?${qs.stringify(queryParameters, { arrayFormat: "repeat" })}` + : baseUrl; +} diff --git a/seed/ts-sdk/grpc/src/core/fetcher/getFetchFn.ts b/seed/ts-sdk/grpc/src/core/fetcher/getFetchFn.ts new file mode 100644 index 00000000000..9fd9bfc42bd --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/fetcher/getFetchFn.ts @@ -0,0 +1,25 @@ +import { RUNTIME } from "../runtime"; + +/** + * Returns a fetch function based on the runtime + */ +export async function getFetchFn(): Promise { + // In Node.js 18+ environments, use native fetch + if (RUNTIME.type === "node" && RUNTIME.parsedVersion != null && RUNTIME.parsedVersion >= 18) { + return fetch; + } + + // In Node.js 18 or lower environments, the SDK always uses`node-fetch`. + if (RUNTIME.type === "node") { + return (await import("node-fetch")).default as any; + } + + // Otherwise the SDK uses global fetch if available, + // and falls back to node-fetch. + if (typeof fetch == "function") { + return fetch; + } + + // Defaults to node `node-fetch` if global fetch isn't available + return (await import("node-fetch")).default as any; +} diff --git a/seed/ts-sdk/grpc/src/core/fetcher/getHeader.ts b/seed/ts-sdk/grpc/src/core/fetcher/getHeader.ts new file mode 100644 index 00000000000..50f922b0e87 --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/fetcher/getHeader.ts @@ -0,0 +1,8 @@ +export function getHeader(headers: Record, header: string): string | undefined { + for (const [headerKey, headerValue] of Object.entries(headers)) { + if (headerKey.toLowerCase() === header.toLowerCase()) { + return headerValue; + } + } + return undefined; +} diff --git a/seed/ts-sdk/grpc/src/core/fetcher/getRequestBody.ts b/seed/ts-sdk/grpc/src/core/fetcher/getRequestBody.ts new file mode 100644 index 00000000000..1138414b1c2 --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/fetcher/getRequestBody.ts @@ -0,0 +1,14 @@ +export declare namespace GetRequestBody { + interface Args { + body: unknown; + type: "json" | "file" | "bytes" | "other"; + } +} + +export async function getRequestBody({ body, type }: GetRequestBody.Args): Promise { + if (type.includes("json")) { + return JSON.stringify(body); + } else { + return body as BodyInit; + } +} diff --git a/seed/ts-sdk/grpc/src/core/fetcher/getResponseBody.ts b/seed/ts-sdk/grpc/src/core/fetcher/getResponseBody.ts new file mode 100644 index 00000000000..a7a9c508777 --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/fetcher/getResponseBody.ts @@ -0,0 +1,32 @@ +import { chooseStreamWrapper } from "./stream-wrappers/chooseStreamWrapper"; + +export async function getResponseBody(response: Response, responseType?: string): Promise { + if (response.body != null && responseType === "blob") { + return await response.blob(); + } else if (response.body != null && responseType === "sse") { + return response.body; + } else if (response.body != null && responseType === "streaming") { + return chooseStreamWrapper(response.body); + } else if (response.body != null && responseType === "text") { + return await response.text(); + } else { + const text = await response.text(); + if (text.length > 0) { + try { + let responseBody = JSON.parse(text); + return responseBody; + } catch (err) { + return { + ok: false, + error: { + reason: "non-json", + statusCode: response.status, + rawBody: text, + }, + }; + } + } else { + return undefined; + } + } +} diff --git a/seed/ts-sdk/grpc/src/core/fetcher/index.ts b/seed/ts-sdk/grpc/src/core/fetcher/index.ts new file mode 100644 index 00000000000..2d658ca48f9 --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/fetcher/index.ts @@ -0,0 +1,5 @@ +export type { APIResponse } from "./APIResponse"; +export { fetcher } from "./Fetcher"; +export type { Fetcher, FetchFunction } from "./Fetcher"; +export { getHeader } from "./getHeader"; +export { Supplier } from "./Supplier"; diff --git a/seed/ts-sdk/grpc/src/core/fetcher/makeRequest.ts b/seed/ts-sdk/grpc/src/core/fetcher/makeRequest.ts new file mode 100644 index 00000000000..8fb4bace466 --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/fetcher/makeRequest.ts @@ -0,0 +1,44 @@ +import { anySignal, getTimeoutSignal } from "./signals"; + +export const makeRequest = async ( + fetchFn: (url: string, init: RequestInit) => Promise, + url: string, + method: string, + headers: Record, + requestBody: BodyInit | undefined, + timeoutMs?: number, + abortSignal?: AbortSignal, + withCredentials?: boolean, + duplex?: "half" +): Promise => { + const signals: AbortSignal[] = []; + + // Add timeout signal + let timeoutAbortId: NodeJS.Timeout | undefined = undefined; + if (timeoutMs != null) { + const { signal, abortId } = getTimeoutSignal(timeoutMs); + timeoutAbortId = abortId; + signals.push(signal); + } + + // Add arbitrary signal + if (abortSignal != null) { + signals.push(abortSignal); + } + let newSignals = anySignal(signals); + const response = await fetchFn(url, { + method: method, + headers, + body: requestBody, + signal: newSignals, + credentials: withCredentials ? "include" : undefined, + // @ts-ignore + duplex, + }); + + if (timeoutAbortId != null) { + clearTimeout(timeoutAbortId); + } + + return response; +}; diff --git a/seed/ts-sdk/grpc/src/core/fetcher/requestWithRetries.ts b/seed/ts-sdk/grpc/src/core/fetcher/requestWithRetries.ts new file mode 100644 index 00000000000..ff5dc3bbabc --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/fetcher/requestWithRetries.ts @@ -0,0 +1,21 @@ +const INITIAL_RETRY_DELAY = 1; +const MAX_RETRY_DELAY = 60; +const DEFAULT_MAX_RETRIES = 2; + +export async function requestWithRetries( + requestFn: () => Promise, + maxRetries: number = DEFAULT_MAX_RETRIES +): Promise { + let response: Response = await requestFn(); + + for (let i = 0; i < maxRetries; ++i) { + if ([408, 409, 429].includes(response.status) || response.status >= 500) { + const delay = Math.min(INITIAL_RETRY_DELAY * Math.pow(2, i), MAX_RETRY_DELAY); + await new Promise((resolve) => setTimeout(resolve, delay)); + response = await requestFn(); + } else { + break; + } + } + return response!; +} diff --git a/seed/ts-sdk/grpc/src/core/fetcher/signals.ts b/seed/ts-sdk/grpc/src/core/fetcher/signals.ts new file mode 100644 index 00000000000..6c124ff7985 --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/fetcher/signals.ts @@ -0,0 +1,38 @@ +const TIMEOUT = "timeout"; + +export function getTimeoutSignal(timeoutMs: number): { signal: AbortSignal; abortId: NodeJS.Timeout } { + const controller = new AbortController(); + const abortId = setTimeout(() => controller.abort(TIMEOUT), timeoutMs); + return { signal: controller.signal, abortId }; +} + +/** + * Returns an abort signal that is getting aborted when + * at least one of the specified abort signals is aborted. + * + * Requires at least node.js 18. + */ +export function anySignal(...args: AbortSignal[] | [AbortSignal[]]): AbortSignal { + // Allowing signals to be passed either as array + // of signals or as multiple arguments. + const signals = (args.length === 1 && Array.isArray(args[0]) ? args[0] : args); + + const controller = new AbortController(); + + for (const signal of signals) { + if (signal.aborted) { + // Exiting early if one of the signals + // is already aborted. + controller.abort((signal as any)?.reason); + break; + } + + // Listening for signals and removing the listeners + // when at least one symbol is aborted. + signal.addEventListener("abort", () => controller.abort((signal as any)?.reason), { + signal: controller.signal, + }); + } + + return controller.signal; +} diff --git a/seed/ts-sdk/grpc/src/core/fetcher/stream-wrappers/Node18UniversalStreamWrapper.ts b/seed/ts-sdk/grpc/src/core/fetcher/stream-wrappers/Node18UniversalStreamWrapper.ts new file mode 100644 index 00000000000..e5db8734c83 --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/fetcher/stream-wrappers/Node18UniversalStreamWrapper.ts @@ -0,0 +1,252 @@ +import type { Writable } from "stream"; +import { EventCallback, StreamWrapper } from "./chooseStreamWrapper"; + +export class Node18UniversalStreamWrapper + implements + StreamWrapper | Writable | WritableStream, ReadFormat> +{ + private readableStream: ReadableStream; + private reader: ReadableStreamDefaultReader; + private events: Record; + private paused: boolean; + private resumeCallback: ((value?: unknown) => void) | null; + private encoding: string | null; + + constructor(readableStream: ReadableStream) { + this.readableStream = readableStream; + this.reader = this.readableStream.getReader(); + this.events = { + data: [], + end: [], + error: [], + readable: [], + close: [], + pause: [], + resume: [], + }; + this.paused = false; + this.resumeCallback = null; + this.encoding = null; + } + + public on(event: string, callback: EventCallback): void { + this.events[event]?.push(callback); + } + + public off(event: string, callback: EventCallback): void { + this.events[event] = this.events[event]?.filter((cb) => cb !== callback); + } + + public pipe( + dest: Node18UniversalStreamWrapper | Writable | WritableStream + ): Node18UniversalStreamWrapper | Writable | WritableStream { + this.on("data", async (chunk) => { + if (dest instanceof Node18UniversalStreamWrapper) { + dest._write(chunk); + } else if (dest instanceof WritableStream) { + const writer = dest.getWriter(); + writer.write(chunk).then(() => writer.releaseLock()); + } else { + dest.write(chunk); + } + }); + + this.on("end", async () => { + if (dest instanceof Node18UniversalStreamWrapper) { + dest._end(); + } else if (dest instanceof WritableStream) { + const writer = dest.getWriter(); + writer.close(); + } else { + dest.end(); + } + }); + + this.on("error", async (error) => { + if (dest instanceof Node18UniversalStreamWrapper) { + dest._error(error); + } else if (dest instanceof WritableStream) { + const writer = dest.getWriter(); + writer.abort(error); + } else { + dest.destroy(error); + } + }); + + this._startReading(); + + return dest; + } + + public pipeTo( + dest: Node18UniversalStreamWrapper | Writable | WritableStream + ): Node18UniversalStreamWrapper | Writable | WritableStream { + return this.pipe(dest); + } + + public unpipe(dest: Node18UniversalStreamWrapper | Writable | WritableStream): void { + this.off("data", async (chunk) => { + if (dest instanceof Node18UniversalStreamWrapper) { + dest._write(chunk); + } else if (dest instanceof WritableStream) { + const writer = dest.getWriter(); + writer.write(chunk).then(() => writer.releaseLock()); + } else { + dest.write(chunk); + } + }); + + this.off("end", async () => { + if (dest instanceof Node18UniversalStreamWrapper) { + dest._end(); + } else if (dest instanceof WritableStream) { + const writer = dest.getWriter(); + writer.close(); + } else { + dest.end(); + } + }); + + this.off("error", async (error) => { + if (dest instanceof Node18UniversalStreamWrapper) { + dest._error(error); + } else if (dest instanceof WritableStream) { + const writer = dest.getWriter(); + writer.abort(error); + } else { + dest.destroy(error); + } + }); + } + + public destroy(error?: Error): void { + this.reader + .cancel(error) + .then(() => { + this._emit("close"); + }) + .catch((err) => { + this._emit("error", err); + }); + } + + public pause(): void { + this.paused = true; + this._emit("pause"); + } + + public resume(): void { + if (this.paused) { + this.paused = false; + this._emit("resume"); + if (this.resumeCallback) { + this.resumeCallback(); + this.resumeCallback = null; + } + } + } + + public get isPaused(): boolean { + return this.paused; + } + + public async read(): Promise { + if (this.paused) { + await new Promise((resolve) => { + this.resumeCallback = resolve; + }); + } + const { done, value } = await this.reader.read(); + + if (done) { + return undefined; + } + return value; + } + + public setEncoding(encoding: string): void { + this.encoding = encoding; + } + + public async text(): Promise { + const chunks: ReadFormat[] = []; + + while (true) { + const { done, value } = await this.reader.read(); + if (done) break; + if (value) chunks.push(value); + } + + const decoder = new TextDecoder(this.encoding || "utf-8"); + return decoder.decode(await new Blob(chunks).arrayBuffer()); + } + + public async json(): Promise { + const text = await this.text(); + return JSON.parse(text); + } + + private _write(chunk: ReadFormat): void { + this._emit("data", chunk); + } + + private _end(): void { + this._emit("end"); + } + + private _error(error: any): void { + this._emit("error", error); + } + + private _emit(event: string, data?: any): void { + if (this.events[event]) { + for (const callback of this.events[event] || []) { + callback(data); + } + } + } + + private async _startReading(): Promise { + try { + this._emit("readable"); + while (true) { + if (this.paused) { + await new Promise((resolve) => { + this.resumeCallback = resolve; + }); + } + const { done, value } = await this.reader.read(); + if (done) { + this._emit("end"); + this._emit("close"); + break; + } + if (value) { + this._emit("data", value); + } + } + } catch (error) { + this._emit("error", error); + } + } + + [Symbol.asyncIterator](): AsyncIterableIterator { + return { + next: async () => { + if (this.paused) { + await new Promise((resolve) => { + this.resumeCallback = resolve; + }); + } + const { done, value } = await this.reader.read(); + if (done) { + return { done: true, value: undefined }; + } + return { done: false, value }; + }, + [Symbol.asyncIterator]() { + return this; + }, + }; + } +} diff --git a/seed/ts-sdk/grpc/src/core/fetcher/stream-wrappers/NodePre18StreamWrapper.ts b/seed/ts-sdk/grpc/src/core/fetcher/stream-wrappers/NodePre18StreamWrapper.ts new file mode 100644 index 00000000000..f9bead21841 --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/fetcher/stream-wrappers/NodePre18StreamWrapper.ts @@ -0,0 +1,106 @@ +import type { Readable, Writable } from "stream"; +import { EventCallback, StreamWrapper } from "./chooseStreamWrapper"; + +export class NodePre18StreamWrapper implements StreamWrapper { + private readableStream: Readable; + private encoding: string | undefined; + + constructor(readableStream: Readable) { + this.readableStream = readableStream; + } + + public on(event: string, callback: EventCallback): void { + this.readableStream.on(event, callback); + } + + public off(event: string, callback: EventCallback): void { + this.readableStream.off(event, callback); + } + + public pipe(dest: Writable): Writable { + this.readableStream.pipe(dest); + return dest; + } + + public pipeTo(dest: Writable): Writable { + return this.pipe(dest); + } + + public unpipe(dest?: Writable): void { + if (dest) { + this.readableStream.unpipe(dest); + } else { + this.readableStream.unpipe(); + } + } + + public destroy(error?: Error): void { + this.readableStream.destroy(error); + } + + public pause(): void { + this.readableStream.pause(); + } + + public resume(): void { + this.readableStream.resume(); + } + + public get isPaused(): boolean { + return this.readableStream.isPaused(); + } + + public async read(): Promise { + return new Promise((resolve, reject) => { + const chunk = this.readableStream.read(); + if (chunk) { + resolve(chunk); + } else { + this.readableStream.once("readable", () => { + const chunk = this.readableStream.read(); + resolve(chunk); + }); + this.readableStream.once("error", reject); + } + }); + } + + public setEncoding(encoding?: string): void { + this.readableStream.setEncoding(encoding as BufferEncoding); + this.encoding = encoding; + } + + public async text(): Promise { + const chunks: Uint8Array[] = []; + const encoder = new TextEncoder(); + this.readableStream.setEncoding((this.encoding || "utf-8") as BufferEncoding); + + for await (const chunk of this.readableStream) { + chunks.push(encoder.encode(chunk)); + } + + const decoder = new TextDecoder(this.encoding || "utf-8"); + return decoder.decode(Buffer.concat(chunks)); + } + + public async json(): Promise { + const text = await this.text(); + return JSON.parse(text); + } + + public [Symbol.asyncIterator](): AsyncIterableIterator { + const readableStream = this.readableStream; + const iterator = readableStream[Symbol.asyncIterator](); + + // Create and return an async iterator that yields buffers + return { + async next(): Promise> { + const { value, done } = await iterator.next(); + return { value: value as Buffer, done }; + }, + [Symbol.asyncIterator]() { + return this; + }, + }; + } +} diff --git a/seed/ts-sdk/grpc/src/core/fetcher/stream-wrappers/UndiciStreamWrapper.ts b/seed/ts-sdk/grpc/src/core/fetcher/stream-wrappers/UndiciStreamWrapper.ts new file mode 100644 index 00000000000..7a52805de80 --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/fetcher/stream-wrappers/UndiciStreamWrapper.ts @@ -0,0 +1,239 @@ +import { StreamWrapper } from "./chooseStreamWrapper"; + +type EventCallback = (data?: any) => void; + +export class UndiciStreamWrapper + implements StreamWrapper | WritableStream, ReadFormat> +{ + private readableStream: ReadableStream; + private reader: ReadableStreamDefaultReader; + private events: Record; + private paused: boolean; + private resumeCallback: ((value?: unknown) => void) | null; + private encoding: string | null; + + constructor(readableStream: ReadableStream) { + this.readableStream = readableStream; + this.reader = this.readableStream.getReader(); + this.events = { + data: [], + end: [], + error: [], + readable: [], + close: [], + pause: [], + resume: [], + }; + this.paused = false; + this.resumeCallback = null; + this.encoding = null; + } + + public on(event: string, callback: EventCallback): void { + this.events[event]?.push(callback); + } + + public off(event: string, callback: EventCallback): void { + this.events[event] = this.events[event]?.filter((cb) => cb !== callback); + } + + public pipe( + dest: UndiciStreamWrapper | WritableStream + ): UndiciStreamWrapper | WritableStream { + this.on("data", (chunk) => { + if (dest instanceof UndiciStreamWrapper) { + dest._write(chunk); + } else { + const writer = dest.getWriter(); + writer.write(chunk).then(() => writer.releaseLock()); + } + }); + + this.on("end", () => { + if (dest instanceof UndiciStreamWrapper) { + dest._end(); + } else { + const writer = dest.getWriter(); + writer.close(); + } + }); + + this.on("error", (error) => { + if (dest instanceof UndiciStreamWrapper) { + dest._error(error); + } else { + const writer = dest.getWriter(); + writer.abort(error); + } + }); + + this._startReading(); + + return dest; + } + + public pipeTo( + dest: UndiciStreamWrapper | WritableStream + ): UndiciStreamWrapper | WritableStream { + return this.pipe(dest); + } + + public unpipe(dest: UndiciStreamWrapper | WritableStream): void { + this.off("data", (chunk) => { + if (dest instanceof UndiciStreamWrapper) { + dest._write(chunk); + } else { + const writer = dest.getWriter(); + writer.write(chunk).then(() => writer.releaseLock()); + } + }); + + this.off("end", () => { + if (dest instanceof UndiciStreamWrapper) { + dest._end(); + } else { + const writer = dest.getWriter(); + writer.close(); + } + }); + + this.off("error", (error) => { + if (dest instanceof UndiciStreamWrapper) { + dest._error(error); + } else { + const writer = dest.getWriter(); + writer.abort(error); + } + }); + } + + public destroy(error?: Error): void { + this.reader + .cancel(error) + .then(() => { + this._emit("close"); + }) + .catch((err) => { + this._emit("error", err); + }); + } + + public pause(): void { + this.paused = true; + this._emit("pause"); + } + + public resume(): void { + if (this.paused) { + this.paused = false; + this._emit("resume"); + if (this.resumeCallback) { + this.resumeCallback(); + this.resumeCallback = null; + } + } + } + + public get isPaused(): boolean { + return this.paused; + } + + public async read(): Promise { + if (this.paused) { + await new Promise((resolve) => { + this.resumeCallback = resolve; + }); + } + const { done, value } = await this.reader.read(); + if (done) { + return undefined; + } + return value; + } + + public setEncoding(encoding: string): void { + this.encoding = encoding; + } + + public async text(): Promise { + const chunks: BlobPart[] = []; + + while (true) { + const { done, value } = await this.reader.read(); + if (done) break; + if (value) chunks.push(value); + } + + const decoder = new TextDecoder(this.encoding || "utf-8"); + return decoder.decode(await new Blob(chunks).arrayBuffer()); + } + + public async json(): Promise { + const text = await this.text(); + return JSON.parse(text); + } + + private _write(chunk: ReadFormat): void { + this._emit("data", chunk); + } + + private _end(): void { + this._emit("end"); + } + + private _error(error: any): void { + this._emit("error", error); + } + + private _emit(event: string, data?: any): void { + if (this.events[event]) { + for (const callback of this.events[event] || []) { + callback(data); + } + } + } + + private async _startReading(): Promise { + try { + this._emit("readable"); + while (true) { + if (this.paused) { + await new Promise((resolve) => { + this.resumeCallback = resolve; + }); + } + const { done, value } = await this.reader.read(); + if (done) { + this._emit("end"); + this._emit("close"); + break; + } + if (value) { + this._emit("data", value); + } + } + } catch (error) { + this._emit("error", error); + } + } + + [Symbol.asyncIterator](): AsyncIterableIterator { + return { + next: async () => { + if (this.paused) { + await new Promise((resolve) => { + this.resumeCallback = resolve; + }); + } + const { done, value } = await this.reader.read(); + if (done) { + return { done: true, value: undefined }; + } + return { done: false, value }; + }, + [Symbol.asyncIterator]() { + return this; + }, + }; + } +} diff --git a/seed/ts-sdk/grpc/src/core/fetcher/stream-wrappers/chooseStreamWrapper.ts b/seed/ts-sdk/grpc/src/core/fetcher/stream-wrappers/chooseStreamWrapper.ts new file mode 100644 index 00000000000..d60991da089 --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/fetcher/stream-wrappers/chooseStreamWrapper.ts @@ -0,0 +1,33 @@ +import type { Readable } from "stream"; +import { RUNTIME } from "../../runtime"; + +export type EventCallback = (data?: any) => void; + +export interface StreamWrapper { + setEncoding(encoding?: string): void; + on(event: string, callback: EventCallback): void; + off(event: string, callback: EventCallback): void; + pipe(dest: WritableStream): WritableStream; + pipeTo(dest: WritableStream): WritableStream; + unpipe(dest?: WritableStream): void; + destroy(error?: Error): void; + pause(): void; + resume(): void; + get isPaused(): boolean; + read(): Promise; + text(): Promise; + json(): Promise; + [Symbol.asyncIterator](): AsyncIterableIterator; +} + +export async function chooseStreamWrapper(responseBody: any): Promise>> { + if (RUNTIME.type === "node" && RUNTIME.parsedVersion != null && RUNTIME.parsedVersion >= 18) { + return new (await import("./Node18UniversalStreamWrapper")).Node18UniversalStreamWrapper( + responseBody as ReadableStream + ); + } else if (RUNTIME.type !== "node" && typeof fetch == "function") { + return new (await import("./UndiciStreamWrapper")).UndiciStreamWrapper(responseBody as ReadableStream); + } else { + return new (await import("./NodePre18StreamWrapper")).NodePre18StreamWrapper(responseBody as Readable); + } +} diff --git a/seed/ts-sdk/grpc/src/core/index.ts b/seed/ts-sdk/grpc/src/core/index.ts new file mode 100644 index 00000000000..e3006860f4d --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/index.ts @@ -0,0 +1,3 @@ +export * from "./fetcher"; +export * from "./runtime"; +export * as serialization from "./schemas"; diff --git a/seed/ts-sdk/grpc/src/core/runtime/index.ts b/seed/ts-sdk/grpc/src/core/runtime/index.ts new file mode 100644 index 00000000000..5c76dbb133f --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/runtime/index.ts @@ -0,0 +1 @@ +export { RUNTIME } from "./runtime"; diff --git a/seed/ts-sdk/grpc/src/core/runtime/runtime.ts b/seed/ts-sdk/grpc/src/core/runtime/runtime.ts new file mode 100644 index 00000000000..4d0687e8eb4 --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/runtime/runtime.ts @@ -0,0 +1,126 @@ +interface DenoGlobal { + version: { + deno: string; + }; +} + +interface BunGlobal { + version: string; +} + +declare const Deno: DenoGlobal; +declare const Bun: BunGlobal; + +/** + * A constant that indicates whether the environment the code is running is a Web Browser. + */ +const isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined"; + +/** + * A constant that indicates whether the environment the code is running is a Web Worker. + */ +const isWebWorker = + typeof self === "object" && + // @ts-ignore + typeof self?.importScripts === "function" && + (self.constructor?.name === "DedicatedWorkerGlobalScope" || + self.constructor?.name === "ServiceWorkerGlobalScope" || + self.constructor?.name === "SharedWorkerGlobalScope"); + +/** + * A constant that indicates whether the environment the code is running is Deno. + */ +const isDeno = + typeof Deno !== "undefined" && typeof Deno.version !== "undefined" && typeof Deno.version.deno !== "undefined"; + +/** + * A constant that indicates whether the environment the code is running is Bun.sh. + */ +const isBun = typeof Bun !== "undefined" && typeof Bun.version !== "undefined"; + +/** + * A constant that indicates whether the environment the code is running is Node.JS. + */ +const isNode = + typeof process !== "undefined" && + Boolean(process.version) && + Boolean(process.versions?.node) && + // Deno spoofs process.versions.node, see https://deno.land/std@0.177.0/node/process.ts?s=versions + !isDeno && + !isBun; + +/** + * A constant that indicates whether the environment the code is running is in React-Native. + * https://github.com/facebook/react-native/blob/main/packages/react-native/Libraries/Core/setUpNavigator.js + */ +const isReactNative = typeof navigator !== "undefined" && navigator?.product === "ReactNative"; + +/** + * A constant that indicates whether the environment the code is running is Cloudflare. + * https://developers.cloudflare.com/workers/runtime-apis/web-standards/#navigatoruseragent + */ +const isCloudflare = typeof globalThis !== "undefined" && globalThis?.navigator?.userAgent === "Cloudflare-Workers"; + +/** + * A constant that indicates which environment and version the SDK is running in. + */ +export const RUNTIME: Runtime = evaluateRuntime(); + +export interface Runtime { + type: "browser" | "web-worker" | "deno" | "bun" | "node" | "react-native" | "unknown" | "workerd"; + version?: string; + parsedVersion?: number; +} + +function evaluateRuntime(): Runtime { + if (isBrowser) { + return { + type: "browser", + version: window.navigator.userAgent, + }; + } + + if (isCloudflare) { + return { + type: "workerd", + }; + } + + if (isWebWorker) { + return { + type: "web-worker", + }; + } + + if (isDeno) { + return { + type: "deno", + version: Deno.version.deno, + }; + } + + if (isBun) { + return { + type: "bun", + version: Bun.version, + }; + } + + if (isNode) { + return { + type: "node", + version: process.versions.node, + parsedVersion: Number(process.versions.node.split(".")[0]), + }; + } + + if (isReactNative) { + return { + type: "react-native", + }; + } + + return { + type: "unknown", + }; +} diff --git a/seed/ts-sdk/grpc/src/core/schemas/Schema.ts b/seed/ts-sdk/grpc/src/core/schemas/Schema.ts new file mode 100644 index 00000000000..19acc5dc44b --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/Schema.ts @@ -0,0 +1,98 @@ +import { SchemaUtils } from "./builders"; + +export type Schema = BaseSchema & SchemaUtils; + +export type inferRaw = S extends Schema ? Raw : never; +export type inferParsed = S extends Schema ? Parsed : never; + +export interface BaseSchema { + parse: (raw: unknown, opts?: SchemaOptions) => MaybeValid; + json: (parsed: unknown, opts?: SchemaOptions) => MaybeValid; + getType: () => SchemaType | SchemaType; +} + +export const SchemaType = { + DATE: "date", + ENUM: "enum", + LIST: "list", + STRING_LITERAL: "stringLiteral", + BOOLEAN_LITERAL: "booleanLiteral", + OBJECT: "object", + ANY: "any", + BOOLEAN: "boolean", + NUMBER: "number", + STRING: "string", + UNKNOWN: "unknown", + RECORD: "record", + SET: "set", + UNION: "union", + UNDISCRIMINATED_UNION: "undiscriminatedUnion", + OPTIONAL: "optional", +} as const; +export type SchemaType = typeof SchemaType[keyof typeof SchemaType]; + +export type MaybeValid = Valid | Invalid; + +export interface Valid { + ok: true; + value: T; +} + +export interface Invalid { + ok: false; + errors: ValidationError[]; +} + +export interface ValidationError { + path: string[]; + message: string; +} + +export interface SchemaOptions { + /** + * how to handle unrecognized keys in objects + * + * @default "fail" + */ + unrecognizedObjectKeys?: "fail" | "passthrough" | "strip"; + + /** + * whether to fail when an unrecognized discriminant value is + * encountered in a union + * + * @default false + */ + allowUnrecognizedUnionMembers?: boolean; + + /** + * whether to fail when an unrecognized enum value is encountered + * + * @default false + */ + allowUnrecognizedEnumValues?: boolean; + + /** + * whether to allow data that doesn't conform to the schema. + * invalid data is passed through without transformation. + * + * when this is enabled, .parse() and .json() will always + * return `ok: true`. `.parseOrThrow()` and `.jsonOrThrow()` + * will never fail. + * + * @default false + */ + skipValidation?: boolean; + + /** + * each validation failure contains a "path" property, which is + * the breadcrumbs to the offending node in the JSON. you can supply + * a prefix that is prepended to all the errors' paths. this can be + * helpful for zurg's internal debug logging. + */ + breadcrumbsPrefix?: string[]; + + /** + * whether to send 'null' for optional properties explicitly set to 'undefined'. + */ + omitUndefined?: boolean; +} diff --git a/seed/ts-sdk/grpc/src/core/schemas/builders/date/date.ts b/seed/ts-sdk/grpc/src/core/schemas/builders/date/date.ts new file mode 100644 index 00000000000..b70f24b045a --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/builders/date/date.ts @@ -0,0 +1,65 @@ +import { BaseSchema, Schema, SchemaType } from "../../Schema"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { getSchemaUtils } from "../schema-utils"; + +// https://stackoverflow.com/questions/12756159/regex-and-iso8601-formatted-datetime +const ISO_8601_REGEX = + /^([+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24:?00)([.,]\d+(?!:))?)?(\17[0-5]\d([.,]\d+)?)?([zZ]|([+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/; + +export function date(): Schema { + const baseSchema: BaseSchema = { + parse: (raw, { breadcrumbsPrefix = [] } = {}) => { + if (typeof raw !== "string") { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(raw, "string"), + }, + ], + }; + } + if (!ISO_8601_REGEX.test(raw)) { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(raw, "ISO 8601 date string"), + }, + ], + }; + } + return { + ok: true, + value: new Date(raw), + }; + }, + json: (date, { breadcrumbsPrefix = [] } = {}) => { + if (date instanceof Date) { + return { + ok: true, + value: date.toISOString(), + }; + } else { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(date, "Date object"), + }, + ], + }; + } + }, + getType: () => SchemaType.DATE, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + }; +} diff --git a/seed/ts-sdk/grpc/src/core/schemas/builders/date/index.ts b/seed/ts-sdk/grpc/src/core/schemas/builders/date/index.ts new file mode 100644 index 00000000000..187b29040f6 --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/builders/date/index.ts @@ -0,0 +1 @@ +export { date } from "./date"; diff --git a/seed/ts-sdk/grpc/src/core/schemas/builders/enum/enum.ts b/seed/ts-sdk/grpc/src/core/schemas/builders/enum/enum.ts new file mode 100644 index 00000000000..c1e24d69dec --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/builders/enum/enum.ts @@ -0,0 +1,43 @@ +import { Schema, SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; + +export function enum_(values: E): Schema { + const validValues = new Set(values); + + const schemaCreator = createIdentitySchemaCreator( + SchemaType.ENUM, + (value, { allowUnrecognizedEnumValues, breadcrumbsPrefix = [] } = {}) => { + if (typeof value !== "string") { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "string"), + }, + ], + }; + } + + if (!validValues.has(value) && !allowUnrecognizedEnumValues) { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "enum"), + }, + ], + }; + } + + return { + ok: true, + value: value as U, + }; + } + ); + + return schemaCreator(); +} diff --git a/seed/ts-sdk/grpc/src/core/schemas/builders/enum/index.ts b/seed/ts-sdk/grpc/src/core/schemas/builders/enum/index.ts new file mode 100644 index 00000000000..fe6faed93e3 --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/builders/enum/index.ts @@ -0,0 +1 @@ +export { enum_ } from "./enum"; diff --git a/seed/ts-sdk/grpc/src/core/schemas/builders/index.ts b/seed/ts-sdk/grpc/src/core/schemas/builders/index.ts new file mode 100644 index 00000000000..050cd2c4efb --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/builders/index.ts @@ -0,0 +1,13 @@ +export * from "./date"; +export * from "./enum"; +export * from "./lazy"; +export * from "./list"; +export * from "./literals"; +export * from "./object"; +export * from "./object-like"; +export * from "./primitives"; +export * from "./record"; +export * from "./schema-utils"; +export * from "./set"; +export * from "./undiscriminated-union"; +export * from "./union"; diff --git a/seed/ts-sdk/grpc/src/core/schemas/builders/lazy/index.ts b/seed/ts-sdk/grpc/src/core/schemas/builders/lazy/index.ts new file mode 100644 index 00000000000..77420fb031c --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/builders/lazy/index.ts @@ -0,0 +1,3 @@ +export { lazy } from "./lazy"; +export type { SchemaGetter } from "./lazy"; +export { lazyObject } from "./lazyObject"; diff --git a/seed/ts-sdk/grpc/src/core/schemas/builders/lazy/lazy.ts b/seed/ts-sdk/grpc/src/core/schemas/builders/lazy/lazy.ts new file mode 100644 index 00000000000..835c61f8a56 --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/builders/lazy/lazy.ts @@ -0,0 +1,32 @@ +import { BaseSchema, Schema } from "../../Schema"; +import { getSchemaUtils } from "../schema-utils"; + +export type SchemaGetter> = () => SchemaType; + +export function lazy(getter: SchemaGetter>): Schema { + const baseSchema = constructLazyBaseSchema(getter); + return { + ...baseSchema, + ...getSchemaUtils(baseSchema), + }; +} + +export function constructLazyBaseSchema( + getter: SchemaGetter> +): BaseSchema { + return { + parse: (raw, opts) => getMemoizedSchema(getter).parse(raw, opts), + json: (parsed, opts) => getMemoizedSchema(getter).json(parsed, opts), + getType: () => getMemoizedSchema(getter).getType(), + }; +} + +type MemoizedGetter> = SchemaGetter & { __zurg_memoized?: SchemaType }; + +export function getMemoizedSchema>(getter: SchemaGetter): SchemaType { + const castedGetter = getter as MemoizedGetter; + if (castedGetter.__zurg_memoized == null) { + castedGetter.__zurg_memoized = getter(); + } + return castedGetter.__zurg_memoized; +} diff --git a/seed/ts-sdk/grpc/src/core/schemas/builders/lazy/lazyObject.ts b/seed/ts-sdk/grpc/src/core/schemas/builders/lazy/lazyObject.ts new file mode 100644 index 00000000000..38c9e28404b --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/builders/lazy/lazyObject.ts @@ -0,0 +1,20 @@ +import { getObjectUtils } from "../object"; +import { getObjectLikeUtils } from "../object-like"; +import { BaseObjectSchema, ObjectSchema } from "../object/types"; +import { getSchemaUtils } from "../schema-utils"; +import { constructLazyBaseSchema, getMemoizedSchema, SchemaGetter } from "./lazy"; + +export function lazyObject(getter: SchemaGetter>): ObjectSchema { + const baseSchema: BaseObjectSchema = { + ...constructLazyBaseSchema(getter), + _getRawProperties: () => getMemoizedSchema(getter)._getRawProperties(), + _getParsedProperties: () => getMemoizedSchema(getter)._getParsedProperties(), + }; + + return { + ...baseSchema, + ...getSchemaUtils(baseSchema), + ...getObjectLikeUtils(baseSchema), + ...getObjectUtils(baseSchema), + }; +} diff --git a/seed/ts-sdk/grpc/src/core/schemas/builders/list/index.ts b/seed/ts-sdk/grpc/src/core/schemas/builders/list/index.ts new file mode 100644 index 00000000000..25f4bcc1737 --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/builders/list/index.ts @@ -0,0 +1 @@ +export { list } from "./list"; diff --git a/seed/ts-sdk/grpc/src/core/schemas/builders/list/list.ts b/seed/ts-sdk/grpc/src/core/schemas/builders/list/list.ts new file mode 100644 index 00000000000..e4c5c4a4a99 --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/builders/list/list.ts @@ -0,0 +1,73 @@ +import { BaseSchema, MaybeValid, Schema, SchemaType, ValidationError } from "../../Schema"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { getSchemaUtils } from "../schema-utils"; + +export function list(schema: Schema): Schema { + const baseSchema: BaseSchema = { + parse: (raw, opts) => + validateAndTransformArray(raw, (item, index) => + schema.parse(item, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `[${index}]`], + }) + ), + json: (parsed, opts) => + validateAndTransformArray(parsed, (item, index) => + schema.json(item, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `[${index}]`], + }) + ), + getType: () => SchemaType.LIST, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + }; +} + +function validateAndTransformArray( + value: unknown, + transformItem: (item: Raw, index: number) => MaybeValid +): MaybeValid { + if (!Array.isArray(value)) { + return { + ok: false, + errors: [ + { + message: getErrorMessageForIncorrectType(value, "list"), + path: [], + }, + ], + }; + } + + const maybeValidItems = value.map((item, index) => transformItem(item, index)); + + return maybeValidItems.reduce>( + (acc, item) => { + if (acc.ok && item.ok) { + return { + ok: true, + value: [...acc.value, item.value], + }; + } + + const errors: ValidationError[] = []; + if (!acc.ok) { + errors.push(...acc.errors); + } + if (!item.ok) { + errors.push(...item.errors); + } + + return { + ok: false, + errors, + }; + }, + { ok: true, value: [] } + ); +} diff --git a/seed/ts-sdk/grpc/src/core/schemas/builders/literals/booleanLiteral.ts b/seed/ts-sdk/grpc/src/core/schemas/builders/literals/booleanLiteral.ts new file mode 100644 index 00000000000..a83d22cd48a --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/builders/literals/booleanLiteral.ts @@ -0,0 +1,29 @@ +import { Schema, SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; + +export function booleanLiteral(literal: V): Schema { + const schemaCreator = createIdentitySchemaCreator( + SchemaType.BOOLEAN_LITERAL, + (value, { breadcrumbsPrefix = [] } = {}) => { + if (value === literal) { + return { + ok: true, + value: literal, + }; + } else { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, `${literal.toString()}`), + }, + ], + }; + } + } + ); + + return schemaCreator(); +} diff --git a/seed/ts-sdk/grpc/src/core/schemas/builders/literals/index.ts b/seed/ts-sdk/grpc/src/core/schemas/builders/literals/index.ts new file mode 100644 index 00000000000..d2bf08fc6ca --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/builders/literals/index.ts @@ -0,0 +1,2 @@ +export { stringLiteral } from "./stringLiteral"; +export { booleanLiteral } from "./booleanLiteral"; diff --git a/seed/ts-sdk/grpc/src/core/schemas/builders/literals/stringLiteral.ts b/seed/ts-sdk/grpc/src/core/schemas/builders/literals/stringLiteral.ts new file mode 100644 index 00000000000..3939b76b48d --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/builders/literals/stringLiteral.ts @@ -0,0 +1,29 @@ +import { Schema, SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; + +export function stringLiteral(literal: V): Schema { + const schemaCreator = createIdentitySchemaCreator( + SchemaType.STRING_LITERAL, + (value, { breadcrumbsPrefix = [] } = {}) => { + if (value === literal) { + return { + ok: true, + value: literal, + }; + } else { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, `"${literal}"`), + }, + ], + }; + } + } + ); + + return schemaCreator(); +} diff --git a/seed/ts-sdk/grpc/src/core/schemas/builders/object-like/getObjectLikeUtils.ts b/seed/ts-sdk/grpc/src/core/schemas/builders/object-like/getObjectLikeUtils.ts new file mode 100644 index 00000000000..8331d08da89 --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/builders/object-like/getObjectLikeUtils.ts @@ -0,0 +1,79 @@ +import { BaseSchema } from "../../Schema"; +import { filterObject } from "../../utils/filterObject"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { isPlainObject } from "../../utils/isPlainObject"; +import { getSchemaUtils } from "../schema-utils"; +import { ObjectLikeSchema, ObjectLikeUtils } from "./types"; + +export function getObjectLikeUtils(schema: BaseSchema): ObjectLikeUtils { + return { + withParsedProperties: (properties) => withParsedProperties(schema, properties), + }; +} + +/** + * object-like utils are defined in one file to resolve issues with circular imports + */ + +export function withParsedProperties( + objectLike: BaseSchema, + properties: { [K in keyof Properties]: Properties[K] | ((parsed: ParsedObjectShape) => Properties[K]) } +): ObjectLikeSchema { + const objectSchema: BaseSchema = { + parse: (raw, opts) => { + const parsedObject = objectLike.parse(raw, opts); + if (!parsedObject.ok) { + return parsedObject; + } + + const additionalProperties = Object.entries(properties).reduce>( + (processed, [key, value]) => { + return { + ...processed, + [key]: typeof value === "function" ? value(parsedObject.value) : value, + }; + }, + {} + ); + + return { + ok: true, + value: { + ...parsedObject.value, + ...(additionalProperties as Properties), + }, + }; + }, + + json: (parsed, opts) => { + if (!isPlainObject(parsed)) { + return { + ok: false, + errors: [ + { + path: opts?.breadcrumbsPrefix ?? [], + message: getErrorMessageForIncorrectType(parsed, "object"), + }, + ], + }; + } + + // strip out added properties + const addedPropertyKeys = new Set(Object.keys(properties)); + const parsedWithoutAddedProperties = filterObject( + parsed, + Object.keys(parsed).filter((key) => !addedPropertyKeys.has(key)) + ); + + return objectLike.json(parsedWithoutAddedProperties as ParsedObjectShape, opts); + }, + + getType: () => objectLike.getType(), + }; + + return { + ...objectSchema, + ...getSchemaUtils(objectSchema), + ...getObjectLikeUtils(objectSchema), + }; +} diff --git a/seed/ts-sdk/grpc/src/core/schemas/builders/object-like/index.ts b/seed/ts-sdk/grpc/src/core/schemas/builders/object-like/index.ts new file mode 100644 index 00000000000..c342e72cf9d --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/builders/object-like/index.ts @@ -0,0 +1,2 @@ +export { getObjectLikeUtils, withParsedProperties } from "./getObjectLikeUtils"; +export type { ObjectLikeSchema, ObjectLikeUtils } from "./types"; diff --git a/seed/ts-sdk/grpc/src/core/schemas/builders/object-like/types.ts b/seed/ts-sdk/grpc/src/core/schemas/builders/object-like/types.ts new file mode 100644 index 00000000000..75b3698729c --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/builders/object-like/types.ts @@ -0,0 +1,11 @@ +import { BaseSchema, Schema } from "../../Schema"; + +export type ObjectLikeSchema = Schema & + BaseSchema & + ObjectLikeUtils; + +export interface ObjectLikeUtils { + withParsedProperties: >(properties: { + [K in keyof T]: T[K] | ((parsed: Parsed) => T[K]); + }) => ObjectLikeSchema; +} diff --git a/seed/ts-sdk/grpc/src/core/schemas/builders/object/index.ts b/seed/ts-sdk/grpc/src/core/schemas/builders/object/index.ts new file mode 100644 index 00000000000..e3f4388db28 --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/builders/object/index.ts @@ -0,0 +1,22 @@ +export { getObjectUtils, object } from "./object"; +export { objectWithoutOptionalProperties } from "./objectWithoutOptionalProperties"; +export type { + inferObjectWithoutOptionalPropertiesSchemaFromPropertySchemas, + inferParsedObjectWithoutOptionalPropertiesFromPropertySchemas, +} from "./objectWithoutOptionalProperties"; +export { isProperty, property } from "./property"; +export type { Property } from "./property"; +export type { + BaseObjectSchema, + inferObjectSchemaFromPropertySchemas, + inferParsedObject, + inferParsedObjectFromPropertySchemas, + inferParsedPropertySchema, + inferRawKey, + inferRawObject, + inferRawObjectFromPropertySchemas, + inferRawPropertySchema, + ObjectSchema, + ObjectUtils, + PropertySchemas, +} from "./types"; diff --git a/seed/ts-sdk/grpc/src/core/schemas/builders/object/object.ts b/seed/ts-sdk/grpc/src/core/schemas/builders/object/object.ts new file mode 100644 index 00000000000..e00136d72fc --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/builders/object/object.ts @@ -0,0 +1,324 @@ +import { MaybeValid, Schema, SchemaType, ValidationError } from "../../Schema"; +import { entries } from "../../utils/entries"; +import { filterObject } from "../../utils/filterObject"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { isPlainObject } from "../../utils/isPlainObject"; +import { keys } from "../../utils/keys"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { partition } from "../../utils/partition"; +import { getObjectLikeUtils } from "../object-like"; +import { getSchemaUtils } from "../schema-utils"; +import { isProperty } from "./property"; +import { + BaseObjectSchema, + inferObjectSchemaFromPropertySchemas, + inferParsedObjectFromPropertySchemas, + inferRawObjectFromPropertySchemas, + ObjectSchema, + ObjectUtils, + PropertySchemas, +} from "./types"; + +interface ObjectPropertyWithRawKey { + rawKey: string; + parsedKey: string; + valueSchema: Schema; +} + +export function object>( + schemas: T +): inferObjectSchemaFromPropertySchemas { + const baseSchema: BaseObjectSchema< + inferRawObjectFromPropertySchemas, + inferParsedObjectFromPropertySchemas + > = { + _getRawProperties: () => + Object.entries(schemas).map(([parsedKey, propertySchema]) => + isProperty(propertySchema) ? propertySchema.rawKey : parsedKey + ) as unknown as (keyof inferRawObjectFromPropertySchemas)[], + _getParsedProperties: () => keys(schemas) as unknown as (keyof inferParsedObjectFromPropertySchemas)[], + + parse: (raw, opts) => { + const rawKeyToProperty: Record = {}; + const requiredKeys: string[] = []; + + for (const [parsedKey, schemaOrObjectProperty] of entries(schemas)) { + const rawKey = isProperty(schemaOrObjectProperty) ? schemaOrObjectProperty.rawKey : parsedKey; + const valueSchema: Schema = isProperty(schemaOrObjectProperty) + ? schemaOrObjectProperty.valueSchema + : schemaOrObjectProperty; + + const property: ObjectPropertyWithRawKey = { + rawKey, + parsedKey: parsedKey as string, + valueSchema, + }; + + rawKeyToProperty[rawKey] = property; + + if (isSchemaRequired(valueSchema)) { + requiredKeys.push(rawKey); + } + } + + return validateAndTransformObject({ + value: raw, + requiredKeys, + getProperty: (rawKey) => { + const property = rawKeyToProperty[rawKey]; + if (property == null) { + return undefined; + } + return { + transformedKey: property.parsedKey, + transform: (propertyValue) => + property.valueSchema.parse(propertyValue, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), rawKey], + }), + }; + }, + unrecognizedObjectKeys: opts?.unrecognizedObjectKeys, + skipValidation: opts?.skipValidation, + breadcrumbsPrefix: opts?.breadcrumbsPrefix, + omitUndefined: opts?.omitUndefined, + }); + }, + + json: (parsed, opts) => { + const requiredKeys: string[] = []; + + for (const [parsedKey, schemaOrObjectProperty] of entries(schemas)) { + const valueSchema: Schema = isProperty(schemaOrObjectProperty) + ? schemaOrObjectProperty.valueSchema + : schemaOrObjectProperty; + + if (isSchemaRequired(valueSchema)) { + requiredKeys.push(parsedKey as string); + } + } + + return validateAndTransformObject({ + value: parsed, + requiredKeys, + getProperty: ( + parsedKey + ): { transformedKey: string; transform: (propertyValue: unknown) => MaybeValid } | undefined => { + const property = schemas[parsedKey as keyof T]; + + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (property == null) { + return undefined; + } + + if (isProperty(property)) { + return { + transformedKey: property.rawKey, + transform: (propertyValue) => + property.valueSchema.json(propertyValue, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), parsedKey], + }), + }; + } else { + return { + transformedKey: parsedKey, + transform: (propertyValue) => + property.json(propertyValue, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), parsedKey], + }), + }; + } + }, + unrecognizedObjectKeys: opts?.unrecognizedObjectKeys, + skipValidation: opts?.skipValidation, + breadcrumbsPrefix: opts?.breadcrumbsPrefix, + omitUndefined: opts?.omitUndefined, + }); + }, + + getType: () => SchemaType.OBJECT, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + ...getObjectLikeUtils(baseSchema), + ...getObjectUtils(baseSchema), + }; +} + +function validateAndTransformObject({ + value, + requiredKeys, + getProperty, + unrecognizedObjectKeys = "fail", + skipValidation = false, + breadcrumbsPrefix = [], +}: { + value: unknown; + requiredKeys: string[]; + getProperty: ( + preTransformedKey: string + ) => { transformedKey: string; transform: (propertyValue: unknown) => MaybeValid } | undefined; + unrecognizedObjectKeys: "fail" | "passthrough" | "strip" | undefined; + skipValidation: boolean | undefined; + breadcrumbsPrefix: string[] | undefined; + omitUndefined: boolean | undefined; +}): MaybeValid { + if (!isPlainObject(value)) { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "object"), + }, + ], + }; + } + + const missingRequiredKeys = new Set(requiredKeys); + const errors: ValidationError[] = []; + const transformed: Record = {}; + + for (const [preTransformedKey, preTransformedItemValue] of Object.entries(value)) { + const property = getProperty(preTransformedKey); + + if (property != null) { + missingRequiredKeys.delete(preTransformedKey); + + const value = property.transform(preTransformedItemValue); + if (value.ok) { + transformed[property.transformedKey] = value.value; + } else { + transformed[preTransformedKey] = preTransformedItemValue; + errors.push(...value.errors); + } + } else { + switch (unrecognizedObjectKeys) { + case "fail": + errors.push({ + path: [...breadcrumbsPrefix, preTransformedKey], + message: `Unexpected key "${preTransformedKey}"`, + }); + break; + case "strip": + break; + case "passthrough": + transformed[preTransformedKey] = preTransformedItemValue; + break; + } + } + } + + errors.push( + ...requiredKeys + .filter((key) => missingRequiredKeys.has(key)) + .map((key) => ({ + path: breadcrumbsPrefix, + message: `Missing required key "${key}"`, + })) + ); + + if (errors.length === 0 || skipValidation) { + return { + ok: true, + value: transformed as Transformed, + }; + } else { + return { + ok: false, + errors, + }; + } +} + +export function getObjectUtils(schema: BaseObjectSchema): ObjectUtils { + return { + extend: (extension: ObjectSchema) => { + const baseSchema: BaseObjectSchema = { + _getParsedProperties: () => [...schema._getParsedProperties(), ...extension._getParsedProperties()], + _getRawProperties: () => [...schema._getRawProperties(), ...extension._getRawProperties()], + parse: (raw, opts) => { + return validateAndTransformExtendedObject({ + extensionKeys: extension._getRawProperties(), + value: raw, + transformBase: (rawBase) => schema.parse(rawBase, opts), + transformExtension: (rawExtension) => extension.parse(rawExtension, opts), + }); + }, + json: (parsed, opts) => { + return validateAndTransformExtendedObject({ + extensionKeys: extension._getParsedProperties(), + value: parsed, + transformBase: (parsedBase) => schema.json(parsedBase, opts), + transformExtension: (parsedExtension) => extension.json(parsedExtension, opts), + }); + }, + getType: () => SchemaType.OBJECT, + }; + + return { + ...baseSchema, + ...getSchemaUtils(baseSchema), + ...getObjectLikeUtils(baseSchema), + ...getObjectUtils(baseSchema), + }; + }, + }; +} + +function validateAndTransformExtendedObject({ + extensionKeys, + value, + transformBase, + transformExtension, +}: { + extensionKeys: (keyof PreTransformedExtension)[]; + value: unknown; + transformBase: (value: unknown) => MaybeValid; + transformExtension: (value: unknown) => MaybeValid; +}): MaybeValid { + const extensionPropertiesSet = new Set(extensionKeys); + const [extensionProperties, baseProperties] = partition(keys(value), (key) => + extensionPropertiesSet.has(key as keyof PreTransformedExtension) + ); + + const transformedBase = transformBase(filterObject(value, baseProperties)); + const transformedExtension = transformExtension(filterObject(value, extensionProperties)); + + if (transformedBase.ok && transformedExtension.ok) { + return { + ok: true, + value: { + ...transformedBase.value, + ...transformedExtension.value, + }, + }; + } else { + return { + ok: false, + errors: [ + ...(transformedBase.ok ? [] : transformedBase.errors), + ...(transformedExtension.ok ? [] : transformedExtension.errors), + ], + }; + } +} + +function isSchemaRequired(schema: Schema): boolean { + return !isSchemaOptional(schema); +} + +function isSchemaOptional(schema: Schema): boolean { + switch (schema.getType()) { + case SchemaType.ANY: + case SchemaType.UNKNOWN: + case SchemaType.OPTIONAL: + return true; + default: + return false; + } +} diff --git a/seed/ts-sdk/grpc/src/core/schemas/builders/object/objectWithoutOptionalProperties.ts b/seed/ts-sdk/grpc/src/core/schemas/builders/object/objectWithoutOptionalProperties.ts new file mode 100644 index 00000000000..a0951f48efc --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/builders/object/objectWithoutOptionalProperties.ts @@ -0,0 +1,18 @@ +import { object } from "./object"; +import { inferParsedPropertySchema, inferRawObjectFromPropertySchemas, ObjectSchema, PropertySchemas } from "./types"; + +export function objectWithoutOptionalProperties>( + schemas: T +): inferObjectWithoutOptionalPropertiesSchemaFromPropertySchemas { + return object(schemas) as unknown as inferObjectWithoutOptionalPropertiesSchemaFromPropertySchemas; +} + +export type inferObjectWithoutOptionalPropertiesSchemaFromPropertySchemas> = + ObjectSchema< + inferRawObjectFromPropertySchemas, + inferParsedObjectWithoutOptionalPropertiesFromPropertySchemas + >; + +export type inferParsedObjectWithoutOptionalPropertiesFromPropertySchemas> = { + [K in keyof T]: inferParsedPropertySchema; +}; diff --git a/seed/ts-sdk/grpc/src/core/schemas/builders/object/property.ts b/seed/ts-sdk/grpc/src/core/schemas/builders/object/property.ts new file mode 100644 index 00000000000..d245c4b193a --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/builders/object/property.ts @@ -0,0 +1,23 @@ +import { Schema } from "../../Schema"; + +export function property( + rawKey: RawKey, + valueSchema: Schema +): Property { + return { + rawKey, + valueSchema, + isProperty: true, + }; +} + +export interface Property { + rawKey: RawKey; + valueSchema: Schema; + isProperty: true; +} + +export function isProperty>(maybeProperty: unknown): maybeProperty is O { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + return (maybeProperty as O).isProperty; +} diff --git a/seed/ts-sdk/grpc/src/core/schemas/builders/object/types.ts b/seed/ts-sdk/grpc/src/core/schemas/builders/object/types.ts new file mode 100644 index 00000000000..de9bb4074e5 --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/builders/object/types.ts @@ -0,0 +1,72 @@ +import { BaseSchema, inferParsed, inferRaw, Schema } from "../../Schema"; +import { addQuestionMarksToNullableProperties } from "../../utils/addQuestionMarksToNullableProperties"; +import { ObjectLikeUtils } from "../object-like"; +import { SchemaUtils } from "../schema-utils"; +import { Property } from "./property"; + +export type ObjectSchema = BaseObjectSchema & + ObjectLikeUtils & + ObjectUtils & + SchemaUtils; + +export interface BaseObjectSchema extends BaseSchema { + _getRawProperties: () => (keyof Raw)[]; + _getParsedProperties: () => (keyof Parsed)[]; +} + +export interface ObjectUtils { + extend: ( + schemas: ObjectSchema + ) => ObjectSchema; +} + +export type inferRawObject> = O extends ObjectSchema ? Raw : never; + +export type inferParsedObject> = O extends ObjectSchema + ? Parsed + : never; + +export type inferObjectSchemaFromPropertySchemas> = ObjectSchema< + inferRawObjectFromPropertySchemas, + inferParsedObjectFromPropertySchemas +>; + +export type inferRawObjectFromPropertySchemas> = + addQuestionMarksToNullableProperties<{ + [ParsedKey in keyof T as inferRawKey]: inferRawPropertySchema; + }>; + +export type inferParsedObjectFromPropertySchemas> = + addQuestionMarksToNullableProperties<{ + [K in keyof T]: inferParsedPropertySchema; + }>; + +export type PropertySchemas = Record< + ParsedKeys, + Property | Schema +>; + +export type inferRawPropertySchema

| Schema> = P extends Property< + any, + infer Raw, + any +> + ? Raw + : P extends Schema + ? inferRaw

+ : never; + +export type inferParsedPropertySchema

| Schema> = P extends Property< + any, + any, + infer Parsed +> + ? Parsed + : P extends Schema + ? inferParsed

+ : never; + +export type inferRawKey< + ParsedKey extends string | number | symbol, + P extends Property | Schema +> = P extends Property ? Raw : ParsedKey; diff --git a/seed/ts-sdk/grpc/src/core/schemas/builders/primitives/any.ts b/seed/ts-sdk/grpc/src/core/schemas/builders/primitives/any.ts new file mode 100644 index 00000000000..fcaeb04255a --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/builders/primitives/any.ts @@ -0,0 +1,4 @@ +import { SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; + +export const any = createIdentitySchemaCreator(SchemaType.ANY, (value) => ({ ok: true, value })); diff --git a/seed/ts-sdk/grpc/src/core/schemas/builders/primitives/boolean.ts b/seed/ts-sdk/grpc/src/core/schemas/builders/primitives/boolean.ts new file mode 100644 index 00000000000..fad60562120 --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/builders/primitives/boolean.ts @@ -0,0 +1,25 @@ +import { SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; + +export const boolean = createIdentitySchemaCreator( + SchemaType.BOOLEAN, + (value, { breadcrumbsPrefix = [] } = {}) => { + if (typeof value === "boolean") { + return { + ok: true, + value, + }; + } else { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "boolean"), + }, + ], + }; + } + } +); diff --git a/seed/ts-sdk/grpc/src/core/schemas/builders/primitives/index.ts b/seed/ts-sdk/grpc/src/core/schemas/builders/primitives/index.ts new file mode 100644 index 00000000000..788f9416bfe --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/builders/primitives/index.ts @@ -0,0 +1,5 @@ +export { any } from "./any"; +export { boolean } from "./boolean"; +export { number } from "./number"; +export { string } from "./string"; +export { unknown } from "./unknown"; diff --git a/seed/ts-sdk/grpc/src/core/schemas/builders/primitives/number.ts b/seed/ts-sdk/grpc/src/core/schemas/builders/primitives/number.ts new file mode 100644 index 00000000000..c2689456936 --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/builders/primitives/number.ts @@ -0,0 +1,25 @@ +import { SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; + +export const number = createIdentitySchemaCreator( + SchemaType.NUMBER, + (value, { breadcrumbsPrefix = [] } = {}) => { + if (typeof value === "number") { + return { + ok: true, + value, + }; + } else { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "number"), + }, + ], + }; + } + } +); diff --git a/seed/ts-sdk/grpc/src/core/schemas/builders/primitives/string.ts b/seed/ts-sdk/grpc/src/core/schemas/builders/primitives/string.ts new file mode 100644 index 00000000000..949f1f2a630 --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/builders/primitives/string.ts @@ -0,0 +1,25 @@ +import { SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; + +export const string = createIdentitySchemaCreator( + SchemaType.STRING, + (value, { breadcrumbsPrefix = [] } = {}) => { + if (typeof value === "string") { + return { + ok: true, + value, + }; + } else { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "string"), + }, + ], + }; + } + } +); diff --git a/seed/ts-sdk/grpc/src/core/schemas/builders/primitives/unknown.ts b/seed/ts-sdk/grpc/src/core/schemas/builders/primitives/unknown.ts new file mode 100644 index 00000000000..4d5249571f5 --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/builders/primitives/unknown.ts @@ -0,0 +1,4 @@ +import { SchemaType } from "../../Schema"; +import { createIdentitySchemaCreator } from "../../utils/createIdentitySchemaCreator"; + +export const unknown = createIdentitySchemaCreator(SchemaType.UNKNOWN, (value) => ({ ok: true, value })); diff --git a/seed/ts-sdk/grpc/src/core/schemas/builders/record/index.ts b/seed/ts-sdk/grpc/src/core/schemas/builders/record/index.ts new file mode 100644 index 00000000000..82e25c5c2af --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/builders/record/index.ts @@ -0,0 +1,2 @@ +export { record } from "./record"; +export type { BaseRecordSchema, RecordSchema } from "./types"; diff --git a/seed/ts-sdk/grpc/src/core/schemas/builders/record/record.ts b/seed/ts-sdk/grpc/src/core/schemas/builders/record/record.ts new file mode 100644 index 00000000000..6683ac3609f --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/builders/record/record.ts @@ -0,0 +1,130 @@ +import { MaybeValid, Schema, SchemaType, ValidationError } from "../../Schema"; +import { entries } from "../../utils/entries"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { isPlainObject } from "../../utils/isPlainObject"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { getSchemaUtils } from "../schema-utils"; +import { BaseRecordSchema, RecordSchema } from "./types"; + +export function record( + keySchema: Schema, + valueSchema: Schema +): RecordSchema { + const baseSchema: BaseRecordSchema = { + parse: (raw, opts) => { + return validateAndTransformRecord({ + value: raw, + isKeyNumeric: keySchema.getType() === SchemaType.NUMBER, + transformKey: (key) => + keySchema.parse(key, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `${key} (key)`], + }), + transformValue: (value, key) => + valueSchema.parse(value, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `${key}`], + }), + breadcrumbsPrefix: opts?.breadcrumbsPrefix, + }); + }, + json: (parsed, opts) => { + return validateAndTransformRecord({ + value: parsed, + isKeyNumeric: keySchema.getType() === SchemaType.NUMBER, + transformKey: (key) => + keySchema.json(key, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `${key} (key)`], + }), + transformValue: (value, key) => + valueSchema.json(value, { + ...opts, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), `${key}`], + }), + breadcrumbsPrefix: opts?.breadcrumbsPrefix, + }); + }, + getType: () => SchemaType.RECORD, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + }; +} + +function validateAndTransformRecord({ + value, + isKeyNumeric, + transformKey, + transformValue, + breadcrumbsPrefix = [], +}: { + value: unknown; + isKeyNumeric: boolean; + transformKey: (key: string | number) => MaybeValid; + transformValue: (value: unknown, key: string | number) => MaybeValid; + breadcrumbsPrefix: string[] | undefined; +}): MaybeValid> { + if (!isPlainObject(value)) { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "object"), + }, + ], + }; + } + + return entries(value).reduce>>( + (accPromise, [stringKey, value]) => { + // skip nullish keys + if (value == null) { + return accPromise; + } + + const acc = accPromise; + + let key: string | number = stringKey; + if (isKeyNumeric) { + const numberKey = stringKey.length > 0 ? Number(stringKey) : NaN; + if (!isNaN(numberKey)) { + key = numberKey; + } + } + const transformedKey = transformKey(key); + + const transformedValue = transformValue(value, key); + + if (acc.ok && transformedKey.ok && transformedValue.ok) { + return { + ok: true, + value: { + ...acc.value, + [transformedKey.value]: transformedValue.value, + }, + }; + } + + const errors: ValidationError[] = []; + if (!acc.ok) { + errors.push(...acc.errors); + } + if (!transformedKey.ok) { + errors.push(...transformedKey.errors); + } + if (!transformedValue.ok) { + errors.push(...transformedValue.errors); + } + + return { + ok: false, + errors, + }; + }, + { ok: true, value: {} as Record } + ); +} diff --git a/seed/ts-sdk/grpc/src/core/schemas/builders/record/types.ts b/seed/ts-sdk/grpc/src/core/schemas/builders/record/types.ts new file mode 100644 index 00000000000..eb82cc7f65c --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/builders/record/types.ts @@ -0,0 +1,17 @@ +import { BaseSchema } from "../../Schema"; +import { SchemaUtils } from "../schema-utils"; + +export type RecordSchema< + RawKey extends string | number, + RawValue, + ParsedKey extends string | number, + ParsedValue +> = BaseRecordSchema & + SchemaUtils, Record>; + +export type BaseRecordSchema< + RawKey extends string | number, + RawValue, + ParsedKey extends string | number, + ParsedValue +> = BaseSchema, Record>; diff --git a/seed/ts-sdk/grpc/src/core/schemas/builders/schema-utils/JsonError.ts b/seed/ts-sdk/grpc/src/core/schemas/builders/schema-utils/JsonError.ts new file mode 100644 index 00000000000..2b89ca0e7ad --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/builders/schema-utils/JsonError.ts @@ -0,0 +1,9 @@ +import { ValidationError } from "../../Schema"; +import { stringifyValidationError } from "./stringifyValidationErrors"; + +export class JsonError extends Error { + constructor(public readonly errors: ValidationError[]) { + super(errors.map(stringifyValidationError).join("; ")); + Object.setPrototypeOf(this, JsonError.prototype); + } +} diff --git a/seed/ts-sdk/grpc/src/core/schemas/builders/schema-utils/ParseError.ts b/seed/ts-sdk/grpc/src/core/schemas/builders/schema-utils/ParseError.ts new file mode 100644 index 00000000000..d056eb45cf7 --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/builders/schema-utils/ParseError.ts @@ -0,0 +1,9 @@ +import { ValidationError } from "../../Schema"; +import { stringifyValidationError } from "./stringifyValidationErrors"; + +export class ParseError extends Error { + constructor(public readonly errors: ValidationError[]) { + super(errors.map(stringifyValidationError).join("; ")); + Object.setPrototypeOf(this, ParseError.prototype); + } +} diff --git a/seed/ts-sdk/grpc/src/core/schemas/builders/schema-utils/getSchemaUtils.ts b/seed/ts-sdk/grpc/src/core/schemas/builders/schema-utils/getSchemaUtils.ts new file mode 100644 index 00000000000..79ecad92132 --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/builders/schema-utils/getSchemaUtils.ts @@ -0,0 +1,105 @@ +import { BaseSchema, Schema, SchemaOptions, SchemaType } from "../../Schema"; +import { JsonError } from "./JsonError"; +import { ParseError } from "./ParseError"; + +export interface SchemaUtils { + optional: () => Schema; + transform: (transformer: SchemaTransformer) => Schema; + parseOrThrow: (raw: unknown, opts?: SchemaOptions) => Parsed; + jsonOrThrow: (raw: unknown, opts?: SchemaOptions) => Raw; +} + +export interface SchemaTransformer { + transform: (parsed: Parsed) => Transformed; + untransform: (transformed: any) => Parsed; +} + +export function getSchemaUtils(schema: BaseSchema): SchemaUtils { + return { + optional: () => optional(schema), + transform: (transformer) => transform(schema, transformer), + parseOrThrow: (raw, opts) => { + const parsed = schema.parse(raw, opts); + if (parsed.ok) { + return parsed.value; + } + throw new ParseError(parsed.errors); + }, + jsonOrThrow: (parsed, opts) => { + const raw = schema.json(parsed, opts); + if (raw.ok) { + return raw.value; + } + throw new JsonError(raw.errors); + }, + }; +} + +/** + * schema utils are defined in one file to resolve issues with circular imports + */ + +export function optional( + schema: BaseSchema +): Schema { + const baseSchema: BaseSchema = { + parse: (raw, opts) => { + if (raw == null) { + return { + ok: true, + value: undefined, + }; + } + return schema.parse(raw, opts); + }, + json: (parsed, opts) => { + if (opts?.omitUndefined && parsed === undefined) { + return { + ok: true, + value: undefined, + }; + } + if (parsed == null) { + return { + ok: true, + value: null, + }; + } + return schema.json(parsed, opts); + }, + getType: () => SchemaType.OPTIONAL, + }; + + return { + ...baseSchema, + ...getSchemaUtils(baseSchema), + }; +} + +export function transform( + schema: BaseSchema, + transformer: SchemaTransformer +): Schema { + const baseSchema: BaseSchema = { + parse: (raw, opts) => { + const parsed = schema.parse(raw, opts); + if (!parsed.ok) { + return parsed; + } + return { + ok: true, + value: transformer.transform(parsed.value), + }; + }, + json: (transformed, opts) => { + const parsed = transformer.untransform(transformed); + return schema.json(parsed, opts); + }, + getType: () => schema.getType(), + }; + + return { + ...baseSchema, + ...getSchemaUtils(baseSchema), + }; +} diff --git a/seed/ts-sdk/grpc/src/core/schemas/builders/schema-utils/index.ts b/seed/ts-sdk/grpc/src/core/schemas/builders/schema-utils/index.ts new file mode 100644 index 00000000000..aa04e051dfa --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/builders/schema-utils/index.ts @@ -0,0 +1,4 @@ +export { getSchemaUtils, optional, transform } from "./getSchemaUtils"; +export type { SchemaUtils } from "./getSchemaUtils"; +export { JsonError } from "./JsonError"; +export { ParseError } from "./ParseError"; diff --git a/seed/ts-sdk/grpc/src/core/schemas/builders/schema-utils/stringifyValidationErrors.ts b/seed/ts-sdk/grpc/src/core/schemas/builders/schema-utils/stringifyValidationErrors.ts new file mode 100644 index 00000000000..4160f0a2617 --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/builders/schema-utils/stringifyValidationErrors.ts @@ -0,0 +1,8 @@ +import { ValidationError } from "../../Schema"; + +export function stringifyValidationError(error: ValidationError): string { + if (error.path.length === 0) { + return error.message; + } + return `${error.path.join(" -> ")}: ${error.message}`; +} diff --git a/seed/ts-sdk/grpc/src/core/schemas/builders/set/index.ts b/seed/ts-sdk/grpc/src/core/schemas/builders/set/index.ts new file mode 100644 index 00000000000..f3310e8bdad --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/builders/set/index.ts @@ -0,0 +1 @@ +export { set } from "./set"; diff --git a/seed/ts-sdk/grpc/src/core/schemas/builders/set/set.ts b/seed/ts-sdk/grpc/src/core/schemas/builders/set/set.ts new file mode 100644 index 00000000000..e9e6bb7e539 --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/builders/set/set.ts @@ -0,0 +1,43 @@ +import { BaseSchema, Schema, SchemaType } from "../../Schema"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { list } from "../list"; +import { getSchemaUtils } from "../schema-utils"; + +export function set(schema: Schema): Schema> { + const listSchema = list(schema); + const baseSchema: BaseSchema> = { + parse: (raw, opts) => { + const parsedList = listSchema.parse(raw, opts); + if (parsedList.ok) { + return { + ok: true, + value: new Set(parsedList.value), + }; + } else { + return parsedList; + } + }, + json: (parsed, opts) => { + if (!(parsed instanceof Set)) { + return { + ok: false, + errors: [ + { + path: opts?.breadcrumbsPrefix ?? [], + message: getErrorMessageForIncorrectType(parsed, "Set"), + }, + ], + }; + } + const jsonList = listSchema.json([...parsed], opts); + return jsonList; + }, + getType: () => SchemaType.SET, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + }; +} diff --git a/seed/ts-sdk/grpc/src/core/schemas/builders/undiscriminated-union/index.ts b/seed/ts-sdk/grpc/src/core/schemas/builders/undiscriminated-union/index.ts new file mode 100644 index 00000000000..75b71cb3565 --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/builders/undiscriminated-union/index.ts @@ -0,0 +1,6 @@ +export type { + inferParsedUnidiscriminatedUnionSchema, + inferRawUnidiscriminatedUnionSchema, + UndiscriminatedUnionSchema, +} from "./types"; +export { undiscriminatedUnion } from "./undiscriminatedUnion"; diff --git a/seed/ts-sdk/grpc/src/core/schemas/builders/undiscriminated-union/types.ts b/seed/ts-sdk/grpc/src/core/schemas/builders/undiscriminated-union/types.ts new file mode 100644 index 00000000000..43e7108a060 --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/builders/undiscriminated-union/types.ts @@ -0,0 +1,10 @@ +import { inferParsed, inferRaw, Schema } from "../../Schema"; + +export type UndiscriminatedUnionSchema = Schema< + inferRawUnidiscriminatedUnionSchema, + inferParsedUnidiscriminatedUnionSchema +>; + +export type inferRawUnidiscriminatedUnionSchema = inferRaw; + +export type inferParsedUnidiscriminatedUnionSchema = inferParsed; diff --git a/seed/ts-sdk/grpc/src/core/schemas/builders/undiscriminated-union/undiscriminatedUnion.ts b/seed/ts-sdk/grpc/src/core/schemas/builders/undiscriminated-union/undiscriminatedUnion.ts new file mode 100644 index 00000000000..21ed3df0f40 --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/builders/undiscriminated-union/undiscriminatedUnion.ts @@ -0,0 +1,60 @@ +import { BaseSchema, MaybeValid, Schema, SchemaOptions, SchemaType, ValidationError } from "../../Schema"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { getSchemaUtils } from "../schema-utils"; +import { inferParsedUnidiscriminatedUnionSchema, inferRawUnidiscriminatedUnionSchema } from "./types"; + +export function undiscriminatedUnion, ...Schema[]]>( + schemas: Schemas +): Schema, inferParsedUnidiscriminatedUnionSchema> { + const baseSchema: BaseSchema< + inferRawUnidiscriminatedUnionSchema, + inferParsedUnidiscriminatedUnionSchema + > = { + parse: (raw, opts) => { + return validateAndTransformUndiscriminatedUnion>( + (schema, opts) => schema.parse(raw, opts), + schemas, + opts + ); + }, + json: (parsed, opts) => { + return validateAndTransformUndiscriminatedUnion>( + (schema, opts) => schema.json(parsed, opts), + schemas, + opts + ); + }, + getType: () => SchemaType.UNDISCRIMINATED_UNION, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + }; +} + +function validateAndTransformUndiscriminatedUnion( + transform: (schema: Schema, opts: SchemaOptions) => MaybeValid, + schemas: Schema[], + opts: SchemaOptions | undefined +): MaybeValid { + const errors: ValidationError[] = []; + for (const [index, schema] of schemas.entries()) { + const transformed = transform(schema, { ...opts, skipValidation: false }); + if (transformed.ok) { + return transformed; + } else { + for (const error of transformed.errors) { + errors.push({ + path: error.path, + message: `[Variant ${index}] ${error.message}`, + }); + } + } + } + + return { + ok: false, + errors, + }; +} diff --git a/seed/ts-sdk/grpc/src/core/schemas/builders/union/discriminant.ts b/seed/ts-sdk/grpc/src/core/schemas/builders/union/discriminant.ts new file mode 100644 index 00000000000..55065bc8946 --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/builders/union/discriminant.ts @@ -0,0 +1,14 @@ +export function discriminant( + parsedDiscriminant: ParsedDiscriminant, + rawDiscriminant: RawDiscriminant +): Discriminant { + return { + parsedDiscriminant, + rawDiscriminant, + }; +} + +export interface Discriminant { + parsedDiscriminant: ParsedDiscriminant; + rawDiscriminant: RawDiscriminant; +} diff --git a/seed/ts-sdk/grpc/src/core/schemas/builders/union/index.ts b/seed/ts-sdk/grpc/src/core/schemas/builders/union/index.ts new file mode 100644 index 00000000000..85fc008a2d8 --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/builders/union/index.ts @@ -0,0 +1,10 @@ +export { discriminant } from "./discriminant"; +export type { Discriminant } from "./discriminant"; +export type { + inferParsedDiscriminant, + inferParsedUnion, + inferRawDiscriminant, + inferRawUnion, + UnionSubtypes, +} from "./types"; +export { union } from "./union"; diff --git a/seed/ts-sdk/grpc/src/core/schemas/builders/union/types.ts b/seed/ts-sdk/grpc/src/core/schemas/builders/union/types.ts new file mode 100644 index 00000000000..6f82c868b2d --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/builders/union/types.ts @@ -0,0 +1,26 @@ +import { inferParsedObject, inferRawObject, ObjectSchema } from "../object"; +import { Discriminant } from "./discriminant"; + +export type UnionSubtypes = { + [K in DiscriminantValues]: ObjectSchema; +}; + +export type inferRawUnion, U extends UnionSubtypes> = { + [K in keyof U]: Record, K> & inferRawObject; +}[keyof U]; + +export type inferParsedUnion, U extends UnionSubtypes> = { + [K in keyof U]: Record, K> & inferParsedObject; +}[keyof U]; + +export type inferRawDiscriminant> = D extends string + ? D + : D extends Discriminant + ? Raw + : never; + +export type inferParsedDiscriminant> = D extends string + ? D + : D extends Discriminant + ? Parsed + : never; diff --git a/seed/ts-sdk/grpc/src/core/schemas/builders/union/union.ts b/seed/ts-sdk/grpc/src/core/schemas/builders/union/union.ts new file mode 100644 index 00000000000..ab61475a572 --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/builders/union/union.ts @@ -0,0 +1,170 @@ +import { BaseSchema, MaybeValid, SchemaType } from "../../Schema"; +import { getErrorMessageForIncorrectType } from "../../utils/getErrorMessageForIncorrectType"; +import { isPlainObject } from "../../utils/isPlainObject"; +import { keys } from "../../utils/keys"; +import { maybeSkipValidation } from "../../utils/maybeSkipValidation"; +import { enum_ } from "../enum"; +import { ObjectSchema } from "../object"; +import { getObjectLikeUtils, ObjectLikeSchema } from "../object-like"; +import { getSchemaUtils } from "../schema-utils"; +import { Discriminant } from "./discriminant"; +import { inferParsedDiscriminant, inferParsedUnion, inferRawDiscriminant, inferRawUnion, UnionSubtypes } from "./types"; + +export function union, U extends UnionSubtypes>( + discriminant: D, + union: U +): ObjectLikeSchema, inferParsedUnion> { + const rawDiscriminant = + typeof discriminant === "string" ? discriminant : (discriminant.rawDiscriminant as inferRawDiscriminant); + const parsedDiscriminant = + typeof discriminant === "string" + ? discriminant + : (discriminant.parsedDiscriminant as inferParsedDiscriminant); + + const discriminantValueSchema = enum_(keys(union) as string[]); + + const baseSchema: BaseSchema, inferParsedUnion> = { + parse: (raw, opts) => { + return transformAndValidateUnion({ + value: raw, + discriminant: rawDiscriminant, + transformedDiscriminant: parsedDiscriminant, + transformDiscriminantValue: (discriminantValue) => + discriminantValueSchema.parse(discriminantValue, { + allowUnrecognizedEnumValues: opts?.allowUnrecognizedUnionMembers, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), rawDiscriminant], + }), + getAdditionalPropertiesSchema: (discriminantValue) => union[discriminantValue], + allowUnrecognizedUnionMembers: opts?.allowUnrecognizedUnionMembers, + transformAdditionalProperties: (additionalProperties, additionalPropertiesSchema) => + additionalPropertiesSchema.parse(additionalProperties, opts), + breadcrumbsPrefix: opts?.breadcrumbsPrefix, + }); + }, + json: (parsed, opts) => { + return transformAndValidateUnion({ + value: parsed, + discriminant: parsedDiscriminant, + transformedDiscriminant: rawDiscriminant, + transformDiscriminantValue: (discriminantValue) => + discriminantValueSchema.json(discriminantValue, { + allowUnrecognizedEnumValues: opts?.allowUnrecognizedUnionMembers, + breadcrumbsPrefix: [...(opts?.breadcrumbsPrefix ?? []), parsedDiscriminant], + }), + getAdditionalPropertiesSchema: (discriminantValue) => union[discriminantValue], + allowUnrecognizedUnionMembers: opts?.allowUnrecognizedUnionMembers, + transformAdditionalProperties: (additionalProperties, additionalPropertiesSchema) => + additionalPropertiesSchema.json(additionalProperties, opts), + breadcrumbsPrefix: opts?.breadcrumbsPrefix, + }); + }, + getType: () => SchemaType.UNION, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + ...getObjectLikeUtils(baseSchema), + }; +} + +function transformAndValidateUnion< + TransformedDiscriminant extends string, + TransformedDiscriminantValue extends string, + TransformedAdditionalProperties +>({ + value, + discriminant, + transformedDiscriminant, + transformDiscriminantValue, + getAdditionalPropertiesSchema, + allowUnrecognizedUnionMembers = false, + transformAdditionalProperties, + breadcrumbsPrefix = [], +}: { + value: unknown; + discriminant: string; + transformedDiscriminant: TransformedDiscriminant; + transformDiscriminantValue: (discriminantValue: unknown) => MaybeValid; + getAdditionalPropertiesSchema: (discriminantValue: string) => ObjectSchema | undefined; + allowUnrecognizedUnionMembers: boolean | undefined; + transformAdditionalProperties: ( + additionalProperties: unknown, + additionalPropertiesSchema: ObjectSchema + ) => MaybeValid; + breadcrumbsPrefix: string[] | undefined; +}): MaybeValid & TransformedAdditionalProperties> { + if (!isPlainObject(value)) { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: getErrorMessageForIncorrectType(value, "object"), + }, + ], + }; + } + + const { [discriminant]: discriminantValue, ...additionalProperties } = value; + + if (discriminantValue == null) { + return { + ok: false, + errors: [ + { + path: breadcrumbsPrefix, + message: `Missing discriminant ("${discriminant}")`, + }, + ], + }; + } + + const transformedDiscriminantValue = transformDiscriminantValue(discriminantValue); + if (!transformedDiscriminantValue.ok) { + return { + ok: false, + errors: transformedDiscriminantValue.errors, + }; + } + + const additionalPropertiesSchema = getAdditionalPropertiesSchema(transformedDiscriminantValue.value); + + if (additionalPropertiesSchema == null) { + if (allowUnrecognizedUnionMembers) { + return { + ok: true, + value: { + [transformedDiscriminant]: transformedDiscriminantValue.value, + ...additionalProperties, + } as Record & TransformedAdditionalProperties, + }; + } else { + return { + ok: false, + errors: [ + { + path: [...breadcrumbsPrefix, discriminant], + message: "Unexpected discriminant value", + }, + ], + }; + } + } + + const transformedAdditionalProperties = transformAdditionalProperties( + additionalProperties, + additionalPropertiesSchema + ); + if (!transformedAdditionalProperties.ok) { + return transformedAdditionalProperties; + } + + return { + ok: true, + value: { + [transformedDiscriminant]: discriminantValue, + ...transformedAdditionalProperties.value, + } as Record & TransformedAdditionalProperties, + }; +} diff --git a/seed/ts-sdk/grpc/src/core/schemas/index.ts b/seed/ts-sdk/grpc/src/core/schemas/index.ts new file mode 100644 index 00000000000..5429d8b43eb --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/index.ts @@ -0,0 +1,2 @@ +export * from "./builders"; +export type { inferParsed, inferRaw, Schema, SchemaOptions } from "./Schema"; diff --git a/seed/ts-sdk/grpc/src/core/schemas/utils/MaybePromise.ts b/seed/ts-sdk/grpc/src/core/schemas/utils/MaybePromise.ts new file mode 100644 index 00000000000..9cd354b3418 --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/utils/MaybePromise.ts @@ -0,0 +1 @@ +export type MaybePromise = T | Promise; diff --git a/seed/ts-sdk/grpc/src/core/schemas/utils/addQuestionMarksToNullableProperties.ts b/seed/ts-sdk/grpc/src/core/schemas/utils/addQuestionMarksToNullableProperties.ts new file mode 100644 index 00000000000..4111d703cd0 --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/utils/addQuestionMarksToNullableProperties.ts @@ -0,0 +1,15 @@ +export type addQuestionMarksToNullableProperties = { + [K in OptionalKeys]?: T[K]; +} & Pick>; + +export type OptionalKeys = { + [K in keyof T]-?: undefined extends T[K] + ? K + : null extends T[K] + ? K + : 1 extends (any extends T[K] ? 0 : 1) + ? never + : K; +}[keyof T]; + +export type RequiredKeys = Exclude>; diff --git a/seed/ts-sdk/grpc/src/core/schemas/utils/createIdentitySchemaCreator.ts b/seed/ts-sdk/grpc/src/core/schemas/utils/createIdentitySchemaCreator.ts new file mode 100644 index 00000000000..de107cf5ee1 --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/utils/createIdentitySchemaCreator.ts @@ -0,0 +1,21 @@ +import { getSchemaUtils } from "../builders/schema-utils"; +import { BaseSchema, MaybeValid, Schema, SchemaOptions, SchemaType } from "../Schema"; +import { maybeSkipValidation } from "./maybeSkipValidation"; + +export function createIdentitySchemaCreator( + schemaType: SchemaType, + validate: (value: unknown, opts?: SchemaOptions) => MaybeValid +): () => Schema { + return () => { + const baseSchema: BaseSchema = { + parse: validate, + json: validate, + getType: () => schemaType, + }; + + return { + ...maybeSkipValidation(baseSchema), + ...getSchemaUtils(baseSchema), + }; + }; +} diff --git a/seed/ts-sdk/grpc/src/core/schemas/utils/entries.ts b/seed/ts-sdk/grpc/src/core/schemas/utils/entries.ts new file mode 100644 index 00000000000..e122952137d --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/utils/entries.ts @@ -0,0 +1,3 @@ +export function entries(object: T): [keyof T, T[keyof T]][] { + return Object.entries(object) as [keyof T, T[keyof T]][]; +} diff --git a/seed/ts-sdk/grpc/src/core/schemas/utils/filterObject.ts b/seed/ts-sdk/grpc/src/core/schemas/utils/filterObject.ts new file mode 100644 index 00000000000..2c25a3455bc --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/utils/filterObject.ts @@ -0,0 +1,10 @@ +export function filterObject(obj: T, keysToInclude: K[]): Pick { + const keysToIncludeSet = new Set(keysToInclude); + return Object.entries(obj).reduce((acc, [key, value]) => { + if (keysToIncludeSet.has(key as K)) { + acc[key as K] = value; + } + return acc; + // eslint-disable-next-line @typescript-eslint/prefer-reduce-type-parameter + }, {} as Pick); +} diff --git a/seed/ts-sdk/grpc/src/core/schemas/utils/getErrorMessageForIncorrectType.ts b/seed/ts-sdk/grpc/src/core/schemas/utils/getErrorMessageForIncorrectType.ts new file mode 100644 index 00000000000..438012df418 --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/utils/getErrorMessageForIncorrectType.ts @@ -0,0 +1,21 @@ +export function getErrorMessageForIncorrectType(value: unknown, expectedType: string): string { + return `Expected ${expectedType}. Received ${getTypeAsString(value)}.`; +} + +function getTypeAsString(value: unknown): string { + if (Array.isArray(value)) { + return "list"; + } + if (value === null) { + return "null"; + } + switch (typeof value) { + case "string": + return `"${value}"`; + case "number": + case "boolean": + case "undefined": + return `${value}`; + } + return typeof value; +} diff --git a/seed/ts-sdk/grpc/src/core/schemas/utils/isPlainObject.ts b/seed/ts-sdk/grpc/src/core/schemas/utils/isPlainObject.ts new file mode 100644 index 00000000000..db82a722c35 --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/utils/isPlainObject.ts @@ -0,0 +1,17 @@ +// borrowed from https://github.com/lodash/lodash/blob/master/isPlainObject.js +export function isPlainObject(value: unknown): value is Record { + if (typeof value !== "object" || value === null) { + return false; + } + + if (Object.getPrototypeOf(value) === null) { + return true; + } + + let proto = value; + while (Object.getPrototypeOf(proto) !== null) { + proto = Object.getPrototypeOf(proto); + } + + return Object.getPrototypeOf(value) === proto; +} diff --git a/seed/ts-sdk/grpc/src/core/schemas/utils/keys.ts b/seed/ts-sdk/grpc/src/core/schemas/utils/keys.ts new file mode 100644 index 00000000000..01867098287 --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/utils/keys.ts @@ -0,0 +1,3 @@ +export function keys(object: T): (keyof T)[] { + return Object.keys(object) as (keyof T)[]; +} diff --git a/seed/ts-sdk/grpc/src/core/schemas/utils/maybeSkipValidation.ts b/seed/ts-sdk/grpc/src/core/schemas/utils/maybeSkipValidation.ts new file mode 100644 index 00000000000..86c07abf2b4 --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/utils/maybeSkipValidation.ts @@ -0,0 +1,38 @@ +import { BaseSchema, MaybeValid, SchemaOptions } from "../Schema"; + +export function maybeSkipValidation, Raw, Parsed>(schema: S): S { + return { + ...schema, + json: transformAndMaybeSkipValidation(schema.json), + parse: transformAndMaybeSkipValidation(schema.parse), + }; +} + +function transformAndMaybeSkipValidation( + transform: (value: unknown, opts?: SchemaOptions) => MaybeValid +): (value: unknown, opts?: SchemaOptions) => MaybeValid { + return (value, opts): MaybeValid => { + const transformed = transform(value, opts); + const { skipValidation = false } = opts ?? {}; + if (!transformed.ok && skipValidation) { + // eslint-disable-next-line no-console + console.warn( + [ + "Failed to validate.", + ...transformed.errors.map( + (error) => + " - " + + (error.path.length > 0 ? `${error.path.join(".")}: ${error.message}` : error.message) + ), + ].join("\n") + ); + + return { + ok: true, + value: value as T, + }; + } else { + return transformed; + } + }; +} diff --git a/seed/ts-sdk/grpc/src/core/schemas/utils/partition.ts b/seed/ts-sdk/grpc/src/core/schemas/utils/partition.ts new file mode 100644 index 00000000000..f58d6f3d35f --- /dev/null +++ b/seed/ts-sdk/grpc/src/core/schemas/utils/partition.ts @@ -0,0 +1,12 @@ +export function partition(items: readonly T[], predicate: (item: T) => boolean): [T[], T[]] { + const trueItems: T[] = [], + falseItems: T[] = []; + for (const item of items) { + if (predicate(item)) { + trueItems.push(item); + } else { + falseItems.push(item); + } + } + return [trueItems, falseItems]; +} diff --git a/seed/ts-sdk/grpc/src/errors/SeedApiError.ts b/seed/ts-sdk/grpc/src/errors/SeedApiError.ts new file mode 100644 index 00000000000..4e591f80194 --- /dev/null +++ b/seed/ts-sdk/grpc/src/errors/SeedApiError.ts @@ -0,0 +1,45 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +export class SeedApiError extends Error { + readonly statusCode?: number; + readonly body?: unknown; + + constructor({ message, statusCode, body }: { message?: string; statusCode?: number; body?: unknown }) { + super(buildMessage({ message, statusCode, body })); + Object.setPrototypeOf(this, SeedApiError.prototype); + if (statusCode != null) { + this.statusCode = statusCode; + } + + if (body !== undefined) { + this.body = body; + } + } +} + +function buildMessage({ + message, + statusCode, + body, +}: { + message: string | undefined; + statusCode: number | undefined; + body: unknown | undefined; +}): string { + let lines: string[] = []; + if (message != null) { + lines.push(message); + } + + if (statusCode != null) { + lines.push(`Status code: ${statusCode.toString()}`); + } + + if (body != null) { + lines.push(`Body: ${JSON.stringify(body, undefined, 2)}`); + } + + return lines.join("\n"); +} diff --git a/seed/ts-sdk/grpc/src/errors/SeedApiTimeoutError.ts b/seed/ts-sdk/grpc/src/errors/SeedApiTimeoutError.ts new file mode 100644 index 00000000000..79e2569aac3 --- /dev/null +++ b/seed/ts-sdk/grpc/src/errors/SeedApiTimeoutError.ts @@ -0,0 +1,10 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +export class SeedApiTimeoutError extends Error { + constructor() { + super("Timeout"); + Object.setPrototypeOf(this, SeedApiTimeoutError.prototype); + } +} diff --git a/seed/ts-sdk/grpc/src/errors/index.ts b/seed/ts-sdk/grpc/src/errors/index.ts new file mode 100644 index 00000000000..83870aebd43 --- /dev/null +++ b/seed/ts-sdk/grpc/src/errors/index.ts @@ -0,0 +1,2 @@ +export { SeedApiError } from "./SeedApiError"; +export { SeedApiTimeoutError } from "./SeedApiTimeoutError"; diff --git a/seed/ts-sdk/grpc/src/index.ts b/seed/ts-sdk/grpc/src/index.ts new file mode 100644 index 00000000000..bed9941cdaa --- /dev/null +++ b/seed/ts-sdk/grpc/src/index.ts @@ -0,0 +1,3 @@ +export * as SeedApi from "./api"; +export { SeedApiClient } from "./Client"; +export { SeedApiError, SeedApiTimeoutError } from "./errors"; diff --git a/seed/ts-sdk/grpc/src/serialization/index.ts b/seed/ts-sdk/grpc/src/serialization/index.ts new file mode 100644 index 00000000000..3e5335fe421 --- /dev/null +++ b/seed/ts-sdk/grpc/src/serialization/index.ts @@ -0,0 +1 @@ +export * from "./resources"; diff --git a/seed/ts-sdk/grpc/src/serialization/resources/index.ts b/seed/ts-sdk/grpc/src/serialization/resources/index.ts new file mode 100644 index 00000000000..0671bd81e84 --- /dev/null +++ b/seed/ts-sdk/grpc/src/serialization/resources/index.ts @@ -0,0 +1,3 @@ +export * as user from "./user"; +export * from "./user/types"; +export * from "./user/client/requests"; diff --git a/seed/ts-sdk/grpc/src/serialization/resources/user/client/index.ts b/seed/ts-sdk/grpc/src/serialization/resources/user/client/index.ts new file mode 100644 index 00000000000..415726b7fea --- /dev/null +++ b/seed/ts-sdk/grpc/src/serialization/resources/user/client/index.ts @@ -0,0 +1 @@ +export * from "./requests"; diff --git a/seed/ts-sdk/grpc/src/serialization/resources/user/client/requests/CreateUserRequest.ts b/seed/ts-sdk/grpc/src/serialization/resources/user/client/requests/CreateUserRequest.ts new file mode 100644 index 00000000000..8e7d8190af2 --- /dev/null +++ b/seed/ts-sdk/grpc/src/serialization/resources/user/client/requests/CreateUserRequest.ts @@ -0,0 +1,26 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../../index"; +import * as SeedApi from "../../../../../api/index"; +import * as core from "../../../../../core"; + +export const CreateUserRequest: core.serialization.Schema< + serializers.CreateUserRequest.Raw, + SeedApi.CreateUserRequest +> = core.serialization.object({ + username: core.serialization.string(), + email: core.serialization.string().optional(), + age: core.serialization.number().optional(), + weight: core.serialization.number().optional(), +}); + +export declare namespace CreateUserRequest { + interface Raw { + username: string; + email?: string | null; + age?: number | null; + weight?: number | null; + } +} diff --git a/seed/ts-sdk/grpc/src/serialization/resources/user/client/requests/index.ts b/seed/ts-sdk/grpc/src/serialization/resources/user/client/requests/index.ts new file mode 100644 index 00000000000..5632de83260 --- /dev/null +++ b/seed/ts-sdk/grpc/src/serialization/resources/user/client/requests/index.ts @@ -0,0 +1 @@ +export { CreateUserRequest } from "./CreateUserRequest"; diff --git a/seed/ts-sdk/grpc/src/serialization/resources/user/index.ts b/seed/ts-sdk/grpc/src/serialization/resources/user/index.ts new file mode 100644 index 00000000000..c9240f83b48 --- /dev/null +++ b/seed/ts-sdk/grpc/src/serialization/resources/user/index.ts @@ -0,0 +1,2 @@ +export * from "./types"; +export * from "./client"; diff --git a/seed/ts-sdk/grpc/src/serialization/resources/user/types/CreateUserResponse.ts b/seed/ts-sdk/grpc/src/serialization/resources/user/types/CreateUserResponse.ts new file mode 100644 index 00000000000..0461632b11a --- /dev/null +++ b/seed/ts-sdk/grpc/src/serialization/resources/user/types/CreateUserResponse.ts @@ -0,0 +1,21 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as SeedApi from "../../../../api/index"; +import * as core from "../../../../core"; +import { User } from "./User"; + +export const CreateUserResponse: core.serialization.ObjectSchema< + serializers.CreateUserResponse.Raw, + SeedApi.CreateUserResponse +> = core.serialization.object({ + user: User, +}); + +export declare namespace CreateUserResponse { + interface Raw { + user: User.Raw; + } +} diff --git a/seed/ts-sdk/grpc/src/serialization/resources/user/types/Metadata.ts b/seed/ts-sdk/grpc/src/serialization/resources/user/types/Metadata.ts new file mode 100644 index 00000000000..dbf02618349 --- /dev/null +++ b/seed/ts-sdk/grpc/src/serialization/resources/user/types/Metadata.ts @@ -0,0 +1,17 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as SeedApi from "../../../../api/index"; +import * as core from "../../../../core"; + +export const Metadata: core.serialization.Schema = + core.serialization.record( + core.serialization.string(), + core.serialization.lazy(() => serializers.MetadataValue).optional() + ); + +export declare namespace Metadata { + type Raw = Record; +} diff --git a/seed/ts-sdk/grpc/src/serialization/resources/user/types/MetadataValue.ts b/seed/ts-sdk/grpc/src/serialization/resources/user/types/MetadataValue.ts new file mode 100644 index 00000000000..4ff84e74aa7 --- /dev/null +++ b/seed/ts-sdk/grpc/src/serialization/resources/user/types/MetadataValue.ts @@ -0,0 +1,19 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as SeedApi from "../../../../api/index"; +import * as core from "../../../../core"; + +export const MetadataValue: core.serialization.Schema = + core.serialization.undiscriminatedUnion([ + core.serialization.number(), + core.serialization.string(), + core.serialization.boolean(), + core.serialization.list(core.serialization.lazy(() => serializers.MetadataValue)), + ]); + +export declare namespace MetadataValue { + type Raw = number | string | boolean | serializers.MetadataValue.Raw[]; +} diff --git a/seed/ts-sdk/grpc/src/serialization/resources/user/types/User.ts b/seed/ts-sdk/grpc/src/serialization/resources/user/types/User.ts new file mode 100644 index 00000000000..3b7bf25aefc --- /dev/null +++ b/seed/ts-sdk/grpc/src/serialization/resources/user/types/User.ts @@ -0,0 +1,28 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as serializers from "../../../index"; +import * as SeedApi from "../../../../api/index"; +import * as core from "../../../../core"; +import { Metadata } from "./Metadata"; + +export const User: core.serialization.ObjectSchema = core.serialization.object({ + id: core.serialization.string(), + username: core.serialization.string(), + email: core.serialization.string().optional(), + age: core.serialization.number().optional(), + weight: core.serialization.number().optional(), + metadata: Metadata.optional(), +}); + +export declare namespace User { + interface Raw { + id: string; + username: string; + email?: string | null; + age?: number | null; + weight?: number | null; + metadata?: Metadata.Raw | null; + } +} diff --git a/seed/ts-sdk/grpc/src/serialization/resources/user/types/index.ts b/seed/ts-sdk/grpc/src/serialization/resources/user/types/index.ts new file mode 100644 index 00000000000..aff31139192 --- /dev/null +++ b/seed/ts-sdk/grpc/src/serialization/resources/user/types/index.ts @@ -0,0 +1,4 @@ +export * from "./Metadata"; +export * from "./MetadataValue"; +export * from "./User"; +export * from "./CreateUserResponse"; diff --git a/seed/ts-sdk/grpc/tests/custom.test.ts b/seed/ts-sdk/grpc/tests/custom.test.ts new file mode 100644 index 00000000000..7f5e031c839 --- /dev/null +++ b/seed/ts-sdk/grpc/tests/custom.test.ts @@ -0,0 +1,13 @@ +/** + * This is a custom test file, if you wish to add more tests + * to your SDK. + * Be sure to mark this file in `.fernignore`. + * + * If you include example requests/responses in your fern definition, + * you will have tests automatically generated for you. + */ +describe("test", () => { + it("default", () => { + expect(true).toBe(true); + }); +}); diff --git a/seed/ts-sdk/grpc/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/grpc/tests/unit/fetcher/Fetcher.test.ts new file mode 100644 index 00000000000..0e14a8c77f8 --- /dev/null +++ b/seed/ts-sdk/grpc/tests/unit/fetcher/Fetcher.test.ts @@ -0,0 +1,25 @@ +import fetchMock from "fetch-mock-jest"; +import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; + +describe("Test fetcherImpl", () => { + it("should handle successful request", async () => { + const mockArgs: Fetcher.Args = { + url: "https://httpbin.org/post", + method: "POST", + headers: { "X-Test": "x-test-header" }, + body: { data: "test" }, + contentType: "application/json", + requestType: "json", + }; + + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); + + const result = await fetcherImpl(mockArgs); + expect(result.ok).toBe(true); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } + }); +}); diff --git a/seed/ts-sdk/grpc/tests/unit/fetcher/createRequestUrl.test.ts b/seed/ts-sdk/grpc/tests/unit/fetcher/createRequestUrl.test.ts new file mode 100644 index 00000000000..f2cd24b6721 --- /dev/null +++ b/seed/ts-sdk/grpc/tests/unit/fetcher/createRequestUrl.test.ts @@ -0,0 +1,51 @@ +import { createRequestUrl } from "../../../src/core/fetcher/createRequestUrl"; + +describe("Test createRequestUrl", () => { + it("should return the base URL when no query parameters are provided", () => { + const baseUrl = "https://api.example.com"; + expect(createRequestUrl(baseUrl)).toBe(baseUrl); + }); + + it("should append simple query parameters", () => { + const baseUrl = "https://api.example.com"; + const queryParams = { key: "value", another: "param" }; + expect(createRequestUrl(baseUrl, queryParams)).toBe("https://api.example.com?key=value&another=param"); + }); + + it("should handle array query parameters", () => { + const baseUrl = "https://api.example.com"; + const queryParams = { items: ["a", "b", "c"] }; + expect(createRequestUrl(baseUrl, queryParams)).toBe("https://api.example.com?items=a&items=b&items=c"); + }); + + it("should handle object query parameters", () => { + const baseUrl = "https://api.example.com"; + const queryParams = { filter: { name: "John", age: 30 } }; + expect(createRequestUrl(baseUrl, queryParams)).toBe( + "https://api.example.com?filter%5Bname%5D=John&filter%5Bage%5D=30" + ); + }); + + it("should handle mixed types of query parameters", () => { + const baseUrl = "https://api.example.com"; + const queryParams = { + simple: "value", + array: ["x", "y"], + object: { key: "value" }, + }; + expect(createRequestUrl(baseUrl, queryParams)).toBe( + "https://api.example.com?simple=value&array=x&array=y&object%5Bkey%5D=value" + ); + }); + + it("should handle empty query parameters object", () => { + const baseUrl = "https://api.example.com"; + expect(createRequestUrl(baseUrl, {})).toBe(baseUrl); + }); + + it("should encode special characters in query parameters", () => { + const baseUrl = "https://api.example.com"; + const queryParams = { special: "a&b=c d" }; + expect(createRequestUrl(baseUrl, queryParams)).toBe("https://api.example.com?special=a%26b%3Dc%20d"); + }); +}); diff --git a/seed/ts-sdk/grpc/tests/unit/fetcher/getFetchFn.test.ts b/seed/ts-sdk/grpc/tests/unit/fetcher/getFetchFn.test.ts new file mode 100644 index 00000000000..9b315ad095a --- /dev/null +++ b/seed/ts-sdk/grpc/tests/unit/fetcher/getFetchFn.test.ts @@ -0,0 +1,22 @@ +import { RUNTIME } from "../../../src/core/runtime"; +import { getFetchFn } from "../../../src/core/fetcher/getFetchFn"; + +describe("Test for getFetchFn", () => { + it("should get node-fetch function", async () => { + if (RUNTIME.type == "node") { + if (RUNTIME.parsedVersion != null && RUNTIME.parsedVersion >= 18) { + expect(await getFetchFn()).toBe(fetch); + } else { + expect(await getFetchFn()).toEqual((await import("node-fetch")).default as any); + } + } + }); + + it("should get fetch function", async () => { + if (RUNTIME.type == "browser") { + const fetchFn = await getFetchFn(); + expect(typeof fetchFn).toBe("function"); + expect(fetchFn.name).toBe("fetch"); + } + }); +}); diff --git a/seed/ts-sdk/grpc/tests/unit/fetcher/getRequestBody.test.ts b/seed/ts-sdk/grpc/tests/unit/fetcher/getRequestBody.test.ts new file mode 100644 index 00000000000..1b1462c51bd --- /dev/null +++ b/seed/ts-sdk/grpc/tests/unit/fetcher/getRequestBody.test.ts @@ -0,0 +1,81 @@ +import { RUNTIME } from "../../../src/core/runtime"; +import { getRequestBody } from "../../../src/core/fetcher/getRequestBody"; + +if (RUNTIME.type === "browser") { + require("jest-fetch-mock").enableMocks(); +} + +describe("Test getRequestBody", () => { + it("should return FormData as is in Node environment", async () => { + if (RUNTIME.type === "node") { + const formData = new (await import("formdata-node")).FormData(); + formData.append("key", "value"); + const result = await getRequestBody({ + body: formData, + type: "file", + }); + expect(result).toBe(formData); + } + }); + + it("should stringify body if not FormData in Node environment", async () => { + if (RUNTIME.type === "node") { + const body = { key: "value" }; + const result = await getRequestBody({ + body, + type: "json", + }); + expect(result).toBe('{"key":"value"}'); + } + }); + + it("should return FormData in browser environment", async () => { + if (RUNTIME.type === "browser") { + const formData = new (await import("form-data")).default(); + formData.append("key", "value"); + const result = await getRequestBody({ + body: formData, + type: "file", + }); + expect(result).toBe(formData); + } + }); + + it("should stringify body if not FormData in browser environment", async () => { + if (RUNTIME.type === "browser") { + const body = { key: "value" }; + const result = await getRequestBody({ + body, + type: "json", + }); + expect(result).toBe('{"key":"value"}'); + } + }); + + it("should return the Uint8Array", async () => { + const input = new Uint8Array([1, 2, 3]); + const result = await getRequestBody({ + body: input, + type: "bytes", + }); + expect(result).toBe(input); + }); + + it("should return the input for content-type 'application/x-www-form-urlencoded'", async () => { + const input = "key=value&another=param"; + const result = await getRequestBody({ + body: input, + type: "other", + }); + expect(result).toBe(input); + }); + + it("should JSON stringify objects", async () => { + const input = { key: "value" }; + const result = await getRequestBody({ + body: input, + type: "json", + }); + expect(result).toBe('{"key":"value"}'); + }); +}); diff --git a/seed/ts-sdk/grpc/tests/unit/fetcher/getResponseBody.test.ts b/seed/ts-sdk/grpc/tests/unit/fetcher/getResponseBody.test.ts new file mode 100644 index 00000000000..3510779e3f9 --- /dev/null +++ b/seed/ts-sdk/grpc/tests/unit/fetcher/getResponseBody.test.ts @@ -0,0 +1,68 @@ +import { RUNTIME } from "../../../src/core/runtime"; +import { getResponseBody } from "../../../src/core/fetcher/getResponseBody"; +import { chooseStreamWrapper } from "../../../src/core/fetcher/stream-wrappers/chooseStreamWrapper"; + +if (RUNTIME.type === "browser") { + require("jest-fetch-mock").enableMocks(); +} + +describe("Test getResponseBody", () => { + it("should handle blob response type", async () => { + const mockBlob = new Blob(["test"], { type: "text/plain" }); + const mockResponse = new Response(mockBlob); + const result = await getResponseBody(mockResponse, "blob"); + // @ts-expect-error + expect(result.constructor.name).toBe("Blob"); + }); + + it("should handle sse response type", async () => { + if (RUNTIME.type === "node") { + const mockStream = new ReadableStream(); + const mockResponse = new Response(mockStream); + const result = await getResponseBody(mockResponse, "sse"); + expect(result).toBe(mockStream); + } + }); + + it("should handle streaming response type", async () => { + if (RUNTIME.type === "node") { + const mockStream = new ReadableStream(); + const mockResponse = new Response(mockStream); + const result = await getResponseBody(mockResponse, "streaming"); + // need to reinstantiate string as a result of locked state in Readable Stream after registration with Response + expect(JSON.stringify(result)).toBe(JSON.stringify(await chooseStreamWrapper(new ReadableStream()))); + } + }); + + it("should handle text response type", async () => { + const mockResponse = new Response("test text"); + const result = await getResponseBody(mockResponse, "text"); + expect(result).toBe("test text"); + }); + + it("should handle JSON response", async () => { + const mockJson = { key: "value" }; + const mockResponse = new Response(JSON.stringify(mockJson)); + const result = await getResponseBody(mockResponse); + expect(result).toEqual(mockJson); + }); + + it("should handle empty response", async () => { + const mockResponse = new Response(""); + const result = await getResponseBody(mockResponse); + expect(result).toBeUndefined(); + }); + + it("should handle non-JSON response", async () => { + const mockResponse = new Response("invalid json"); + const result = await getResponseBody(mockResponse); + expect(result).toEqual({ + ok: false, + error: { + reason: "non-json", + statusCode: 200, + rawBody: "invalid json", + }, + }); + }); +}); diff --git a/seed/ts-sdk/grpc/tests/unit/fetcher/makeRequest.test.ts b/seed/ts-sdk/grpc/tests/unit/fetcher/makeRequest.test.ts new file mode 100644 index 00000000000..5969d5155ac --- /dev/null +++ b/seed/ts-sdk/grpc/tests/unit/fetcher/makeRequest.test.ts @@ -0,0 +1,58 @@ +import { RUNTIME } from "../../../src/core/runtime"; +import { makeRequest } from "../../../src/core/fetcher/makeRequest"; + +if (RUNTIME.type === "browser") { + require("jest-fetch-mock").enableMocks(); +} + +describe("Test makeRequest", () => { + const mockPostUrl = "https://httpbin.org/post"; + const mockGetUrl = "https://httpbin.org/get"; + const mockHeaders = { "Content-Type": "application/json" }; + const mockBody = JSON.stringify({ key: "value" }); + + let mockFetch: jest.Mock; + + beforeEach(() => { + mockFetch = jest.fn(); + mockFetch.mockResolvedValue(new Response(JSON.stringify({ test: "successful" }), { status: 200 })); + }); + + it("should handle POST request correctly", async () => { + const response = await makeRequest(mockFetch, mockPostUrl, "POST", mockHeaders, mockBody); + const responseBody = await response.json(); + expect(responseBody).toEqual({ test: "successful" }); + expect(mockFetch).toHaveBeenCalledTimes(1); + const [calledUrl, calledOptions] = mockFetch.mock.calls[0]; + expect(calledUrl).toBe(mockPostUrl); + expect(calledOptions).toEqual( + expect.objectContaining({ + method: "POST", + headers: mockHeaders, + body: mockBody, + credentials: undefined, + }) + ); + expect(calledOptions.signal).toBeDefined(); + expect(calledOptions.signal).toBeInstanceOf(AbortSignal); + }); + + it("should handle GET request correctly", async () => { + const response = await makeRequest(mockFetch, mockGetUrl, "GET", mockHeaders, undefined); + const responseBody = await response.json(); + expect(responseBody).toEqual({ test: "successful" }); + expect(mockFetch).toHaveBeenCalledTimes(1); + const [calledUrl, calledOptions] = mockFetch.mock.calls[0]; + expect(calledUrl).toBe(mockGetUrl); + expect(calledOptions).toEqual( + expect.objectContaining({ + method: "GET", + headers: mockHeaders, + body: undefined, + credentials: undefined, + }) + ); + expect(calledOptions.signal).toBeDefined(); + expect(calledOptions.signal).toBeInstanceOf(AbortSignal); + }); +}); diff --git a/seed/ts-sdk/grpc/tests/unit/fetcher/requestWithRetries.test.ts b/seed/ts-sdk/grpc/tests/unit/fetcher/requestWithRetries.test.ts new file mode 100644 index 00000000000..b53e04367c5 --- /dev/null +++ b/seed/ts-sdk/grpc/tests/unit/fetcher/requestWithRetries.test.ts @@ -0,0 +1,85 @@ +import { RUNTIME } from "../../../src/core/runtime"; +import { requestWithRetries } from "../../../src/core/fetcher/requestWithRetries"; + +if (RUNTIME.type === "browser") { + require("jest-fetch-mock").enableMocks(); +} + +describe("Test exponential backoff", () => { + let mockFetch: jest.Mock; + let originalSetTimeout: typeof setTimeout; + + beforeEach(() => { + mockFetch = jest.fn(); + originalSetTimeout = global.setTimeout; + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + global.setTimeout = originalSetTimeout; + }); + + it("should retry on 408, 409, 429, 500+", async () => { + mockFetch + .mockResolvedValueOnce(new Response("", { status: 408 })) + .mockResolvedValueOnce(new Response("", { status: 409 })) + .mockResolvedValueOnce(new Response("", { status: 429 })) + .mockResolvedValueOnce(new Response("", { status: 500 })) + .mockResolvedValueOnce(new Response("", { status: 502 })) + .mockResolvedValueOnce(new Response("", { status: 200 })) + .mockResolvedValueOnce(new Response("", { status: 408 })); + + const responsePromise = requestWithRetries(() => mockFetch(), 10); + + await jest.advanceTimersByTimeAsync(10000); + const response = await responsePromise; + + expect(mockFetch).toHaveBeenCalledTimes(6); + expect(response.status).toBe(200); + }); + + it("should retry max 3 times", async () => { + mockFetch + .mockResolvedValueOnce(new Response("", { status: 408 })) + .mockResolvedValueOnce(new Response("", { status: 409 })) + .mockResolvedValueOnce(new Response("", { status: 429 })) + .mockResolvedValueOnce(new Response("", { status: 429 })); + + const responsePromise = requestWithRetries(() => mockFetch(), 3); + + await jest.advanceTimersByTimeAsync(10000); + const response = await responsePromise; + + expect(mockFetch).toHaveBeenCalledTimes(4); + expect(response.status).toBe(429); + }); + it("should not retry on 200", async () => { + mockFetch + .mockResolvedValueOnce(new Response("", { status: 200 })) + .mockResolvedValueOnce(new Response("", { status: 409 })); + + const responsePromise = requestWithRetries(() => mockFetch(), 3); + + await jest.advanceTimersByTimeAsync(10000); + const response = await responsePromise; + + expect(mockFetch).toHaveBeenCalledTimes(1); + expect(response.status).toBe(200); + }); + + it("should retry with exponential backoff timing", async () => { + mockFetch.mockResolvedValue(new Response("", { status: 500 })); + const maxRetries = 7; + const responsePromise = requestWithRetries(() => mockFetch(), maxRetries); + expect(mockFetch).toHaveBeenCalledTimes(1); + + const delays = [1, 2, 4, 8, 16, 32, 64]; + for (let i = 0; i < delays.length; i++) { + await jest.advanceTimersByTimeAsync(delays[i] as number); + expect(mockFetch).toHaveBeenCalledTimes(Math.min(i + 2, maxRetries + 1)); + } + const response = await responsePromise; + expect(response.status).toBe(500); + }); +}); diff --git a/seed/ts-sdk/grpc/tests/unit/fetcher/signals.test.ts b/seed/ts-sdk/grpc/tests/unit/fetcher/signals.test.ts new file mode 100644 index 00000000000..9cabfa07447 --- /dev/null +++ b/seed/ts-sdk/grpc/tests/unit/fetcher/signals.test.ts @@ -0,0 +1,69 @@ +import { anySignal, getTimeoutSignal } from "../../../src/core/fetcher/signals"; + +describe("Test getTimeoutSignal", () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it("should return an object with signal and abortId", () => { + const { signal, abortId } = getTimeoutSignal(1000); + + expect(signal).toBeDefined(); + expect(abortId).toBeDefined(); + expect(signal).toBeInstanceOf(AbortSignal); + expect(signal.aborted).toBe(false); + }); + + it("should create a signal that aborts after the specified timeout", () => { + const timeoutMs = 5000; + const { signal } = getTimeoutSignal(timeoutMs); + + expect(signal.aborted).toBe(false); + + jest.advanceTimersByTime(timeoutMs - 1); + expect(signal.aborted).toBe(false); + + jest.advanceTimersByTime(1); + expect(signal.aborted).toBe(true); + }); +}); + +describe("Test anySignal", () => { + it("should return an AbortSignal", () => { + const signal = anySignal(new AbortController().signal); + expect(signal).toBeInstanceOf(AbortSignal); + }); + + it("should abort when any of the input signals is aborted", () => { + const controller1 = new AbortController(); + const controller2 = new AbortController(); + const signal = anySignal(controller1.signal, controller2.signal); + + expect(signal.aborted).toBe(false); + controller1.abort(); + expect(signal.aborted).toBe(true); + }); + + it("should handle an array of signals", () => { + const controller1 = new AbortController(); + const controller2 = new AbortController(); + const signal = anySignal([controller1.signal, controller2.signal]); + + expect(signal.aborted).toBe(false); + controller2.abort(); + expect(signal.aborted).toBe(true); + }); + + it("should abort immediately if one of the input signals is already aborted", () => { + const controller1 = new AbortController(); + const controller2 = new AbortController(); + controller1.abort(); + + const signal = anySignal(controller1.signal, controller2.signal); + expect(signal.aborted).toBe(true); + }); +}); diff --git a/seed/ts-sdk/grpc/tests/unit/fetcher/stream-wrappers/Node18UniversalStreamWrapper.test.ts b/seed/ts-sdk/grpc/tests/unit/fetcher/stream-wrappers/Node18UniversalStreamWrapper.test.ts new file mode 100644 index 00000000000..e307b1589a7 --- /dev/null +++ b/seed/ts-sdk/grpc/tests/unit/fetcher/stream-wrappers/Node18UniversalStreamWrapper.test.ts @@ -0,0 +1,178 @@ +import { Node18UniversalStreamWrapper } from "../../../../src/core/fetcher/stream-wrappers/Node18UniversalStreamWrapper"; + +describe("Node18UniversalStreamWrapper", () => { + it("should set encoding to utf-8", async () => { + const rawStream = new ReadableStream(); + const stream = new Node18UniversalStreamWrapper(rawStream); + const setEncodingSpy = jest.spyOn(stream, "setEncoding"); + + stream.setEncoding("utf-8"); + + expect(setEncodingSpy).toHaveBeenCalledWith("utf-8"); + }); + + it("should register an event listener for readable", async () => { + const rawStream = new ReadableStream(); + const stream = new Node18UniversalStreamWrapper(rawStream); + const onSpy = jest.spyOn(stream, "on"); + + stream.on("readable", () => {}); + + expect(onSpy).toHaveBeenCalledWith("readable", expect.any(Function)); + }); + + it("should remove an event listener for data", async () => { + const rawStream = new ReadableStream(); + const stream = new Node18UniversalStreamWrapper(rawStream); + const offSpy = jest.spyOn(stream, "off"); + + const fn = () => {}; + stream.on("data", fn); + stream.off("data", fn); + + expect(offSpy).toHaveBeenCalledWith("data", expect.any(Function)); + }); + + it("should write to dest when calling pipe to writable stream", async () => { + const rawStream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode("test")); + controller.enqueue(new TextEncoder().encode("test")); + controller.close(); + }, + }); + const stream = new Node18UniversalStreamWrapper(rawStream); + const dest = new WritableStream({ + write(chunk) { + expect(chunk).toEqual(new TextEncoder().encode("test")); + }, + }); + + stream.pipe(dest); + }); + + it("should write to dest when calling pipe to node writable stream", async () => { + const rawStream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode("test")); + controller.enqueue(new TextEncoder().encode("test")); + controller.close(); + }, + }); + const stream = new Node18UniversalStreamWrapper(rawStream); + const dest = new (await import("stream")).Writable({ + write(chunk, encoding, callback) { + expect(chunk.toString()).toEqual("test"); + callback(); + }, + }); + + stream.pipe(dest); + }); + + it("should write nothing when calling pipe and unpipe", async () => { + const rawStream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode("test")); + controller.enqueue(new TextEncoder().encode("test")); + controller.close(); + }, + }); + const stream = new Node18UniversalStreamWrapper(rawStream); + const buffer: Uint8Array[] = []; + const dest = new WritableStream({ + write(chunk) { + buffer.push(chunk); + }, + }); + + stream.pipe(dest); + stream.unpipe(dest); + expect(buffer).toEqual([]); + }); + + it("should destroy the stream", async () => { + const rawStream = new ReadableStream(); + const stream = new Node18UniversalStreamWrapper(rawStream); + const destroySpy = jest.spyOn(stream, "destroy"); + + stream.destroy(); + + expect(destroySpy).toHaveBeenCalled(); + }); + + it("should pause and resume the stream", async () => { + const rawStream = new ReadableStream(); + const stream = new Node18UniversalStreamWrapper(rawStream); + const pauseSpy = jest.spyOn(stream, "pause"); + const resumeSpy = jest.spyOn(stream, "resume"); + + expect(stream.isPaused).toBe(false); + stream.pause(); + expect(stream.isPaused).toBe(true); + stream.resume(); + + expect(pauseSpy).toHaveBeenCalled(); + expect(resumeSpy).toHaveBeenCalled(); + }); + + it("should read the stream", async () => { + const rawStream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode("test")); + controller.enqueue(new TextEncoder().encode("test")); + controller.close(); + }, + }); + const stream = new Node18UniversalStreamWrapper(rawStream); + + expect(await stream.read()).toEqual(new TextEncoder().encode("test")); + expect(await stream.read()).toEqual(new TextEncoder().encode("test")); + }); + + it("should read the stream as text", async () => { + const rawStream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode("test")); + controller.enqueue(new TextEncoder().encode("test")); + controller.close(); + }, + }); + const stream = new Node18UniversalStreamWrapper(rawStream); + + const data = await stream.text(); + + expect(data).toEqual("testtest"); + }); + + it("should read the stream as json", async () => { + const rawStream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode(JSON.stringify({ test: "test" }))); + controller.close(); + }, + }); + const stream = new Node18UniversalStreamWrapper(rawStream); + + const data = await stream.json(); + + expect(data).toEqual({ test: "test" }); + }); + + it("should allow use with async iteratable stream", async () => { + const rawStream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode("test")); + controller.enqueue(new TextEncoder().encode("test")); + controller.close(); + }, + }); + let data = ""; + const stream = new Node18UniversalStreamWrapper(rawStream); + for await (const chunk of stream) { + data += new TextDecoder().decode(chunk); + } + + expect(data).toEqual("testtest"); + }); +}); diff --git a/seed/ts-sdk/grpc/tests/unit/fetcher/stream-wrappers/NodePre18StreamWrapper.test.ts b/seed/ts-sdk/grpc/tests/unit/fetcher/stream-wrappers/NodePre18StreamWrapper.test.ts new file mode 100644 index 00000000000..861142a08b0 --- /dev/null +++ b/seed/ts-sdk/grpc/tests/unit/fetcher/stream-wrappers/NodePre18StreamWrapper.test.ts @@ -0,0 +1,124 @@ +import { NodePre18StreamWrapper } from "../../../../src/core/fetcher/stream-wrappers/NodePre18StreamWrapper"; + +describe("NodePre18StreamWrapper", () => { + it("should set encoding to utf-8", async () => { + const rawStream = (await import("stream")).Readable.from(["test", "test"]); + const stream = new NodePre18StreamWrapper(rawStream); + const setEncodingSpy = jest.spyOn(stream, "setEncoding"); + + stream.setEncoding("utf-8"); + + expect(setEncodingSpy).toHaveBeenCalledWith("utf-8"); + }); + + it("should register an event listener for readable", async () => { + const rawStream = (await import("stream")).Readable.from(["test", "test"]); + const stream = new NodePre18StreamWrapper(rawStream); + const onSpy = jest.spyOn(stream, "on"); + + stream.on("readable", () => {}); + + expect(onSpy).toHaveBeenCalledWith("readable", expect.any(Function)); + }); + + it("should remove an event listener for data", async () => { + const rawStream = (await import("stream")).Readable.from(["test", "test"]); + const stream = new NodePre18StreamWrapper(rawStream); + const offSpy = jest.spyOn(stream, "off"); + + const fn = () => {}; + stream.on("data", fn); + stream.off("data", fn); + + expect(offSpy).toHaveBeenCalledWith("data", expect.any(Function)); + }); + + it("should write to dest when calling pipe to node writable stream", async () => { + const rawStream = (await import("stream")).Readable.from(["test", "test"]); + const stream = new NodePre18StreamWrapper(rawStream); + const dest = new (await import("stream")).Writable({ + write(chunk, encoding, callback) { + expect(chunk.toString()).toEqual("test"); + callback(); + }, + }); + + stream.pipe(dest); + }); + + it("should write nothing when calling pipe and unpipe", async () => { + const rawStream = (await import("stream")).Readable.from(["test", "test"]); + const stream = new NodePre18StreamWrapper(rawStream); + const buffer: Uint8Array[] = []; + const dest = new (await import("stream")).Writable({ + write(chunk, encoding, callback) { + buffer.push(chunk); + callback(); + }, + }); + stream.pipe(dest); + stream.unpipe(); + + expect(buffer).toEqual([]); + }); + + it("should destroy the stream", async () => { + const rawStream = (await import("stream")).Readable.from(["test", "test"]); + const stream = new NodePre18StreamWrapper(rawStream); + const destroySpy = jest.spyOn(stream, "destroy"); + + stream.destroy(); + + expect(destroySpy).toHaveBeenCalledWith(); + }); + + it("should pause the stream and resume", async () => { + const rawStream = (await import("stream")).Readable.from(["test", "test"]); + const stream = new NodePre18StreamWrapper(rawStream); + const pauseSpy = jest.spyOn(stream, "pause"); + + stream.pause(); + expect(stream.isPaused).toBe(true); + stream.resume(); + expect(stream.isPaused).toBe(false); + + expect(pauseSpy).toHaveBeenCalledWith(); + }); + + it("should read the stream", async () => { + const rawStream = (await import("stream")).Readable.from(["test", "test"]); + const stream = new NodePre18StreamWrapper(rawStream); + + expect(await stream.read()).toEqual("test"); + expect(await stream.read()).toEqual("test"); + }); + + it("should read the stream as text", async () => { + const rawStream = (await import("stream")).Readable.from(["test", "test"]); + const stream = new NodePre18StreamWrapper(rawStream); + + const data = await stream.text(); + + expect(data).toEqual("testtest"); + }); + + it("should read the stream as json", async () => { + const rawStream = (await import("stream")).Readable.from([JSON.stringify({ test: "test" })]); + const stream = new NodePre18StreamWrapper(rawStream); + + const data = await stream.json(); + + expect(data).toEqual({ test: "test" }); + }); + + it("should allow use with async iteratable stream", async () => { + const rawStream = (await import("stream")).Readable.from(["test", "test"]); + let data = ""; + const stream = new NodePre18StreamWrapper(rawStream); + for await (const chunk of stream) { + data += chunk; + } + + expect(data).toEqual("testtest"); + }); +}); diff --git a/seed/ts-sdk/grpc/tests/unit/fetcher/stream-wrappers/UndiciStreamWrapper.test.ts b/seed/ts-sdk/grpc/tests/unit/fetcher/stream-wrappers/UndiciStreamWrapper.test.ts new file mode 100644 index 00000000000..1d171ce6c67 --- /dev/null +++ b/seed/ts-sdk/grpc/tests/unit/fetcher/stream-wrappers/UndiciStreamWrapper.test.ts @@ -0,0 +1,153 @@ +import { UndiciStreamWrapper } from "../../../../src/core/fetcher/stream-wrappers/UndiciStreamWrapper"; + +describe("UndiciStreamWrapper", () => { + it("should set encoding to utf-8", async () => { + const rawStream = new ReadableStream(); + const stream = new UndiciStreamWrapper(rawStream); + const setEncodingSpy = jest.spyOn(stream, "setEncoding"); + + stream.setEncoding("utf-8"); + + expect(setEncodingSpy).toHaveBeenCalledWith("utf-8"); + }); + + it("should register an event listener for readable", async () => { + const rawStream = new ReadableStream(); + const stream = new UndiciStreamWrapper(rawStream); + const onSpy = jest.spyOn(stream, "on"); + + stream.on("readable", () => {}); + + expect(onSpy).toHaveBeenCalledWith("readable", expect.any(Function)); + }); + + it("should remove an event listener for data", async () => { + const rawStream = new ReadableStream(); + const stream = new UndiciStreamWrapper(rawStream); + const offSpy = jest.spyOn(stream, "off"); + + const fn = () => {}; + stream.on("data", fn); + stream.off("data", fn); + + expect(offSpy).toHaveBeenCalledWith("data", expect.any(Function)); + }); + + it("should write to dest when calling pipe to writable stream", async () => { + const rawStream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode("test")); + controller.enqueue(new TextEncoder().encode("test")); + controller.close(); + }, + }); + const stream = new UndiciStreamWrapper(rawStream); + const dest = new WritableStream({ + write(chunk) { + expect(chunk).toEqual(new TextEncoder().encode("test")); + }, + }); + + stream.pipe(dest); + }); + + it("should write nothing when calling pipe and unpipe", async () => { + const rawStream = new ReadableStream(); + const stream = new UndiciStreamWrapper(rawStream); + const buffer: Uint8Array[] = []; + const dest = new WritableStream({ + write(chunk) { + buffer.push(chunk); + }, + }); + stream.pipe(dest); + stream.unpipe(dest); + + expect(buffer).toEqual([]); + }); + + it("should destroy the stream", async () => { + const rawStream = new ReadableStream(); + const stream = new UndiciStreamWrapper(rawStream); + const destroySpy = jest.spyOn(stream, "destroy"); + + stream.destroy(); + + expect(destroySpy).toHaveBeenCalled(); + }); + + it("should pause and resume the stream", async () => { + const rawStream = new ReadableStream(); + const stream = new UndiciStreamWrapper(rawStream); + const pauseSpy = jest.spyOn(stream, "pause"); + const resumeSpy = jest.spyOn(stream, "resume"); + + expect(stream.isPaused).toBe(false); + stream.pause(); + expect(stream.isPaused).toBe(true); + stream.resume(); + + expect(pauseSpy).toHaveBeenCalled(); + expect(resumeSpy).toHaveBeenCalled(); + }); + + it("should read the stream", async () => { + const rawStream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode("test")); + controller.enqueue(new TextEncoder().encode("test")); + controller.close(); + }, + }); + const stream = new UndiciStreamWrapper(rawStream); + + expect(await stream.read()).toEqual(new TextEncoder().encode("test")); + expect(await stream.read()).toEqual(new TextEncoder().encode("test")); + }); + + it("should read the stream as text", async () => { + const rawStream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode("test")); + controller.enqueue(new TextEncoder().encode("test")); + controller.close(); + }, + }); + const stream = new UndiciStreamWrapper(rawStream); + + const data = await stream.text(); + + expect(data).toEqual("testtest"); + }); + + it("should read the stream as json", async () => { + const rawStream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode(JSON.stringify({ test: "test" }))); + controller.close(); + }, + }); + const stream = new UndiciStreamWrapper(rawStream); + + const data = await stream.json(); + + expect(data).toEqual({ test: "test" }); + }); + + it("should allow use with async iteratable stream", async () => { + const rawStream = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode("test")); + controller.enqueue(new TextEncoder().encode("test")); + controller.close(); + }, + }); + let data = ""; + const stream = new UndiciStreamWrapper(rawStream); + for await (const chunk of stream) { + data += new TextDecoder().decode(chunk); + } + + expect(data).toEqual("testtest"); + }); +}); diff --git a/seed/ts-sdk/grpc/tests/unit/fetcher/stream-wrappers/chooseStreamWrapper.test.ts b/seed/ts-sdk/grpc/tests/unit/fetcher/stream-wrappers/chooseStreamWrapper.test.ts new file mode 100644 index 00000000000..aff7579e47a --- /dev/null +++ b/seed/ts-sdk/grpc/tests/unit/fetcher/stream-wrappers/chooseStreamWrapper.test.ts @@ -0,0 +1,43 @@ +import { RUNTIME } from "../../../../src/core/runtime"; +import { chooseStreamWrapper } from "../../../../src/core/fetcher/stream-wrappers/chooseStreamWrapper"; +import { Node18UniversalStreamWrapper } from "../../../../src/core/fetcher/stream-wrappers/Node18UniversalStreamWrapper"; +import { NodePre18StreamWrapper } from "../../../../src/core/fetcher/stream-wrappers/NodePre18StreamWrapper"; +import { UndiciStreamWrapper } from "../../../../src/core/fetcher/stream-wrappers/UndiciStreamWrapper"; + +describe("chooseStreamWrapper", () => { + beforeEach(() => { + RUNTIME.type = "unknown"; + RUNTIME.parsedVersion = 0; + }); + + it('should return a Node18UniversalStreamWrapper when RUNTIME.type is "node" and RUNTIME.parsedVersion is not null and RUNTIME.parsedVersion is greater than or equal to 18', async () => { + const expected = new Node18UniversalStreamWrapper(new ReadableStream()); + RUNTIME.type = "node"; + RUNTIME.parsedVersion = 18; + + const result = await chooseStreamWrapper(new ReadableStream()); + + expect(JSON.stringify(result)).toBe(JSON.stringify(expected)); + }); + + it('should return a NodePre18StreamWrapper when RUNTIME.type is "node" and RUNTIME.parsedVersion is not null and RUNTIME.parsedVersion is less than 18', async () => { + const stream = await import("stream"); + const expected = new NodePre18StreamWrapper(new stream.Readable()); + + RUNTIME.type = "node"; + RUNTIME.parsedVersion = 16; + + const result = await chooseStreamWrapper(new stream.Readable()); + + expect(JSON.stringify(result)).toEqual(JSON.stringify(expected)); + }); + + it('should return a Undici when RUNTIME.type is not "node"', async () => { + const expected = new UndiciStreamWrapper(new ReadableStream()); + RUNTIME.type = "browser"; + + const result = await chooseStreamWrapper(new ReadableStream()); + + expect(JSON.stringify(result)).toEqual(JSON.stringify(expected)); + }); +}); diff --git a/seed/ts-sdk/grpc/tests/unit/zurg/date/date.test.ts b/seed/ts-sdk/grpc/tests/unit/zurg/date/date.test.ts new file mode 100644 index 00000000000..2790268a09c --- /dev/null +++ b/seed/ts-sdk/grpc/tests/unit/zurg/date/date.test.ts @@ -0,0 +1,31 @@ +import { date } from "../../../../src/core/schemas/builders/date"; +import { itSchema } from "../utils/itSchema"; +import { itValidateJson, itValidateParse } from "../utils/itValidate"; + +describe("date", () => { + itSchema("converts between raw ISO string and parsed Date", date(), { + raw: "2022-09-29T05:41:21.939Z", + parsed: new Date("2022-09-29T05:41:21.939Z"), + }); + + itValidateParse("non-string", date(), 42, [ + { + message: "Expected string. Received 42.", + path: [], + }, + ]); + + itValidateParse("non-ISO", date(), "hello world", [ + { + message: 'Expected ISO 8601 date string. Received "hello world".', + path: [], + }, + ]); + + itValidateJson("non-Date", date(), "hello", [ + { + message: 'Expected Date object. Received "hello".', + path: [], + }, + ]); +}); diff --git a/seed/ts-sdk/grpc/tests/unit/zurg/enum/enum.test.ts b/seed/ts-sdk/grpc/tests/unit/zurg/enum/enum.test.ts new file mode 100644 index 00000000000..ab0df0285cd --- /dev/null +++ b/seed/ts-sdk/grpc/tests/unit/zurg/enum/enum.test.ts @@ -0,0 +1,30 @@ +import { enum_ } from "../../../../src/core/schemas/builders/enum"; +import { itSchemaIdentity } from "../utils/itSchema"; +import { itValidate } from "../utils/itValidate"; + +describe("enum", () => { + itSchemaIdentity(enum_(["A", "B", "C"]), "A"); + + itSchemaIdentity(enum_(["A", "B", "C"]), "D" as any, { + opts: { allowUnrecognizedEnumValues: true }, + }); + + itValidate("invalid enum", enum_(["A", "B", "C"]), "D", [ + { + message: 'Expected enum. Received "D".', + path: [], + }, + ]); + + itValidate( + "non-string", + enum_(["A", "B", "C"]), + [], + [ + { + message: "Expected string. Received list.", + path: [], + }, + ] + ); +}); diff --git a/seed/ts-sdk/grpc/tests/unit/zurg/lazy/lazy.test.ts b/seed/ts-sdk/grpc/tests/unit/zurg/lazy/lazy.test.ts new file mode 100644 index 00000000000..6906bf4cf91 --- /dev/null +++ b/seed/ts-sdk/grpc/tests/unit/zurg/lazy/lazy.test.ts @@ -0,0 +1,57 @@ +import { Schema } from "../../../../src/core/schemas/Schema"; +import { lazy, list, object, string } from "../../../../src/core/schemas/builders"; +import { itSchemaIdentity } from "../utils/itSchema"; + +describe("lazy", () => { + it("doesn't run immediately", () => { + let wasRun = false; + lazy(() => { + wasRun = true; + return string(); + }); + expect(wasRun).toBe(false); + }); + + it("only runs first time", async () => { + let count = 0; + const schema = lazy(() => { + count++; + return string(); + }); + await schema.parse("hello"); + await schema.json("world"); + expect(count).toBe(1); + }); + + itSchemaIdentity( + lazy(() => object({})), + { foo: "hello" }, + { + title: "passes opts through", + opts: { unrecognizedObjectKeys: "passthrough" }, + } + ); + + itSchemaIdentity( + lazy(() => object({ foo: string() })), + { foo: "hello" } + ); + + // eslint-disable-next-line jest/expect-expect + it("self-referencial schema doesn't compile", () => { + () => { + // @ts-expect-error + const a = lazy(() => object({ foo: a })); + }; + }); + + // eslint-disable-next-line jest/expect-expect + it("self-referencial compiles with explicit type", () => { + () => { + interface TreeNode { + children: TreeNode[]; + } + const TreeNode: Schema = lazy(() => object({ children: list(TreeNode) })); + }; + }); +}); diff --git a/seed/ts-sdk/grpc/tests/unit/zurg/lazy/lazyObject.test.ts b/seed/ts-sdk/grpc/tests/unit/zurg/lazy/lazyObject.test.ts new file mode 100644 index 00000000000..8813cc9fbb4 --- /dev/null +++ b/seed/ts-sdk/grpc/tests/unit/zurg/lazy/lazyObject.test.ts @@ -0,0 +1,18 @@ +import { lazyObject, number, object, string } from "../../../../src/core/schemas/builders"; +import { itSchemaIdentity } from "../utils/itSchema"; + +describe("lazy", () => { + itSchemaIdentity( + lazyObject(() => object({ foo: string() })), + { foo: "hello" } + ); + + itSchemaIdentity( + lazyObject(() => object({ foo: string() })).extend(object({ bar: number() })), + { + foo: "hello", + bar: 42, + }, + { title: "returned schema has object utils" } + ); +}); diff --git a/seed/ts-sdk/grpc/tests/unit/zurg/lazy/recursive/a.ts b/seed/ts-sdk/grpc/tests/unit/zurg/lazy/recursive/a.ts new file mode 100644 index 00000000000..8b7d5e40cfa --- /dev/null +++ b/seed/ts-sdk/grpc/tests/unit/zurg/lazy/recursive/a.ts @@ -0,0 +1,7 @@ +import { object } from "../../../../../src/core/schemas/builders/object"; +import { schemaB } from "./b"; + +// @ts-expect-error +export const schemaA = object({ + b: schemaB, +}); diff --git a/seed/ts-sdk/grpc/tests/unit/zurg/lazy/recursive/b.ts b/seed/ts-sdk/grpc/tests/unit/zurg/lazy/recursive/b.ts new file mode 100644 index 00000000000..fb219d54c8e --- /dev/null +++ b/seed/ts-sdk/grpc/tests/unit/zurg/lazy/recursive/b.ts @@ -0,0 +1,8 @@ +import { object } from "../../../../../src/core/schemas/builders/object"; +import { optional } from "../../../../../src/core/schemas/builders/schema-utils"; +import { schemaA } from "./a"; + +// @ts-expect-error +export const schemaB = object({ + a: optional(schemaA), +}); diff --git a/seed/ts-sdk/grpc/tests/unit/zurg/list/list.test.ts b/seed/ts-sdk/grpc/tests/unit/zurg/list/list.test.ts new file mode 100644 index 00000000000..424ed642db2 --- /dev/null +++ b/seed/ts-sdk/grpc/tests/unit/zurg/list/list.test.ts @@ -0,0 +1,41 @@ +import { list, object, property, string } from "../../../../src/core/schemas/builders"; +import { itSchema, itSchemaIdentity } from "../utils/itSchema"; +import { itValidate } from "../utils/itValidate"; + +describe("list", () => { + itSchemaIdentity(list(string()), ["hello", "world"], { + title: "functions as identity when item type is primitive", + }); + + itSchema( + "converts objects correctly", + list( + object({ + helloWorld: property("hello_world", string()), + }) + ), + { + raw: [{ hello_world: "123" }], + parsed: [{ helloWorld: "123" }], + } + ); + + itValidate("not a list", list(string()), 42, [ + { + path: [], + message: "Expected list. Received 42.", + }, + ]); + + itValidate( + "invalid item type", + list(string()), + [42], + [ + { + path: ["[0]"], + message: "Expected string. Received 42.", + }, + ] + ); +}); diff --git a/seed/ts-sdk/grpc/tests/unit/zurg/literals/stringLiteral.test.ts b/seed/ts-sdk/grpc/tests/unit/zurg/literals/stringLiteral.test.ts new file mode 100644 index 00000000000..fa6c88873c6 --- /dev/null +++ b/seed/ts-sdk/grpc/tests/unit/zurg/literals/stringLiteral.test.ts @@ -0,0 +1,21 @@ +import { stringLiteral } from "../../../../src/core/schemas/builders"; +import { itSchemaIdentity } from "../utils/itSchema"; +import { itValidate } from "../utils/itValidate"; + +describe("stringLiteral", () => { + itSchemaIdentity(stringLiteral("A"), "A"); + + itValidate("incorrect string", stringLiteral("A"), "B", [ + { + path: [], + message: 'Expected "A". Received "B".', + }, + ]); + + itValidate("non-string", stringLiteral("A"), 42, [ + { + path: [], + message: 'Expected "A". Received 42.', + }, + ]); +}); diff --git a/seed/ts-sdk/grpc/tests/unit/zurg/object-like/withParsedProperties.test.ts b/seed/ts-sdk/grpc/tests/unit/zurg/object-like/withParsedProperties.test.ts new file mode 100644 index 00000000000..9f5dd0ed39b --- /dev/null +++ b/seed/ts-sdk/grpc/tests/unit/zurg/object-like/withParsedProperties.test.ts @@ -0,0 +1,57 @@ +import { object, property, string, stringLiteral } from "../../../../src/core/schemas/builders"; + +describe("withParsedProperties", () => { + it("Added properties included on parsed object", async () => { + const schema = object({ + foo: property("raw_foo", string()), + bar: stringLiteral("bar"), + }).withParsedProperties({ + printFoo: (parsed) => () => parsed.foo, + printHelloWorld: () => () => "Hello world", + helloWorld: "Hello world", + }); + + const parsed = await schema.parse({ raw_foo: "value of foo", bar: "bar" }); + if (!parsed.ok) { + throw new Error("Failed to parse"); + } + expect(parsed.value.printFoo()).toBe("value of foo"); + expect(parsed.value.printHelloWorld()).toBe("Hello world"); + expect(parsed.value.helloWorld).toBe("Hello world"); + }); + + it("Added property is removed on raw object", async () => { + const schema = object({ + foo: property("raw_foo", string()), + bar: stringLiteral("bar"), + }).withParsedProperties({ + printFoo: (parsed) => () => parsed.foo, + }); + + const original = { raw_foo: "value of foo", bar: "bar" } as const; + const parsed = await schema.parse(original); + if (!parsed.ok) { + throw new Error("Failed to parse()"); + } + + const raw = await schema.json(parsed.value); + + if (!raw.ok) { + throw new Error("Failed to json()"); + } + + expect(raw.value).toEqual(original); + }); + + describe("compile", () => { + // eslint-disable-next-line jest/expect-expect + it("doesn't compile with non-object schema", () => { + () => + object({ + foo: string(), + }) + // @ts-expect-error + .withParsedProperties(42); + }); + }); +}); diff --git a/seed/ts-sdk/grpc/tests/unit/zurg/object/extend.test.ts b/seed/ts-sdk/grpc/tests/unit/zurg/object/extend.test.ts new file mode 100644 index 00000000000..54fc8c4ebf8 --- /dev/null +++ b/seed/ts-sdk/grpc/tests/unit/zurg/object/extend.test.ts @@ -0,0 +1,89 @@ +import { boolean, object, property, string, stringLiteral } from "../../../../src/core/schemas/builders"; +import { itSchema, itSchemaIdentity } from "../utils/itSchema"; + +describe("extend", () => { + itSchemaIdentity( + object({ + foo: string(), + }).extend( + object({ + bar: stringLiteral("bar"), + }) + ), + { + foo: "", + bar: "bar", + } as const, + { + title: "extended properties are included in schema", + } + ); + + itSchemaIdentity( + object({ + foo: string(), + }) + .extend( + object({ + bar: stringLiteral("bar"), + }) + ) + .extend( + object({ + baz: boolean(), + }) + ), + { + foo: "", + bar: "bar", + baz: true, + } as const, + { + title: "extensions can be extended", + } + ); + + itSchema( + "converts nested object", + object({ + item: object({ + helloWorld: property("hello_world", string()), + }), + }).extend( + object({ + goodbye: property("goodbye_raw", string()), + }) + ), + { + raw: { item: { hello_world: "yo" }, goodbye_raw: "peace" }, + parsed: { item: { helloWorld: "yo" }, goodbye: "peace" }, + } + ); + + itSchema( + "extensions work with raw/parsed property name conversions", + object({ + item: property("item_raw", string()), + }).extend( + object({ + goodbye: property("goodbye_raw", string()), + }) + ), + { + raw: { item_raw: "hi", goodbye_raw: "peace" }, + parsed: { item: "hi", goodbye: "peace" }, + } + ); + + describe("compile", () => { + // eslint-disable-next-line jest/expect-expect + it("doesn't compile with non-object schema", () => { + () => + object({ + foo: string(), + }) + // @ts-expect-error + .extend([]); + }); + }); +}); diff --git a/seed/ts-sdk/grpc/tests/unit/zurg/object/object.test.ts b/seed/ts-sdk/grpc/tests/unit/zurg/object/object.test.ts new file mode 100644 index 00000000000..0acf0e240f6 --- /dev/null +++ b/seed/ts-sdk/grpc/tests/unit/zurg/object/object.test.ts @@ -0,0 +1,255 @@ +import { any, number, object, property, string, stringLiteral, unknown } from "../../../../src/core/schemas/builders"; +import { itJson, itParse, itSchema, itSchemaIdentity } from "../utils/itSchema"; +import { itValidate } from "../utils/itValidate"; + +describe("object", () => { + itSchemaIdentity( + object({ + foo: string(), + bar: stringLiteral("bar"), + }), + { + foo: "", + bar: "bar", + }, + { + title: "functions as identity when values are primitives and property() isn't used", + } + ); + + itSchema( + "uses raw key from property()", + object({ + foo: property("raw_foo", string()), + bar: stringLiteral("bar"), + }), + { + raw: { raw_foo: "foo", bar: "bar" }, + parsed: { foo: "foo", bar: "bar" }, + } + ); + + itSchema( + "keys with unknown type can be omitted", + object({ + foo: unknown(), + }), + { + raw: {}, + parsed: {}, + } + ); + + itSchema( + "keys with any type can be omitted", + object({ + foo: any(), + }), + { + raw: {}, + parsed: {}, + } + ); + + describe("unrecognizedObjectKeys", () => { + describe("parse", () => { + itParse( + 'includes unknown values when unrecognizedObjectKeys === "passthrough"', + object({ + foo: property("raw_foo", string()), + bar: stringLiteral("bar"), + }), + { + raw: { + raw_foo: "foo", + bar: "bar", + // @ts-expect-error + baz: "yoyo", + }, + parsed: { + foo: "foo", + bar: "bar", + // @ts-expect-error + baz: "yoyo", + }, + opts: { + unrecognizedObjectKeys: "passthrough", + }, + } + ); + + itParse( + 'strips unknown values when unrecognizedObjectKeys === "strip"', + object({ + foo: property("raw_foo", string()), + bar: stringLiteral("bar"), + }), + { + raw: { + raw_foo: "foo", + bar: "bar", + // @ts-expect-error + baz: "yoyo", + }, + parsed: { + foo: "foo", + bar: "bar", + }, + opts: { + unrecognizedObjectKeys: "strip", + }, + } + ); + }); + + describe("json", () => { + itJson( + 'includes unknown values when unrecognizedObjectKeys === "passthrough"', + object({ + foo: property("raw_foo", string()), + bar: stringLiteral("bar"), + }), + { + raw: { + raw_foo: "foo", + bar: "bar", + // @ts-expect-error + baz: "yoyo", + }, + parsed: { + foo: "foo", + bar: "bar", + // @ts-expect-error + baz: "yoyo", + }, + opts: { + unrecognizedObjectKeys: "passthrough", + }, + } + ); + + itJson( + 'strips unknown values when unrecognizedObjectKeys === "strip"', + object({ + foo: property("raw_foo", string()), + bar: stringLiteral("bar"), + }), + { + raw: { + raw_foo: "foo", + bar: "bar", + }, + parsed: { + foo: "foo", + bar: "bar", + // @ts-expect-error + baz: "yoyo", + }, + opts: { + unrecognizedObjectKeys: "strip", + }, + } + ); + }); + }); + + describe("nullish properties", () => { + itSchema("missing properties are not added", object({ foo: property("raw_foo", string().optional()) }), { + raw: {}, + parsed: {}, + }); + + itSchema("undefined properties are not dropped", object({ foo: property("raw_foo", string().optional()) }), { + raw: { raw_foo: null }, + parsed: { foo: undefined }, + }); + + itSchema("null properties are not dropped", object({ foo: property("raw_foo", string().optional()) }), { + raw: { raw_foo: null }, + parsed: { foo: undefined }, + }); + + describe("extensions", () => { + itSchema( + "undefined properties are not dropped", + object({}).extend(object({ foo: property("raw_foo", string().optional()) })), + { + raw: { raw_foo: null }, + parsed: { foo: undefined }, + } + ); + + describe("parse()", () => { + itParse( + "null properties are not dropped", + object({}).extend(object({ foo: property("raw_foo", string().optional()) })), + { + raw: { raw_foo: null }, + parsed: { foo: undefined }, + } + ); + }); + }); + }); + + itValidate( + "missing property", + object({ + foo: string(), + bar: stringLiteral("bar"), + }), + { foo: "hello" }, + [ + { + path: [], + message: 'Missing required key "bar"', + }, + ] + ); + + itValidate( + "extra property", + object({ + foo: string(), + bar: stringLiteral("bar"), + }), + { foo: "hello", bar: "bar", baz: 42 }, + [ + { + path: ["baz"], + message: 'Unexpected key "baz"', + }, + ] + ); + + itValidate( + "not an object", + object({ + foo: string(), + bar: stringLiteral("bar"), + }), + [], + [ + { + path: [], + message: "Expected object. Received list.", + }, + ] + ); + + itValidate( + "nested validation error", + object({ + foo: object({ + bar: number(), + }), + }), + { foo: { bar: "hello" } }, + [ + { + path: ["foo", "bar"], + message: 'Expected number. Received "hello".', + }, + ] + ); +}); diff --git a/seed/ts-sdk/grpc/tests/unit/zurg/object/objectWithoutOptionalProperties.test.ts b/seed/ts-sdk/grpc/tests/unit/zurg/object/objectWithoutOptionalProperties.test.ts new file mode 100644 index 00000000000..d87a65febfd --- /dev/null +++ b/seed/ts-sdk/grpc/tests/unit/zurg/object/objectWithoutOptionalProperties.test.ts @@ -0,0 +1,21 @@ +import { objectWithoutOptionalProperties, string, stringLiteral } from "../../../../src/core/schemas/builders"; +import { itSchema } from "../utils/itSchema"; + +describe("objectWithoutOptionalProperties", () => { + itSchema( + "all properties are required", + objectWithoutOptionalProperties({ + foo: string(), + bar: stringLiteral("bar").optional(), + }), + { + raw: { + foo: "hello", + }, + // @ts-expect-error + parsed: { + foo: "hello", + }, + } + ); +}); diff --git a/seed/ts-sdk/grpc/tests/unit/zurg/primitives/any.test.ts b/seed/ts-sdk/grpc/tests/unit/zurg/primitives/any.test.ts new file mode 100644 index 00000000000..1adbbe2a838 --- /dev/null +++ b/seed/ts-sdk/grpc/tests/unit/zurg/primitives/any.test.ts @@ -0,0 +1,6 @@ +import { any } from "../../../../src/core/schemas/builders"; +import { itSchemaIdentity } from "../utils/itSchema"; + +describe("any", () => { + itSchemaIdentity(any(), true); +}); diff --git a/seed/ts-sdk/grpc/tests/unit/zurg/primitives/boolean.test.ts b/seed/ts-sdk/grpc/tests/unit/zurg/primitives/boolean.test.ts new file mode 100644 index 00000000000..897a8295dca --- /dev/null +++ b/seed/ts-sdk/grpc/tests/unit/zurg/primitives/boolean.test.ts @@ -0,0 +1,14 @@ +import { boolean } from "../../../../src/core/schemas/builders"; +import { itSchemaIdentity } from "../utils/itSchema"; +import { itValidate } from "../utils/itValidate"; + +describe("boolean", () => { + itSchemaIdentity(boolean(), true); + + itValidate("non-boolean", boolean(), {}, [ + { + path: [], + message: "Expected boolean. Received object.", + }, + ]); +}); diff --git a/seed/ts-sdk/grpc/tests/unit/zurg/primitives/number.test.ts b/seed/ts-sdk/grpc/tests/unit/zurg/primitives/number.test.ts new file mode 100644 index 00000000000..2d01415a60b --- /dev/null +++ b/seed/ts-sdk/grpc/tests/unit/zurg/primitives/number.test.ts @@ -0,0 +1,14 @@ +import { number } from "../../../../src/core/schemas/builders"; +import { itSchemaIdentity } from "../utils/itSchema"; +import { itValidate } from "../utils/itValidate"; + +describe("number", () => { + itSchemaIdentity(number(), 42); + + itValidate("non-number", number(), "hello", [ + { + path: [], + message: 'Expected number. Received "hello".', + }, + ]); +}); diff --git a/seed/ts-sdk/grpc/tests/unit/zurg/primitives/string.test.ts b/seed/ts-sdk/grpc/tests/unit/zurg/primitives/string.test.ts new file mode 100644 index 00000000000..57b2368784a --- /dev/null +++ b/seed/ts-sdk/grpc/tests/unit/zurg/primitives/string.test.ts @@ -0,0 +1,14 @@ +import { string } from "../../../../src/core/schemas/builders"; +import { itSchemaIdentity } from "../utils/itSchema"; +import { itValidate } from "../utils/itValidate"; + +describe("string", () => { + itSchemaIdentity(string(), "hello"); + + itValidate("non-string", string(), 42, [ + { + path: [], + message: "Expected string. Received 42.", + }, + ]); +}); diff --git a/seed/ts-sdk/grpc/tests/unit/zurg/primitives/unknown.test.ts b/seed/ts-sdk/grpc/tests/unit/zurg/primitives/unknown.test.ts new file mode 100644 index 00000000000..4d17a7dbd00 --- /dev/null +++ b/seed/ts-sdk/grpc/tests/unit/zurg/primitives/unknown.test.ts @@ -0,0 +1,6 @@ +import { unknown } from "../../../../src/core/schemas/builders"; +import { itSchemaIdentity } from "../utils/itSchema"; + +describe("unknown", () => { + itSchemaIdentity(unknown(), true); +}); diff --git a/seed/ts-sdk/grpc/tests/unit/zurg/record/record.test.ts b/seed/ts-sdk/grpc/tests/unit/zurg/record/record.test.ts new file mode 100644 index 00000000000..7e4ba39cc55 --- /dev/null +++ b/seed/ts-sdk/grpc/tests/unit/zurg/record/record.test.ts @@ -0,0 +1,34 @@ +import { number, record, string } from "../../../../src/core/schemas/builders"; +import { itSchemaIdentity } from "../utils/itSchema"; +import { itValidate } from "../utils/itValidate"; + +describe("record", () => { + itSchemaIdentity(record(string(), string()), { hello: "world" }); + itSchemaIdentity(record(number(), string()), { 42: "world" }); + + itValidate( + "non-record", + record(number(), string()), + [], + [ + { + path: [], + message: "Expected object. Received list.", + }, + ] + ); + + itValidate("invalid key type", record(number(), string()), { hello: "world" }, [ + { + path: ["hello (key)"], + message: 'Expected number. Received "hello".', + }, + ]); + + itValidate("invalid value type", record(string(), number()), { hello: "world" }, [ + { + path: ["hello"], + message: 'Expected number. Received "world".', + }, + ]); +}); diff --git a/seed/ts-sdk/grpc/tests/unit/zurg/schema-utils/getSchemaUtils.test.ts b/seed/ts-sdk/grpc/tests/unit/zurg/schema-utils/getSchemaUtils.test.ts new file mode 100644 index 00000000000..da10086bc1d --- /dev/null +++ b/seed/ts-sdk/grpc/tests/unit/zurg/schema-utils/getSchemaUtils.test.ts @@ -0,0 +1,83 @@ +import { object, string } from "../../../../src/core/schemas/builders"; +import { itSchema } from "../utils/itSchema"; + +describe("getSchemaUtils", () => { + describe("optional()", () => { + itSchema("optional fields allow original schema", string().optional(), { + raw: "hello", + parsed: "hello", + }); + + itSchema("optional fields are not required", string().optional(), { + raw: null, + parsed: undefined, + }); + }); + + describe("transform()", () => { + itSchema( + "transorm and untransform run correctly", + string().transform({ + transform: (x) => x + "X", + untransform: (x) => (x as string).slice(0, -1), + }), + { + raw: "hello", + parsed: "helloX", + } + ); + }); + + describe("parseOrThrow()", () => { + it("parses valid value", async () => { + const value = string().parseOrThrow("hello"); + expect(value).toBe("hello"); + }); + + it("throws on invalid value", async () => { + const value = () => object({ a: string(), b: string() }).parseOrThrow({ a: 24 }); + expect(value).toThrowError(new Error('a: Expected string. Received 24.; Missing required key "b"')); + }); + }); + + describe("jsonOrThrow()", () => { + it("serializes valid value", async () => { + const value = string().jsonOrThrow("hello"); + expect(value).toBe("hello"); + }); + + it("throws on invalid value", async () => { + const value = () => object({ a: string(), b: string() }).jsonOrThrow({ a: 24 }); + expect(value).toThrowError(new Error('a: Expected string. Received 24.; Missing required key "b"')); + }); + }); + + describe("omitUndefined", () => { + it("serializes undefined as null", async () => { + const value = object({ + a: string().optional(), + b: string().optional(), + }).jsonOrThrow({ + a: "hello", + b: undefined, + }); + expect(value).toEqual({ a: "hello", b: null }); + }); + + it("omits undefined values", async () => { + const value = object({ + a: string().optional(), + b: string().optional(), + }).jsonOrThrow( + { + a: "hello", + b: undefined, + }, + { + omitUndefined: true, + } + ); + expect(value).toEqual({ a: "hello" }); + }); + }); +}); diff --git a/seed/ts-sdk/grpc/tests/unit/zurg/schema.test.ts b/seed/ts-sdk/grpc/tests/unit/zurg/schema.test.ts new file mode 100644 index 00000000000..94089a9a91b --- /dev/null +++ b/seed/ts-sdk/grpc/tests/unit/zurg/schema.test.ts @@ -0,0 +1,78 @@ +import { + boolean, + discriminant, + list, + number, + object, + string, + stringLiteral, + union, +} from "../../../src/core/schemas/builders"; +import { booleanLiteral } from "../../../src/core/schemas/builders/literals/booleanLiteral"; +import { property } from "../../../src/core/schemas/builders/object/property"; +import { itSchema } from "./utils/itSchema"; + +describe("Schema", () => { + itSchema( + "large nested object", + object({ + a: string(), + b: stringLiteral("b value"), + c: property( + "raw_c", + list( + object({ + animal: union(discriminant("type", "_type"), { + dog: object({ value: boolean() }), + cat: object({ value: property("raw_cat", number()) }), + }), + }) + ) + ), + d: property("raw_d", boolean()), + e: booleanLiteral(true), + }), + { + raw: { + a: "hello", + b: "b value", + raw_c: [ + { + animal: { + _type: "dog", + value: true, + }, + }, + { + animal: { + _type: "cat", + raw_cat: 42, + }, + }, + ], + raw_d: false, + e: true, + }, + parsed: { + a: "hello", + b: "b value", + c: [ + { + animal: { + type: "dog", + value: true, + }, + }, + { + animal: { + type: "cat", + value: 42, + }, + }, + ], + d: false, + e: true, + }, + } + ); +}); diff --git a/seed/ts-sdk/grpc/tests/unit/zurg/set/set.test.ts b/seed/ts-sdk/grpc/tests/unit/zurg/set/set.test.ts new file mode 100644 index 00000000000..e17f908c80e --- /dev/null +++ b/seed/ts-sdk/grpc/tests/unit/zurg/set/set.test.ts @@ -0,0 +1,48 @@ +import { set, string } from "../../../../src/core/schemas/builders"; +import { itSchema } from "../utils/itSchema"; +import { itValidateJson, itValidateParse } from "../utils/itValidate"; + +describe("set", () => { + itSchema("converts between raw list and parsed Set", set(string()), { + raw: ["A", "B"], + parsed: new Set(["A", "B"]), + }); + + itValidateParse("not a list", set(string()), 42, [ + { + path: [], + message: "Expected list. Received 42.", + }, + ]); + + itValidateJson( + "not a Set", + set(string()), + [], + [ + { + path: [], + message: "Expected Set. Received list.", + }, + ] + ); + + itValidateParse( + "invalid item type", + set(string()), + [42], + [ + { + path: ["[0]"], + message: "Expected string. Received 42.", + }, + ] + ); + + itValidateJson("invalid item type", set(string()), new Set([42]), [ + { + path: ["[0]"], + message: "Expected string. Received 42.", + }, + ]); +}); diff --git a/seed/ts-sdk/grpc/tests/unit/zurg/skipValidation.test.ts b/seed/ts-sdk/grpc/tests/unit/zurg/skipValidation.test.ts new file mode 100644 index 00000000000..5dc88096a9f --- /dev/null +++ b/seed/ts-sdk/grpc/tests/unit/zurg/skipValidation.test.ts @@ -0,0 +1,45 @@ +/* eslint-disable no-console */ + +import { boolean, number, object, property, string, undiscriminatedUnion } from "../../../src/core/schemas/builders"; + +describe("skipValidation", () => { + it("allows data that doesn't conform to the schema", async () => { + const warningLogs: string[] = []; + const originalConsoleWarn = console.warn; + console.warn = (...args) => warningLogs.push(args.join(" ")); + + const schema = object({ + camelCase: property("snake_case", string()), + numberProperty: number(), + requiredProperty: boolean(), + anyPrimitive: undiscriminatedUnion([string(), number(), boolean()]), + }); + + const parsed = await schema.parse( + { + snake_case: "hello", + numberProperty: "oops", + anyPrimitive: true, + }, + { + skipValidation: true, + } + ); + + expect(parsed).toEqual({ + ok: true, + value: { + camelCase: "hello", + numberProperty: "oops", + anyPrimitive: true, + }, + }); + + expect(warningLogs).toEqual([ + `Failed to validate. + - numberProperty: Expected number. Received "oops".`, + ]); + + console.warn = originalConsoleWarn; + }); +}); diff --git a/seed/ts-sdk/grpc/tests/unit/zurg/undiscriminated-union/undiscriminatedUnion.test.ts b/seed/ts-sdk/grpc/tests/unit/zurg/undiscriminated-union/undiscriminatedUnion.test.ts new file mode 100644 index 00000000000..0e66433371c --- /dev/null +++ b/seed/ts-sdk/grpc/tests/unit/zurg/undiscriminated-union/undiscriminatedUnion.test.ts @@ -0,0 +1,44 @@ +import { number, object, property, string, undiscriminatedUnion } from "../../../../src/core/schemas/builders"; +import { itSchema, itSchemaIdentity } from "../utils/itSchema"; + +describe("undiscriminatedUnion", () => { + itSchemaIdentity(undiscriminatedUnion([string(), number()]), "hello world"); + + itSchemaIdentity(undiscriminatedUnion([object({ hello: string() }), object({ goodbye: string() })]), { + goodbye: "foo", + }); + + itSchema( + "Correctly transforms", + undiscriminatedUnion([object({ hello: string() }), object({ helloWorld: property("hello_world", string()) })]), + { + raw: { hello_world: "foo " }, + parsed: { helloWorld: "foo " }, + } + ); + + it("Returns errors for all variants", async () => { + const result = await undiscriminatedUnion([string(), number()]).parse(true); + if (result.ok) { + throw new Error("Unexpectedly passed validation"); + } + expect(result.errors).toEqual([ + { + message: "[Variant 0] Expected string. Received true.", + path: [], + }, + { + message: "[Variant 1] Expected number. Received true.", + path: [], + }, + ]); + }); + + describe("compile", () => { + // eslint-disable-next-line jest/expect-expect + it("doesn't compile with zero members", () => { + // @ts-expect-error + () => undiscriminatedUnion([]); + }); + }); +}); diff --git a/seed/ts-sdk/grpc/tests/unit/zurg/union/union.test.ts b/seed/ts-sdk/grpc/tests/unit/zurg/union/union.test.ts new file mode 100644 index 00000000000..790184603ac --- /dev/null +++ b/seed/ts-sdk/grpc/tests/unit/zurg/union/union.test.ts @@ -0,0 +1,113 @@ +import { boolean, discriminant, number, object, string, union } from "../../../../src/core/schemas/builders"; +import { itSchema, itSchemaIdentity } from "../utils/itSchema"; +import { itValidate } from "../utils/itValidate"; + +describe("union", () => { + itSchemaIdentity( + union("type", { + lion: object({ + meows: boolean(), + }), + giraffe: object({ + heightInInches: number(), + }), + }), + { type: "lion", meows: true }, + { title: "doesn't transform discriminant when it's a string" } + ); + + itSchema( + "transforms discriminant when it's a discriminant()", + union(discriminant("type", "_type"), { + lion: object({ meows: boolean() }), + giraffe: object({ heightInInches: number() }), + }), + { + raw: { _type: "lion", meows: true }, + parsed: { type: "lion", meows: true }, + } + ); + + describe("allowUnrecognizedUnionMembers", () => { + itSchema( + "transforms discriminant & passes through values when discriminant value is unrecognized", + union(discriminant("type", "_type"), { + lion: object({ meows: boolean() }), + giraffe: object({ heightInInches: number() }), + }), + { + // @ts-expect-error + raw: { _type: "moose", isAMoose: true }, + // @ts-expect-error + parsed: { type: "moose", isAMoose: true }, + opts: { + allowUnrecognizedUnionMembers: true, + }, + } + ); + }); + + describe("withParsedProperties", () => { + it("Added property is included on parsed object", async () => { + const schema = union("type", { + lion: object({}), + tiger: object({ value: string() }), + }).withParsedProperties({ + printType: (parsed) => () => parsed.type, + }); + + const parsed = await schema.parse({ type: "lion" }); + if (!parsed.ok) { + throw new Error("Failed to parse"); + } + expect(parsed.value.printType()).toBe("lion"); + }); + }); + + itValidate( + "non-object", + union("type", { + lion: object({}), + tiger: object({ value: string() }), + }), + [], + [ + { + path: [], + message: "Expected object. Received list.", + }, + ] + ); + + itValidate( + "missing discriminant", + union("type", { + lion: object({}), + tiger: object({ value: string() }), + }), + {}, + [ + { + path: [], + message: 'Missing discriminant ("type")', + }, + ] + ); + + itValidate( + "unrecognized discriminant value", + union("type", { + lion: object({}), + tiger: object({ value: string() }), + }), + { + type: "bear", + }, + [ + { + path: ["type"], + message: 'Expected enum. Received "bear".', + }, + ] + ); +}); diff --git a/seed/ts-sdk/grpc/tests/unit/zurg/utils/itSchema.ts b/seed/ts-sdk/grpc/tests/unit/zurg/utils/itSchema.ts new file mode 100644 index 00000000000..67b6c928175 --- /dev/null +++ b/seed/ts-sdk/grpc/tests/unit/zurg/utils/itSchema.ts @@ -0,0 +1,78 @@ +/* eslint-disable jest/no-export */ +import { Schema, SchemaOptions } from "../../../../src/core/schemas/Schema"; + +export function itSchemaIdentity( + schema: Schema, + value: T, + { title = "functions as identity", opts }: { title?: string; opts?: SchemaOptions } = {} +): void { + itSchema(title, schema, { raw: value, parsed: value, opts }); +} + +export function itSchema( + title: string, + schema: Schema, + { + raw, + parsed, + opts, + only = false, + }: { + raw: Raw; + parsed: Parsed; + opts?: SchemaOptions; + only?: boolean; + } +): void { + // eslint-disable-next-line jest/valid-title + (only ? describe.only : describe)(title, () => { + itParse("parse()", schema, { raw, parsed, opts }); + itJson("json()", schema, { raw, parsed, opts }); + }); +} + +export function itParse( + title: string, + schema: Schema, + { + raw, + parsed, + opts, + }: { + raw: Raw; + parsed: Parsed; + opts?: SchemaOptions; + } +): void { + // eslint-disable-next-line jest/valid-title + it(title, () => { + const maybeValid = schema.parse(raw, opts); + if (!maybeValid.ok) { + throw new Error("Failed to parse() " + JSON.stringify(maybeValid.errors, undefined, 4)); + } + expect(maybeValid.value).toStrictEqual(parsed); + }); +} + +export function itJson( + title: string, + schema: Schema, + { + raw, + parsed, + opts, + }: { + raw: Raw; + parsed: Parsed; + opts?: SchemaOptions; + } +): void { + // eslint-disable-next-line jest/valid-title + it(title, () => { + const maybeValid = schema.json(parsed, opts); + if (!maybeValid.ok) { + throw new Error("Failed to json() " + JSON.stringify(maybeValid.errors, undefined, 4)); + } + expect(maybeValid.value).toStrictEqual(raw); + }); +} diff --git a/seed/ts-sdk/grpc/tests/unit/zurg/utils/itValidate.ts b/seed/ts-sdk/grpc/tests/unit/zurg/utils/itValidate.ts new file mode 100644 index 00000000000..75b2c08b036 --- /dev/null +++ b/seed/ts-sdk/grpc/tests/unit/zurg/utils/itValidate.ts @@ -0,0 +1,56 @@ +/* eslint-disable jest/no-export */ +import { Schema, SchemaOptions, ValidationError } from "../../../../src/core/schemas/Schema"; + +export function itValidate( + title: string, + schema: Schema, + input: unknown, + errors: ValidationError[], + opts?: SchemaOptions +): void { + // eslint-disable-next-line jest/valid-title + describe("parse()", () => { + itValidateParse(title, schema, input, errors, opts); + }); + describe("json()", () => { + itValidateJson(title, schema, input, errors, opts); + }); +} + +export function itValidateParse( + title: string, + schema: Schema, + raw: unknown, + errors: ValidationError[], + opts?: SchemaOptions +): void { + describe("parse", () => { + // eslint-disable-next-line jest/valid-title + it(title, async () => { + const maybeValid = await schema.parse(raw, opts); + if (maybeValid.ok) { + throw new Error("Value passed validation"); + } + expect(maybeValid.errors).toStrictEqual(errors); + }); + }); +} + +export function itValidateJson( + title: string, + schema: Schema, + parsed: unknown, + errors: ValidationError[], + opts?: SchemaOptions +): void { + describe("json", () => { + // eslint-disable-next-line jest/valid-title + it(title, async () => { + const maybeValid = await schema.json(parsed, opts); + if (maybeValid.ok) { + throw new Error("Value passed validation"); + } + expect(maybeValid.errors).toStrictEqual(errors); + }); + }); +} diff --git a/seed/ts-sdk/grpc/tsconfig.json b/seed/ts-sdk/grpc/tsconfig.json new file mode 100644 index 00000000000..538c94fe015 --- /dev/null +++ b/seed/ts-sdk/grpc/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "extendedDiagnostics": true, + "strict": true, + "target": "ES6", + "module": "CommonJS", + "moduleResolution": "node", + "esModuleInterop": true, + "skipLibCheck": true, + "declaration": true, + "outDir": "dist", + "rootDir": "src", + "baseUrl": "src" + }, + "include": ["src"], + "exclude": [] +} diff --git a/seed/ts-sdk/idempotency-headers/package.json b/seed/ts-sdk/idempotency-headers/package.json index 21fbd49dc23..ec5b16f2cac 100644 --- a/seed/ts-sdk/idempotency-headers/package.json +++ b/seed/ts-sdk/idempotency-headers/package.json @@ -23,6 +23,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/idempotency-headers/src/api/resources/payment/client/Client.ts b/seed/ts-sdk/idempotency-headers/src/api/resources/payment/client/Client.ts index 51b96f254b8..277d8db6ef9 100644 --- a/seed/ts-sdk/idempotency-headers/src/api/resources/payment/client/Client.ts +++ b/seed/ts-sdk/idempotency-headers/src/api/resources/payment/client/Client.ts @@ -54,6 +54,7 @@ export class Payment { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/idempotency-headers", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/idempotency-headers/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, "Idempotency-Key": requestOptions?.idempotencyKey, @@ -116,6 +117,7 @@ export class Payment { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/idempotency-headers", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/idempotency-headers/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/idempotency-headers/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/idempotency-headers/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/idempotency-headers/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/idempotency-headers/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/imdb/no-custom-config/package.json b/seed/ts-sdk/imdb/no-custom-config/package.json index 036cfe5219e..1fc7453da13 100644 --- a/seed/ts-sdk/imdb/no-custom-config/package.json +++ b/seed/ts-sdk/imdb/no-custom-config/package.json @@ -23,6 +23,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/imdb/no-custom-config/src/api/resources/imdb/client/Client.ts b/seed/ts-sdk/imdb/no-custom-config/src/api/resources/imdb/client/Client.ts index 8cd113bd1b9..1d5ced15628 100644 --- a/seed/ts-sdk/imdb/no-custom-config/src/api/resources/imdb/client/Client.ts +++ b/seed/ts-sdk/imdb/no-custom-config/src/api/resources/imdb/client/Client.ts @@ -51,6 +51,7 @@ export class Imdb { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/imdb", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/imdb/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -113,6 +114,7 @@ export class Imdb { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/imdb", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/imdb/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/imdb/no-custom-config/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/imdb/no-custom-config/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/imdb/no-custom-config/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/imdb/no-custom-config/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/imdb/noScripts/package.json b/seed/ts-sdk/imdb/noScripts/package.json index fdb5c961da4..38f5adcae71 100644 --- a/seed/ts-sdk/imdb/noScripts/package.json +++ b/seed/ts-sdk/imdb/noScripts/package.json @@ -24,6 +24,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/imdb/noScripts/src/api/resources/imdb/client/Client.ts b/seed/ts-sdk/imdb/noScripts/src/api/resources/imdb/client/Client.ts index a2da0e2e6d0..3e99c306919 100644 --- a/seed/ts-sdk/imdb/noScripts/src/api/resources/imdb/client/Client.ts +++ b/seed/ts-sdk/imdb/noScripts/src/api/resources/imdb/client/Client.ts @@ -49,6 +49,7 @@ export class Imdb { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/imdb", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/imdb/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version }, @@ -100,6 +101,7 @@ export class Imdb { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/imdb", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/imdb/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version }, diff --git a/seed/ts-sdk/imdb/noScripts/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/imdb/noScripts/tests/unit/fetcher/Fetcher.test.ts index b765141a2aa..5a60d6c82dd 100644 --- a/seed/ts-sdk/imdb/noScripts/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/imdb/noScripts/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json" }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }) + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/imdb/omit-undefined/package.json b/seed/ts-sdk/imdb/omit-undefined/package.json index 036cfe5219e..1fc7453da13 100644 --- a/seed/ts-sdk/imdb/omit-undefined/package.json +++ b/seed/ts-sdk/imdb/omit-undefined/package.json @@ -23,6 +23,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/imdb/omit-undefined/src/api/resources/imdb/client/Client.ts b/seed/ts-sdk/imdb/omit-undefined/src/api/resources/imdb/client/Client.ts index f33b5d2474b..59c2b446f70 100644 --- a/seed/ts-sdk/imdb/omit-undefined/src/api/resources/imdb/client/Client.ts +++ b/seed/ts-sdk/imdb/omit-undefined/src/api/resources/imdb/client/Client.ts @@ -51,6 +51,7 @@ export class Imdb { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/imdb", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/imdb/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -116,6 +117,7 @@ export class Imdb { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/imdb", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/imdb/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/imdb/omit-undefined/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/imdb/omit-undefined/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/imdb/omit-undefined/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/imdb/omit-undefined/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/literal/package.json b/seed/ts-sdk/literal/package.json index 90733641368..325dce9ed74 100644 --- a/seed/ts-sdk/literal/package.json +++ b/seed/ts-sdk/literal/package.json @@ -22,6 +22,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/literal/src/api/resources/headers/client/Client.ts b/seed/ts-sdk/literal/src/api/resources/headers/client/Client.ts index 5a7dc8dc4c0..56aaeef6ac5 100644 --- a/seed/ts-sdk/literal/src/api/resources/headers/client/Client.ts +++ b/seed/ts-sdk/literal/src/api/resources/headers/client/Client.ts @@ -60,6 +60,7 @@ export class Headers { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/literal", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/literal/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, "X-Endpoint-Version": "02-12-2024", diff --git a/seed/ts-sdk/literal/src/api/resources/inlined/client/Client.ts b/seed/ts-sdk/literal/src/api/resources/inlined/client/Client.ts index 0fca8f71dd2..bec9052bae1 100644 --- a/seed/ts-sdk/literal/src/api/resources/inlined/client/Client.ts +++ b/seed/ts-sdk/literal/src/api/resources/inlined/client/Client.ts @@ -64,6 +64,7 @@ export class Inlined { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/literal", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/literal/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/literal/src/api/resources/path/client/Client.ts b/seed/ts-sdk/literal/src/api/resources/path/client/Client.ts index 4a20805dab3..6c0cd5bb842 100644 --- a/seed/ts-sdk/literal/src/api/resources/path/client/Client.ts +++ b/seed/ts-sdk/literal/src/api/resources/path/client/Client.ts @@ -55,6 +55,7 @@ export class Path { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/literal", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/literal/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/literal/src/api/resources/query/client/Client.ts b/seed/ts-sdk/literal/src/api/resources/query/client/Client.ts index 49899e8b861..0d3b1ef9840 100644 --- a/seed/ts-sdk/literal/src/api/resources/query/client/Client.ts +++ b/seed/ts-sdk/literal/src/api/resources/query/client/Client.ts @@ -65,6 +65,7 @@ export class Query { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/literal", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/literal/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/literal/src/api/resources/reference/client/Client.ts b/seed/ts-sdk/literal/src/api/resources/reference/client/Client.ts index e381e9e5688..2e311e1e3c8 100644 --- a/seed/ts-sdk/literal/src/api/resources/reference/client/Client.ts +++ b/seed/ts-sdk/literal/src/api/resources/reference/client/Client.ts @@ -63,6 +63,7 @@ export class Reference { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/literal", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/literal/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/literal/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/literal/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/literal/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/literal/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/mixed-case/no-custom-config/package.json b/seed/ts-sdk/mixed-case/no-custom-config/package.json index 8cae05924e7..70984f7393d 100644 --- a/seed/ts-sdk/mixed-case/no-custom-config/package.json +++ b/seed/ts-sdk/mixed-case/no-custom-config/package.json @@ -22,6 +22,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/mixed-case/no-custom-config/src/api/resources/service/client/Client.ts b/seed/ts-sdk/mixed-case/no-custom-config/src/api/resources/service/client/Client.ts index 6a697f0b1a2..a447069a2c5 100644 --- a/seed/ts-sdk/mixed-case/no-custom-config/src/api/resources/service/client/Client.ts +++ b/seed/ts-sdk/mixed-case/no-custom-config/src/api/resources/service/client/Client.ts @@ -47,6 +47,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/mixed-case", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/mixed-case/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -112,6 +113,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/mixed-case", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/mixed-case/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/mixed-case/no-custom-config/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/mixed-case/no-custom-config/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/mixed-case/no-custom-config/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/mixed-case/no-custom-config/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/mixed-case/retain-original-casing/package.json b/seed/ts-sdk/mixed-case/retain-original-casing/package.json index 8cae05924e7..70984f7393d 100644 --- a/seed/ts-sdk/mixed-case/retain-original-casing/package.json +++ b/seed/ts-sdk/mixed-case/retain-original-casing/package.json @@ -22,6 +22,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/mixed-case/retain-original-casing/src/api/resources/service/client/Client.ts b/seed/ts-sdk/mixed-case/retain-original-casing/src/api/resources/service/client/Client.ts index 3ba07230e18..48b675b4eb3 100644 --- a/seed/ts-sdk/mixed-case/retain-original-casing/src/api/resources/service/client/Client.ts +++ b/seed/ts-sdk/mixed-case/retain-original-casing/src/api/resources/service/client/Client.ts @@ -47,6 +47,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/mixed-case", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/mixed-case/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -112,6 +113,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/mixed-case", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/mixed-case/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/mixed-case/retain-original-casing/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/mixed-case/retain-original-casing/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/mixed-case/retain-original-casing/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/mixed-case/retain-original-casing/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/multi-line-docs/package.json b/seed/ts-sdk/multi-line-docs/package.json index 9c2505d9824..ea7377bc699 100644 --- a/seed/ts-sdk/multi-line-docs/package.json +++ b/seed/ts-sdk/multi-line-docs/package.json @@ -22,6 +22,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/multi-line-docs/src/api/resources/user/client/Client.ts b/seed/ts-sdk/multi-line-docs/src/api/resources/user/client/Client.ts index 09d52622e7e..7500993aeca 100644 --- a/seed/ts-sdk/multi-line-docs/src/api/resources/user/client/Client.ts +++ b/seed/ts-sdk/multi-line-docs/src/api/resources/user/client/Client.ts @@ -45,6 +45,7 @@ export class User { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/multi-line-docs", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/multi-line-docs/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -104,6 +105,7 @@ export class User { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/multi-line-docs", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/multi-line-docs/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/multi-line-docs/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/multi-line-docs/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/multi-line-docs/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/multi-line-docs/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/multi-url-environment-no-default/package.json b/seed/ts-sdk/multi-url-environment-no-default/package.json index 482f2fd81cd..284a020e7a1 100644 --- a/seed/ts-sdk/multi-url-environment-no-default/package.json +++ b/seed/ts-sdk/multi-url-environment-no-default/package.json @@ -23,6 +23,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/multi-url-environment-no-default/src/api/resources/ec2/client/Client.ts b/seed/ts-sdk/multi-url-environment-no-default/src/api/resources/ec2/client/Client.ts index cd60e4eadaa..f8bd10fd2b7 100644 --- a/seed/ts-sdk/multi-url-environment-no-default/src/api/resources/ec2/client/Client.ts +++ b/seed/ts-sdk/multi-url-environment-no-default/src/api/resources/ec2/client/Client.ts @@ -52,6 +52,7 @@ export class Ec2 { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/multi-url-environment-no-default", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/multi-url-environment-no-default/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/multi-url-environment-no-default/src/api/resources/s3/client/Client.ts b/seed/ts-sdk/multi-url-environment-no-default/src/api/resources/s3/client/Client.ts index 95f86ab4fef..fc9a5f677a1 100644 --- a/seed/ts-sdk/multi-url-environment-no-default/src/api/resources/s3/client/Client.ts +++ b/seed/ts-sdk/multi-url-environment-no-default/src/api/resources/s3/client/Client.ts @@ -52,6 +52,7 @@ export class S3 { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/multi-url-environment-no-default", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/multi-url-environment-no-default/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/multi-url-environment-no-default/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/multi-url-environment-no-default/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/multi-url-environment-no-default/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/multi-url-environment-no-default/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/multi-url-environment/package.json b/seed/ts-sdk/multi-url-environment/package.json index 092a56b763b..667504b72ed 100644 --- a/seed/ts-sdk/multi-url-environment/package.json +++ b/seed/ts-sdk/multi-url-environment/package.json @@ -23,6 +23,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/multi-url-environment/src/api/resources/ec2/client/Client.ts b/seed/ts-sdk/multi-url-environment/src/api/resources/ec2/client/Client.ts index 15aa9cc2b71..9dd62e2f486 100644 --- a/seed/ts-sdk/multi-url-environment/src/api/resources/ec2/client/Client.ts +++ b/seed/ts-sdk/multi-url-environment/src/api/resources/ec2/client/Client.ts @@ -57,6 +57,7 @@ export class Ec2 { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/multi-url-environment", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/multi-url-environment/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/multi-url-environment/src/api/resources/s3/client/Client.ts b/seed/ts-sdk/multi-url-environment/src/api/resources/s3/client/Client.ts index 1efe1323c54..cf313f36103 100644 --- a/seed/ts-sdk/multi-url-environment/src/api/resources/s3/client/Client.ts +++ b/seed/ts-sdk/multi-url-environment/src/api/resources/s3/client/Client.ts @@ -57,6 +57,7 @@ export class S3 { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/multi-url-environment", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/multi-url-environment/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/multi-url-environment/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/multi-url-environment/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/multi-url-environment/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/multi-url-environment/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/no-environment/package.json b/seed/ts-sdk/no-environment/package.json index 45143467838..b3f5077d760 100644 --- a/seed/ts-sdk/no-environment/package.json +++ b/seed/ts-sdk/no-environment/package.json @@ -23,6 +23,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/no-environment/src/api/resources/dummy/client/Client.ts b/seed/ts-sdk/no-environment/src/api/resources/dummy/client/Client.ts index 45886d405d5..35f70837264 100644 --- a/seed/ts-sdk/no-environment/src/api/resources/dummy/client/Client.ts +++ b/seed/ts-sdk/no-environment/src/api/resources/dummy/client/Client.ts @@ -41,6 +41,7 @@ export class Dummy { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/no-environment", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/no-environment/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/no-environment/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/no-environment/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/no-environment/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/no-environment/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/oauth-client-credentials-default/package.json b/seed/ts-sdk/oauth-client-credentials-default/package.json index b7ff4a1874f..feed8ac429e 100644 --- a/seed/ts-sdk/oauth-client-credentials-default/package.json +++ b/seed/ts-sdk/oauth-client-credentials-default/package.json @@ -23,6 +23,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/oauth-client-credentials-default/src/api/resources/auth/client/Client.ts b/seed/ts-sdk/oauth-client-credentials-default/src/api/resources/auth/client/Client.ts index 35825cb12a0..780a8ac5cfb 100644 --- a/seed/ts-sdk/oauth-client-credentials-default/src/api/resources/auth/client/Client.ts +++ b/seed/ts-sdk/oauth-client-credentials-default/src/api/resources/auth/client/Client.ts @@ -49,6 +49,7 @@ export class Auth { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/oauth-client-credentials-default", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/oauth-client-credentials-default/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/oauth-client-credentials-default/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/oauth-client-credentials-default/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/oauth-client-credentials-default/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/oauth-client-credentials-default/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/oauth-client-credentials-environment-variables/package.json b/seed/ts-sdk/oauth-client-credentials-environment-variables/package.json index 8f7ed6fd138..97bd95bce73 100644 --- a/seed/ts-sdk/oauth-client-credentials-environment-variables/package.json +++ b/seed/ts-sdk/oauth-client-credentials-environment-variables/package.json @@ -23,6 +23,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/oauth-client-credentials-environment-variables/src/api/resources/auth/client/Client.ts b/seed/ts-sdk/oauth-client-credentials-environment-variables/src/api/resources/auth/client/Client.ts index e90a59ccf56..4718dc96200 100644 --- a/seed/ts-sdk/oauth-client-credentials-environment-variables/src/api/resources/auth/client/Client.ts +++ b/seed/ts-sdk/oauth-client-credentials-environment-variables/src/api/resources/auth/client/Client.ts @@ -50,6 +50,7 @@ export class Auth { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/oauth-client-credentials-environment-variables", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/oauth-client-credentials-environment-variables/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -119,6 +120,7 @@ export class Auth { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/oauth-client-credentials-environment-variables", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/oauth-client-credentials-environment-variables/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/oauth-client-credentials-environment-variables/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/oauth-client-credentials-environment-variables/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/oauth-client-credentials-environment-variables/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/oauth-client-credentials-environment-variables/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/oauth-client-credentials-nested-root/never-throw-errors/package.json b/seed/ts-sdk/oauth-client-credentials-nested-root/never-throw-errors/package.json index 3bfab58ea8c..7a1008aef4e 100644 --- a/seed/ts-sdk/oauth-client-credentials-nested-root/never-throw-errors/package.json +++ b/seed/ts-sdk/oauth-client-credentials-nested-root/never-throw-errors/package.json @@ -23,6 +23,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/oauth-client-credentials-nested-root/never-throw-errors/src/api/resources/auth/client/Client.ts b/seed/ts-sdk/oauth-client-credentials-nested-root/never-throw-errors/src/api/resources/auth/client/Client.ts index bf9be260677..38b516e5b4c 100644 --- a/seed/ts-sdk/oauth-client-credentials-nested-root/never-throw-errors/src/api/resources/auth/client/Client.ts +++ b/seed/ts-sdk/oauth-client-credentials-nested-root/never-throw-errors/src/api/resources/auth/client/Client.ts @@ -51,6 +51,7 @@ export class Auth { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/oauth-client-credentials-nested-root", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/oauth-client-credentials-nested-root/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/oauth-client-credentials-nested-root/never-throw-errors/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/oauth-client-credentials-nested-root/never-throw-errors/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/oauth-client-credentials-nested-root/never-throw-errors/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/oauth-client-credentials-nested-root/never-throw-errors/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/oauth-client-credentials-nested-root/no-custom-config/package.json b/seed/ts-sdk/oauth-client-credentials-nested-root/no-custom-config/package.json index 3bfab58ea8c..7a1008aef4e 100644 --- a/seed/ts-sdk/oauth-client-credentials-nested-root/no-custom-config/package.json +++ b/seed/ts-sdk/oauth-client-credentials-nested-root/no-custom-config/package.json @@ -23,6 +23,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/oauth-client-credentials-nested-root/no-custom-config/src/api/resources/auth/client/Client.ts b/seed/ts-sdk/oauth-client-credentials-nested-root/no-custom-config/src/api/resources/auth/client/Client.ts index 9fed4b6a391..4cbddb69f48 100644 --- a/seed/ts-sdk/oauth-client-credentials-nested-root/no-custom-config/src/api/resources/auth/client/Client.ts +++ b/seed/ts-sdk/oauth-client-credentials-nested-root/no-custom-config/src/api/resources/auth/client/Client.ts @@ -50,6 +50,7 @@ export class Auth { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/oauth-client-credentials-nested-root", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/oauth-client-credentials-nested-root/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/oauth-client-credentials-nested-root/no-custom-config/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/oauth-client-credentials-nested-root/no-custom-config/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/oauth-client-credentials-nested-root/no-custom-config/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/oauth-client-credentials-nested-root/no-custom-config/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/oauth-client-credentials/package.json b/seed/ts-sdk/oauth-client-credentials/package.json index e9f2682699e..eeb5485c651 100644 --- a/seed/ts-sdk/oauth-client-credentials/package.json +++ b/seed/ts-sdk/oauth-client-credentials/package.json @@ -23,6 +23,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/oauth-client-credentials/src/api/resources/auth/client/Client.ts b/seed/ts-sdk/oauth-client-credentials/src/api/resources/auth/client/Client.ts index a6b595bf89b..0f0000421b6 100644 --- a/seed/ts-sdk/oauth-client-credentials/src/api/resources/auth/client/Client.ts +++ b/seed/ts-sdk/oauth-client-credentials/src/api/resources/auth/client/Client.ts @@ -50,6 +50,7 @@ export class Auth { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/oauth-client-credentials", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/oauth-client-credentials/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -119,6 +120,7 @@ export class Auth { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/oauth-client-credentials", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/oauth-client-credentials/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/oauth-client-credentials/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/oauth-client-credentials/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/oauth-client-credentials/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/oauth-client-credentials/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/optional/package.json b/seed/ts-sdk/optional/package.json index 0d913b3261c..9c949d72a84 100644 --- a/seed/ts-sdk/optional/package.json +++ b/seed/ts-sdk/optional/package.json @@ -22,6 +22,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/optional/src/api/resources/optional/client/Client.ts b/seed/ts-sdk/optional/src/api/resources/optional/client/Client.ts index 5e4cbd30dba..994aed3eae0 100644 --- a/seed/ts-sdk/optional/src/api/resources/optional/client/Client.ts +++ b/seed/ts-sdk/optional/src/api/resources/optional/client/Client.ts @@ -47,6 +47,7 @@ export class Optional { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/optional", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/optional/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/optional/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/optional/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/optional/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/optional/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/package-yml/package.json b/seed/ts-sdk/package-yml/package.json index bb0b3674649..08c5343a8a6 100644 --- a/seed/ts-sdk/package-yml/package.json +++ b/seed/ts-sdk/package-yml/package.json @@ -22,6 +22,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/package-yml/src/Client.ts b/seed/ts-sdk/package-yml/src/Client.ts index f3024a91e41..1cf41e94fa8 100644 --- a/seed/ts-sdk/package-yml/src/Client.ts +++ b/seed/ts-sdk/package-yml/src/Client.ts @@ -52,6 +52,7 @@ export class SeedPackageYmlClient { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/package-yml", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/package-yml/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/package-yml/src/api/resources/service/client/Client.ts b/seed/ts-sdk/package-yml/src/api/resources/service/client/Client.ts index bffd00d29e8..84414ebd34f 100644 --- a/seed/ts-sdk/package-yml/src/api/resources/service/client/Client.ts +++ b/seed/ts-sdk/package-yml/src/api/resources/service/client/Client.ts @@ -43,6 +43,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/package-yml", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/package-yml/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/package-yml/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/package-yml/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/package-yml/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/package-yml/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/pagination/package.json b/seed/ts-sdk/pagination/package.json index f6d44455946..56b3a3b877e 100644 --- a/seed/ts-sdk/pagination/package.json +++ b/seed/ts-sdk/pagination/package.json @@ -23,6 +23,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/pagination/src/api/resources/users/client/Client.ts b/seed/ts-sdk/pagination/src/api/resources/users/client/Client.ts index 2660cf34d44..3007dbe5199 100644 --- a/seed/ts-sdk/pagination/src/api/resources/users/client/Client.ts +++ b/seed/ts-sdk/pagination/src/api/resources/users/client/Client.ts @@ -68,6 +68,7 @@ export class Users { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/pagination", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/pagination/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -142,6 +143,7 @@ export class Users { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/pagination", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/pagination/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -233,6 +235,7 @@ export class Users { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/pagination", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/pagination/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -309,6 +312,7 @@ export class Users { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/pagination", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/pagination/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -398,6 +402,7 @@ export class Users { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/pagination", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/pagination/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -477,6 +482,7 @@ export class Users { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/pagination", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/pagination/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -552,6 +558,7 @@ export class Users { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/pagination", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/pagination/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -629,6 +636,7 @@ export class Users { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/pagination", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/pagination/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/pagination/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/pagination/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/pagination/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/pagination/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/plain-text/package.json b/seed/ts-sdk/plain-text/package.json index ea8d86f65d1..692bfad863c 100644 --- a/seed/ts-sdk/plain-text/package.json +++ b/seed/ts-sdk/plain-text/package.json @@ -22,6 +22,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/plain-text/src/api/resources/service/client/Client.ts b/seed/ts-sdk/plain-text/src/api/resources/service/client/Client.ts index 4b830d5ec20..755b589c6bb 100644 --- a/seed/ts-sdk/plain-text/src/api/resources/service/client/Client.ts +++ b/seed/ts-sdk/plain-text/src/api/resources/service/client/Client.ts @@ -38,6 +38,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/plain-text", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/plain-text/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/plain-text/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/plain-text/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/plain-text/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/plain-text/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/query-parameters/no-custom-config/package.json b/seed/ts-sdk/query-parameters/no-custom-config/package.json index 608bd4ef564..bdfb7fac46f 100644 --- a/seed/ts-sdk/query-parameters/no-custom-config/package.json +++ b/seed/ts-sdk/query-parameters/no-custom-config/package.json @@ -22,6 +22,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/query-parameters/no-custom-config/src/api/resources/user/client/Client.ts b/seed/ts-sdk/query-parameters/no-custom-config/src/api/resources/user/client/Client.ts index 32ba9f8352d..482c0f2f2aa 100644 --- a/seed/ts-sdk/query-parameters/no-custom-config/src/api/resources/user/client/Client.ts +++ b/seed/ts-sdk/query-parameters/no-custom-config/src/api/resources/user/client/Client.ts @@ -158,6 +158,7 @@ export class User { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/query-parameters", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/query-parameters/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/query-parameters/no-custom-config/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/query-parameters/no-custom-config/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/query-parameters/no-custom-config/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/query-parameters/no-custom-config/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/query-parameters/no-serde-layer-query/package.json b/seed/ts-sdk/query-parameters/no-serde-layer-query/package.json index 608bd4ef564..bdfb7fac46f 100644 --- a/seed/ts-sdk/query-parameters/no-serde-layer-query/package.json +++ b/seed/ts-sdk/query-parameters/no-serde-layer-query/package.json @@ -22,6 +22,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/query-parameters/no-serde-layer-query/src/api/resources/user/client/Client.ts b/seed/ts-sdk/query-parameters/no-serde-layer-query/src/api/resources/user/client/Client.ts index 532dba46292..3b0eaed3b02 100644 --- a/seed/ts-sdk/query-parameters/no-serde-layer-query/src/api/resources/user/client/Client.ts +++ b/seed/ts-sdk/query-parameters/no-serde-layer-query/src/api/resources/user/client/Client.ts @@ -128,6 +128,7 @@ export class User { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/query-parameters", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/query-parameters/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/query-parameters/no-serde-layer-query/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/query-parameters/no-serde-layer-query/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/query-parameters/no-serde-layer-query/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/query-parameters/no-serde-layer-query/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/reserved-keywords/package.json b/seed/ts-sdk/reserved-keywords/package.json index 06433c1b8ab..9da63d5aeff 100644 --- a/seed/ts-sdk/reserved-keywords/package.json +++ b/seed/ts-sdk/reserved-keywords/package.json @@ -20,6 +20,7 @@ "devDependencies": { "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/reserved-keywords/src/api/resources/package/client/Client.ts b/seed/ts-sdk/reserved-keywords/src/api/resources/package/client/Client.ts index 7f91f3691f7..5be8edceef3 100644 --- a/seed/ts-sdk/reserved-keywords/src/api/resources/package/client/Client.ts +++ b/seed/ts-sdk/reserved-keywords/src/api/resources/package/client/Client.ts @@ -44,6 +44,7 @@ export class Package { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/reserved-keywords", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/reserved-keywords/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/reserved-keywords/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/reserved-keywords/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/reserved-keywords/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/reserved-keywords/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/response-property/package.json b/seed/ts-sdk/response-property/package.json index d9a6f6dfc0d..350d2b9ab6e 100644 --- a/seed/ts-sdk/response-property/package.json +++ b/seed/ts-sdk/response-property/package.json @@ -22,6 +22,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/response-property/src/api/resources/service/client/Client.ts b/seed/ts-sdk/response-property/src/api/resources/service/client/Client.ts index 5f85709d8c3..5453b882ef4 100644 --- a/seed/ts-sdk/response-property/src/api/resources/service/client/Client.ts +++ b/seed/ts-sdk/response-property/src/api/resources/service/client/Client.ts @@ -44,6 +44,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/response-property", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/response-property/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -103,6 +104,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/response-property", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/response-property/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -162,6 +164,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/response-property", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/response-property/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -221,6 +224,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/response-property", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/response-property/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -282,6 +286,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/response-property", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/response-property/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -343,6 +348,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/response-property", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/response-property/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -404,6 +410,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/response-property", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/response-property/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/response-property/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/response-property/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/response-property/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/response-property/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/server-sent-events/package.json b/seed/ts-sdk/server-sent-events/package.json index f351a6dba61..3dc98b60480 100644 --- a/seed/ts-sdk/server-sent-events/package.json +++ b/seed/ts-sdk/server-sent-events/package.json @@ -22,6 +22,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/server-sent-events/src/api/resources/completions/client/Client.ts b/seed/ts-sdk/server-sent-events/src/api/resources/completions/client/Client.ts index 1905e43d5b2..4b907dc8a90 100644 --- a/seed/ts-sdk/server-sent-events/src/api/resources/completions/client/Client.ts +++ b/seed/ts-sdk/server-sent-events/src/api/resources/completions/client/Client.ts @@ -38,6 +38,7 @@ export class Completions { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/server-sent-events", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/server-sent-events/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/server-sent-events/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/server-sent-events/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/server-sent-events/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/server-sent-events/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/single-url-environment-default/package.json b/seed/ts-sdk/single-url-environment-default/package.json index 3f8c2984374..3d2b78a8d37 100644 --- a/seed/ts-sdk/single-url-environment-default/package.json +++ b/seed/ts-sdk/single-url-environment-default/package.json @@ -23,6 +23,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/single-url-environment-default/src/api/resources/dummy/client/Client.ts b/seed/ts-sdk/single-url-environment-default/src/api/resources/dummy/client/Client.ts index 395b60aaa97..17ea96b07e3 100644 --- a/seed/ts-sdk/single-url-environment-default/src/api/resources/dummy/client/Client.ts +++ b/seed/ts-sdk/single-url-environment-default/src/api/resources/dummy/client/Client.ts @@ -46,6 +46,7 @@ export class Dummy { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/single-url-environment-default", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/single-url-environment-default/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/single-url-environment-default/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/single-url-environment-default/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/single-url-environment-default/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/single-url-environment-default/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/single-url-environment-no-default/package.json b/seed/ts-sdk/single-url-environment-no-default/package.json index 732e703ad0b..c667061699c 100644 --- a/seed/ts-sdk/single-url-environment-no-default/package.json +++ b/seed/ts-sdk/single-url-environment-no-default/package.json @@ -23,6 +23,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/single-url-environment-no-default/src/api/resources/dummy/client/Client.ts b/seed/ts-sdk/single-url-environment-no-default/src/api/resources/dummy/client/Client.ts index e722ee18f48..da08be1578b 100644 --- a/seed/ts-sdk/single-url-environment-no-default/src/api/resources/dummy/client/Client.ts +++ b/seed/ts-sdk/single-url-environment-no-default/src/api/resources/dummy/client/Client.ts @@ -42,6 +42,7 @@ export class Dummy { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/single-url-environment-no-default", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/single-url-environment-no-default/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/single-url-environment-no-default/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/single-url-environment-no-default/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/single-url-environment-no-default/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/single-url-environment-no-default/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/streaming/allow-custom-fetcher/package.json b/seed/ts-sdk/streaming/allow-custom-fetcher/package.json index 9be4e38595b..887f8792db7 100644 --- a/seed/ts-sdk/streaming/allow-custom-fetcher/package.json +++ b/seed/ts-sdk/streaming/allow-custom-fetcher/package.json @@ -22,6 +22,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/streaming/allow-custom-fetcher/src/api/resources/dummy/client/Client.ts b/seed/ts-sdk/streaming/allow-custom-fetcher/src/api/resources/dummy/client/Client.ts index a3e600fc44a..7320e153165 100644 --- a/seed/ts-sdk/streaming/allow-custom-fetcher/src/api/resources/dummy/client/Client.ts +++ b/seed/ts-sdk/streaming/allow-custom-fetcher/src/api/resources/dummy/client/Client.ts @@ -39,6 +39,7 @@ export class Dummy { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/streaming", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/streaming/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -114,6 +115,7 @@ export class Dummy { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/streaming", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/streaming/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/streaming/allow-custom-fetcher/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/streaming/allow-custom-fetcher/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/streaming/allow-custom-fetcher/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/streaming/allow-custom-fetcher/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/streaming/no-custom-config/package.json b/seed/ts-sdk/streaming/no-custom-config/package.json index 9be4e38595b..887f8792db7 100644 --- a/seed/ts-sdk/streaming/no-custom-config/package.json +++ b/seed/ts-sdk/streaming/no-custom-config/package.json @@ -22,6 +22,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/streaming/no-custom-config/src/api/resources/dummy/client/Client.ts b/seed/ts-sdk/streaming/no-custom-config/src/api/resources/dummy/client/Client.ts index 5fdf914307f..199c26001d9 100644 --- a/seed/ts-sdk/streaming/no-custom-config/src/api/resources/dummy/client/Client.ts +++ b/seed/ts-sdk/streaming/no-custom-config/src/api/resources/dummy/client/Client.ts @@ -38,6 +38,7 @@ export class Dummy { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/streaming", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/streaming/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -113,6 +114,7 @@ export class Dummy { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/streaming", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/streaming/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/streaming/no-custom-config/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/streaming/no-custom-config/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/streaming/no-custom-config/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/streaming/no-custom-config/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/trace/exhaustive/package.json b/seed/ts-sdk/trace/exhaustive/package.json index bb222c89edf..de268a4c1a0 100644 --- a/seed/ts-sdk/trace/exhaustive/package.json +++ b/seed/ts-sdk/trace/exhaustive/package.json @@ -23,6 +23,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/trace/exhaustive/src/api/resources/admin/client/Client.ts b/seed/ts-sdk/trace/exhaustive/src/api/resources/admin/client/Client.ts index 3a9c1728b7f..797a0af4d8a 100644 --- a/seed/ts-sdk/trace/exhaustive/src/api/resources/admin/client/Client.ts +++ b/seed/ts-sdk/trace/exhaustive/src/api/resources/admin/client/Client.ts @@ -61,6 +61,7 @@ export class Admin { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -118,6 +119,7 @@ export class Admin { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -172,6 +174,7 @@ export class Admin { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -229,6 +232,7 @@ export class Admin { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -298,6 +302,7 @@ export class Admin { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -362,6 +367,7 @@ export class Admin { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -432,6 +438,7 @@ export class Admin { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -494,6 +501,7 @@ export class Admin { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/trace/exhaustive/src/api/resources/homepage/client/Client.ts b/seed/ts-sdk/trace/exhaustive/src/api/resources/homepage/client/Client.ts index a8f0159ef63..a5f9f2ca322 100644 --- a/seed/ts-sdk/trace/exhaustive/src/api/resources/homepage/client/Client.ts +++ b/seed/ts-sdk/trace/exhaustive/src/api/resources/homepage/client/Client.ts @@ -55,6 +55,7 @@ export class Homepage { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -109,6 +110,7 @@ export class Homepage { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/trace/exhaustive/src/api/resources/migration/client/Client.ts b/seed/ts-sdk/trace/exhaustive/src/api/resources/migration/client/Client.ts index 7caa50af970..9f2fb6af15b 100644 --- a/seed/ts-sdk/trace/exhaustive/src/api/resources/migration/client/Client.ts +++ b/seed/ts-sdk/trace/exhaustive/src/api/resources/migration/client/Client.ts @@ -60,6 +60,7 @@ export class Migration { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, "admin-key-header": adminKeyHeader, diff --git a/seed/ts-sdk/trace/exhaustive/src/api/resources/playlist/client/Client.ts b/seed/ts-sdk/trace/exhaustive/src/api/resources/playlist/client/Client.ts index feb6beccb58..9320909b1bc 100644 --- a/seed/ts-sdk/trace/exhaustive/src/api/resources/playlist/client/Client.ts +++ b/seed/ts-sdk/trace/exhaustive/src/api/resources/playlist/client/Client.ts @@ -75,6 +75,7 @@ export class Playlist { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -163,6 +164,7 @@ export class Playlist { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -224,6 +226,7 @@ export class Playlist { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -308,6 +311,7 @@ export class Playlist { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -392,6 +396,7 @@ export class Playlist { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/trace/exhaustive/src/api/resources/problem/client/Client.ts b/seed/ts-sdk/trace/exhaustive/src/api/resources/problem/client/Client.ts index ee10190a7a8..3b7eb109ecb 100644 --- a/seed/ts-sdk/trace/exhaustive/src/api/resources/problem/client/Client.ts +++ b/seed/ts-sdk/trace/exhaustive/src/api/resources/problem/client/Client.ts @@ -89,6 +89,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -178,6 +179,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -235,6 +237,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -295,6 +298,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/trace/exhaustive/src/api/resources/submission/client/Client.ts b/seed/ts-sdk/trace/exhaustive/src/api/resources/submission/client/Client.ts index 87bb8c38e30..bdde77f68c0 100644 --- a/seed/ts-sdk/trace/exhaustive/src/api/resources/submission/client/Client.ts +++ b/seed/ts-sdk/trace/exhaustive/src/api/resources/submission/client/Client.ts @@ -64,6 +64,7 @@ export class Submission { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -122,6 +123,7 @@ export class Submission { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -178,6 +180,7 @@ export class Submission { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -230,6 +233,7 @@ export class Submission { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/trace/exhaustive/src/api/resources/sysprop/client/Client.ts b/seed/ts-sdk/trace/exhaustive/src/api/resources/sysprop/client/Client.ts index 3a1f2b670ce..0cbd1581a84 100644 --- a/seed/ts-sdk/trace/exhaustive/src/api/resources/sysprop/client/Client.ts +++ b/seed/ts-sdk/trace/exhaustive/src/api/resources/sysprop/client/Client.ts @@ -61,6 +61,7 @@ export class Sysprop { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -110,6 +111,7 @@ export class Sysprop { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/trace/exhaustive/src/api/resources/v2/client/Client.ts b/seed/ts-sdk/trace/exhaustive/src/api/resources/v2/client/Client.ts index 7d99fb940fc..d8bcda945b0 100644 --- a/seed/ts-sdk/trace/exhaustive/src/api/resources/v2/client/Client.ts +++ b/seed/ts-sdk/trace/exhaustive/src/api/resources/v2/client/Client.ts @@ -50,6 +50,7 @@ export class V2 { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/trace/exhaustive/src/api/resources/v2/resources/problem/client/Client.ts b/seed/ts-sdk/trace/exhaustive/src/api/resources/v2/resources/problem/client/Client.ts index 4e8e9d0ed9b..955c4321adb 100644 --- a/seed/ts-sdk/trace/exhaustive/src/api/resources/v2/resources/problem/client/Client.ts +++ b/seed/ts-sdk/trace/exhaustive/src/api/resources/v2/resources/problem/client/Client.ts @@ -59,6 +59,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -113,6 +114,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -169,6 +171,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -229,6 +232,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/trace/exhaustive/src/api/resources/v2/resources/v3/resources/problem/client/Client.ts b/seed/ts-sdk/trace/exhaustive/src/api/resources/v2/resources/v3/resources/problem/client/Client.ts index 547c92e2579..875a72e7e94 100644 --- a/seed/ts-sdk/trace/exhaustive/src/api/resources/v2/resources/v3/resources/problem/client/Client.ts +++ b/seed/ts-sdk/trace/exhaustive/src/api/resources/v2/resources/v3/resources/problem/client/Client.ts @@ -62,6 +62,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -116,6 +117,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -172,6 +174,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -232,6 +235,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/trace/exhaustive/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/trace/exhaustive/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/trace/exhaustive/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/trace/exhaustive/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/trace/no-custom-config/package.json b/seed/ts-sdk/trace/no-custom-config/package.json index bb222c89edf..de268a4c1a0 100644 --- a/seed/ts-sdk/trace/no-custom-config/package.json +++ b/seed/ts-sdk/trace/no-custom-config/package.json @@ -23,6 +23,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/trace/no-custom-config/src/api/resources/admin/client/Client.ts b/seed/ts-sdk/trace/no-custom-config/src/api/resources/admin/client/Client.ts index 7baa47243d8..0076c193f32 100644 --- a/seed/ts-sdk/trace/no-custom-config/src/api/resources/admin/client/Client.ts +++ b/seed/ts-sdk/trace/no-custom-config/src/api/resources/admin/client/Client.ts @@ -64,6 +64,7 @@ export class Admin { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -136,6 +137,7 @@ export class Admin { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -204,6 +206,7 @@ export class Admin { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -276,6 +279,7 @@ export class Admin { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -360,6 +364,7 @@ export class Admin { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -439,6 +444,7 @@ export class Admin { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -526,6 +532,7 @@ export class Admin { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -603,6 +610,7 @@ export class Admin { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/trace/no-custom-config/src/api/resources/homepage/client/Client.ts b/seed/ts-sdk/trace/no-custom-config/src/api/resources/homepage/client/Client.ts index 866b626af11..d584c827faa 100644 --- a/seed/ts-sdk/trace/no-custom-config/src/api/resources/homepage/client/Client.ts +++ b/seed/ts-sdk/trace/no-custom-config/src/api/resources/homepage/client/Client.ts @@ -54,6 +54,7 @@ export class Homepage { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -120,6 +121,7 @@ export class Homepage { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/trace/no-custom-config/src/api/resources/migration/client/Client.ts b/seed/ts-sdk/trace/no-custom-config/src/api/resources/migration/client/Client.ts index 5e5b811e5d5..453e1d293f0 100644 --- a/seed/ts-sdk/trace/no-custom-config/src/api/resources/migration/client/Client.ts +++ b/seed/ts-sdk/trace/no-custom-config/src/api/resources/migration/client/Client.ts @@ -61,6 +61,7 @@ export class Migration { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, "admin-key-header": adminKeyHeader, diff --git a/seed/ts-sdk/trace/no-custom-config/src/api/resources/playlist/client/Client.ts b/seed/ts-sdk/trace/no-custom-config/src/api/resources/playlist/client/Client.ts index 8cba59974ef..d8081631589 100644 --- a/seed/ts-sdk/trace/no-custom-config/src/api/resources/playlist/client/Client.ts +++ b/seed/ts-sdk/trace/no-custom-config/src/api/resources/playlist/client/Client.ts @@ -76,6 +76,7 @@ export class Playlist { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -176,6 +177,7 @@ export class Playlist { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -252,6 +254,7 @@ export class Playlist { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -345,6 +348,7 @@ export class Playlist { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -435,6 +439,7 @@ export class Playlist { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/trace/no-custom-config/src/api/resources/problem/client/Client.ts b/seed/ts-sdk/trace/no-custom-config/src/api/resources/problem/client/Client.ts index e5fa4f0cc1d..b5e15ef149d 100644 --- a/seed/ts-sdk/trace/no-custom-config/src/api/resources/problem/client/Client.ts +++ b/seed/ts-sdk/trace/no-custom-config/src/api/resources/problem/client/Client.ts @@ -103,6 +103,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -217,6 +218,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -283,6 +285,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -357,6 +360,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/trace/no-custom-config/src/api/resources/submission/client/Client.ts b/seed/ts-sdk/trace/no-custom-config/src/api/resources/submission/client/Client.ts index 859d24c6e82..cda112ae8e9 100644 --- a/seed/ts-sdk/trace/no-custom-config/src/api/resources/submission/client/Client.ts +++ b/seed/ts-sdk/trace/no-custom-config/src/api/resources/submission/client/Client.ts @@ -63,6 +63,7 @@ export class Submission { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -131,6 +132,7 @@ export class Submission { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -196,6 +198,7 @@ export class Submission { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -255,6 +258,7 @@ export class Submission { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/trace/no-custom-config/src/api/resources/sysprop/client/Client.ts b/seed/ts-sdk/trace/no-custom-config/src/api/resources/sysprop/client/Client.ts index e3c031ff494..2290de08edf 100644 --- a/seed/ts-sdk/trace/no-custom-config/src/api/resources/sysprop/client/Client.ts +++ b/seed/ts-sdk/trace/no-custom-config/src/api/resources/sysprop/client/Client.ts @@ -62,6 +62,7 @@ export class Sysprop { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -121,6 +122,7 @@ export class Sysprop { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/trace/no-custom-config/src/api/resources/v2/client/Client.ts b/seed/ts-sdk/trace/no-custom-config/src/api/resources/v2/client/Client.ts index dbeaa7b9877..b960b30c818 100644 --- a/seed/ts-sdk/trace/no-custom-config/src/api/resources/v2/client/Client.ts +++ b/seed/ts-sdk/trace/no-custom-config/src/api/resources/v2/client/Client.ts @@ -50,6 +50,7 @@ export class V2 { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/trace/no-custom-config/src/api/resources/v2/resources/problem/client/Client.ts b/seed/ts-sdk/trace/no-custom-config/src/api/resources/v2/resources/problem/client/Client.ts index 34a21aeadac..d032fafab19 100644 --- a/seed/ts-sdk/trace/no-custom-config/src/api/resources/v2/resources/problem/client/Client.ts +++ b/seed/ts-sdk/trace/no-custom-config/src/api/resources/v2/resources/problem/client/Client.ts @@ -58,6 +58,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -122,6 +123,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -190,6 +192,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -262,6 +265,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/trace/no-custom-config/src/api/resources/v2/resources/v3/resources/problem/client/Client.ts b/seed/ts-sdk/trace/no-custom-config/src/api/resources/v2/resources/v3/resources/problem/client/Client.ts index c6c3ea24fe0..c6a8aa75444 100644 --- a/seed/ts-sdk/trace/no-custom-config/src/api/resources/v2/resources/v3/resources/problem/client/Client.ts +++ b/seed/ts-sdk/trace/no-custom-config/src/api/resources/v2/resources/v3/resources/problem/client/Client.ts @@ -58,6 +58,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -122,6 +123,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -190,6 +192,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -262,6 +265,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/trace/no-custom-config/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/trace/no-custom-config/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/trace/no-custom-config/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/trace/no-custom-config/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/trace/no-zurg-no-throwing/package.json b/seed/ts-sdk/trace/no-zurg-no-throwing/package.json index bb222c89edf..de268a4c1a0 100644 --- a/seed/ts-sdk/trace/no-zurg-no-throwing/package.json +++ b/seed/ts-sdk/trace/no-zurg-no-throwing/package.json @@ -23,6 +23,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/trace/no-zurg-no-throwing/src/api/resources/admin/client/Client.ts b/seed/ts-sdk/trace/no-zurg-no-throwing/src/api/resources/admin/client/Client.ts index c96a0483866..da00643332e 100644 --- a/seed/ts-sdk/trace/no-zurg-no-throwing/src/api/resources/admin/client/Client.ts +++ b/seed/ts-sdk/trace/no-zurg-no-throwing/src/api/resources/admin/client/Client.ts @@ -60,6 +60,7 @@ export class Admin { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -117,6 +118,7 @@ export class Admin { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -170,6 +172,7 @@ export class Admin { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -227,6 +230,7 @@ export class Admin { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -298,6 +302,7 @@ export class Admin { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -364,6 +369,7 @@ export class Admin { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -434,6 +440,7 @@ export class Admin { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -496,6 +503,7 @@ export class Admin { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/trace/no-zurg-no-throwing/src/api/resources/homepage/client/Client.ts b/seed/ts-sdk/trace/no-zurg-no-throwing/src/api/resources/homepage/client/Client.ts index 8e2869a21ca..093b00112ba 100644 --- a/seed/ts-sdk/trace/no-zurg-no-throwing/src/api/resources/homepage/client/Client.ts +++ b/seed/ts-sdk/trace/no-zurg-no-throwing/src/api/resources/homepage/client/Client.ts @@ -54,6 +54,7 @@ export class Homepage { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -102,6 +103,7 @@ export class Homepage { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/trace/no-zurg-no-throwing/src/api/resources/migration/client/Client.ts b/seed/ts-sdk/trace/no-zurg-no-throwing/src/api/resources/migration/client/Client.ts index 07b2708b5fa..06b7846f102 100644 --- a/seed/ts-sdk/trace/no-zurg-no-throwing/src/api/resources/migration/client/Client.ts +++ b/seed/ts-sdk/trace/no-zurg-no-throwing/src/api/resources/migration/client/Client.ts @@ -59,6 +59,7 @@ export class Migration { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, "admin-key-header": adminKeyHeader, diff --git a/seed/ts-sdk/trace/no-zurg-no-throwing/src/api/resources/playlist/client/Client.ts b/seed/ts-sdk/trace/no-zurg-no-throwing/src/api/resources/playlist/client/Client.ts index b34e464f401..af37f394960 100644 --- a/seed/ts-sdk/trace/no-zurg-no-throwing/src/api/resources/playlist/client/Client.ts +++ b/seed/ts-sdk/trace/no-zurg-no-throwing/src/api/resources/playlist/client/Client.ts @@ -74,6 +74,7 @@ export class Playlist { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -156,6 +157,7 @@ export class Playlist { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -209,6 +211,7 @@ export class Playlist { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -277,6 +280,7 @@ export class Playlist { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -340,6 +344,7 @@ export class Playlist { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/trace/no-zurg-no-throwing/src/api/resources/problem/client/Client.ts b/seed/ts-sdk/trace/no-zurg-no-throwing/src/api/resources/problem/client/Client.ts index a9b41110220..41ec8e22491 100644 --- a/seed/ts-sdk/trace/no-zurg-no-throwing/src/api/resources/problem/client/Client.ts +++ b/seed/ts-sdk/trace/no-zurg-no-throwing/src/api/resources/problem/client/Client.ts @@ -101,6 +101,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -197,6 +198,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -248,6 +250,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -311,6 +314,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/trace/no-zurg-no-throwing/src/api/resources/submission/client/Client.ts b/seed/ts-sdk/trace/no-zurg-no-throwing/src/api/resources/submission/client/Client.ts index 88221a885b8..8c5f7fffcd9 100644 --- a/seed/ts-sdk/trace/no-zurg-no-throwing/src/api/resources/submission/client/Client.ts +++ b/seed/ts-sdk/trace/no-zurg-no-throwing/src/api/resources/submission/client/Client.ts @@ -63,6 +63,7 @@ export class Submission { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -115,6 +116,7 @@ export class Submission { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -165,6 +167,7 @@ export class Submission { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -216,6 +219,7 @@ export class Submission { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/trace/no-zurg-no-throwing/src/api/resources/sysprop/client/Client.ts b/seed/ts-sdk/trace/no-zurg-no-throwing/src/api/resources/sysprop/client/Client.ts index 36353800b84..1a183c45802 100644 --- a/seed/ts-sdk/trace/no-zurg-no-throwing/src/api/resources/sysprop/client/Client.ts +++ b/seed/ts-sdk/trace/no-zurg-no-throwing/src/api/resources/sysprop/client/Client.ts @@ -58,6 +58,7 @@ export class Sysprop { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -106,6 +107,7 @@ export class Sysprop { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/trace/no-zurg-no-throwing/src/api/resources/v2/client/Client.ts b/seed/ts-sdk/trace/no-zurg-no-throwing/src/api/resources/v2/client/Client.ts index 82175bd0484..b05be21fb87 100644 --- a/seed/ts-sdk/trace/no-zurg-no-throwing/src/api/resources/v2/client/Client.ts +++ b/seed/ts-sdk/trace/no-zurg-no-throwing/src/api/resources/v2/client/Client.ts @@ -50,6 +50,7 @@ export class V2 { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/trace/no-zurg-no-throwing/src/api/resources/v2/resources/problem/client/Client.ts b/seed/ts-sdk/trace/no-zurg-no-throwing/src/api/resources/v2/resources/problem/client/Client.ts index 05772979789..3f4eb0f4051 100644 --- a/seed/ts-sdk/trace/no-zurg-no-throwing/src/api/resources/v2/resources/problem/client/Client.ts +++ b/seed/ts-sdk/trace/no-zurg-no-throwing/src/api/resources/v2/resources/problem/client/Client.ts @@ -58,6 +58,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -106,6 +107,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -156,6 +158,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -210,6 +213,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/trace/no-zurg-no-throwing/src/api/resources/v2/resources/v3/resources/problem/client/Client.ts b/seed/ts-sdk/trace/no-zurg-no-throwing/src/api/resources/v2/resources/v3/resources/problem/client/Client.ts index 7268acbed00..4279db33c17 100644 --- a/seed/ts-sdk/trace/no-zurg-no-throwing/src/api/resources/v2/resources/v3/resources/problem/client/Client.ts +++ b/seed/ts-sdk/trace/no-zurg-no-throwing/src/api/resources/v2/resources/v3/resources/problem/client/Client.ts @@ -61,6 +61,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -109,6 +110,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -159,6 +161,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -213,6 +216,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/trace/no-zurg-no-throwing/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/trace/no-zurg-no-throwing/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/trace/no-zurg-no-throwing/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/trace/no-zurg-no-throwing/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/trace/no-zurg-trace/package.json b/seed/ts-sdk/trace/no-zurg-trace/package.json index bb222c89edf..de268a4c1a0 100644 --- a/seed/ts-sdk/trace/no-zurg-trace/package.json +++ b/seed/ts-sdk/trace/no-zurg-trace/package.json @@ -23,6 +23,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/trace/no-zurg-trace/src/api/resources/admin/client/Client.ts b/seed/ts-sdk/trace/no-zurg-trace/src/api/resources/admin/client/Client.ts index 65e76f531d6..662bb664662 100644 --- a/seed/ts-sdk/trace/no-zurg-trace/src/api/resources/admin/client/Client.ts +++ b/seed/ts-sdk/trace/no-zurg-trace/src/api/resources/admin/client/Client.ts @@ -61,6 +61,7 @@ export class Admin { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -131,6 +132,7 @@ export class Admin { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -197,6 +199,7 @@ export class Admin { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -267,6 +270,7 @@ export class Admin { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -351,6 +355,7 @@ export class Admin { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -430,6 +435,7 @@ export class Admin { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -513,6 +519,7 @@ export class Admin { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -588,6 +595,7 @@ export class Admin { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/trace/no-zurg-trace/src/api/resources/homepage/client/Client.ts b/seed/ts-sdk/trace/no-zurg-trace/src/api/resources/homepage/client/Client.ts index 6c14b3f93f3..87e10ffcb73 100644 --- a/seed/ts-sdk/trace/no-zurg-trace/src/api/resources/homepage/client/Client.ts +++ b/seed/ts-sdk/trace/no-zurg-trace/src/api/resources/homepage/client/Client.ts @@ -53,6 +53,7 @@ export class Homepage { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -114,6 +115,7 @@ export class Homepage { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/trace/no-zurg-trace/src/api/resources/migration/client/Client.ts b/seed/ts-sdk/trace/no-zurg-trace/src/api/resources/migration/client/Client.ts index ab79c024239..f9625a9ff6d 100644 --- a/seed/ts-sdk/trace/no-zurg-trace/src/api/resources/migration/client/Client.ts +++ b/seed/ts-sdk/trace/no-zurg-trace/src/api/resources/migration/client/Client.ts @@ -60,6 +60,7 @@ export class Migration { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, "admin-key-header": adminKeyHeader, diff --git a/seed/ts-sdk/trace/no-zurg-trace/src/api/resources/playlist/client/Client.ts b/seed/ts-sdk/trace/no-zurg-trace/src/api/resources/playlist/client/Client.ts index 71c59415681..949ca2d6c64 100644 --- a/seed/ts-sdk/trace/no-zurg-trace/src/api/resources/playlist/client/Client.ts +++ b/seed/ts-sdk/trace/no-zurg-trace/src/api/resources/playlist/client/Client.ts @@ -75,6 +75,7 @@ export class Playlist { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -170,6 +171,7 @@ export class Playlist { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -239,6 +241,7 @@ export class Playlist { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -320,6 +323,7 @@ export class Playlist { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -393,6 +397,7 @@ export class Playlist { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/trace/no-zurg-trace/src/api/resources/problem/client/Client.ts b/seed/ts-sdk/trace/no-zurg-trace/src/api/resources/problem/client/Client.ts index f30eeff419e..0e1e7729825 100644 --- a/seed/ts-sdk/trace/no-zurg-trace/src/api/resources/problem/client/Client.ts +++ b/seed/ts-sdk/trace/no-zurg-trace/src/api/resources/problem/client/Client.ts @@ -102,6 +102,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -211,6 +212,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -272,6 +274,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -346,6 +349,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/trace/no-zurg-trace/src/api/resources/submission/client/Client.ts b/seed/ts-sdk/trace/no-zurg-trace/src/api/resources/submission/client/Client.ts index fbd68395fc4..66e0bd7dcd8 100644 --- a/seed/ts-sdk/trace/no-zurg-trace/src/api/resources/submission/client/Client.ts +++ b/seed/ts-sdk/trace/no-zurg-trace/src/api/resources/submission/client/Client.ts @@ -62,6 +62,7 @@ export class Submission { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -125,6 +126,7 @@ export class Submission { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -185,6 +187,7 @@ export class Submission { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -244,6 +247,7 @@ export class Submission { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/trace/no-zurg-trace/src/api/resources/sysprop/client/Client.ts b/seed/ts-sdk/trace/no-zurg-trace/src/api/resources/sysprop/client/Client.ts index 80eb2ee0c28..249a466b791 100644 --- a/seed/ts-sdk/trace/no-zurg-trace/src/api/resources/sysprop/client/Client.ts +++ b/seed/ts-sdk/trace/no-zurg-trace/src/api/resources/sysprop/client/Client.ts @@ -59,6 +59,7 @@ export class Sysprop { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -118,6 +119,7 @@ export class Sysprop { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/trace/no-zurg-trace/src/api/resources/v2/client/Client.ts b/seed/ts-sdk/trace/no-zurg-trace/src/api/resources/v2/client/Client.ts index dbeaa7b9877..b960b30c818 100644 --- a/seed/ts-sdk/trace/no-zurg-trace/src/api/resources/v2/client/Client.ts +++ b/seed/ts-sdk/trace/no-zurg-trace/src/api/resources/v2/client/Client.ts @@ -50,6 +50,7 @@ export class V2 { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/trace/no-zurg-trace/src/api/resources/v2/resources/problem/client/Client.ts b/seed/ts-sdk/trace/no-zurg-trace/src/api/resources/v2/resources/problem/client/Client.ts index 7a285f1145e..21601b6c6c3 100644 --- a/seed/ts-sdk/trace/no-zurg-trace/src/api/resources/v2/resources/problem/client/Client.ts +++ b/seed/ts-sdk/trace/no-zurg-trace/src/api/resources/v2/resources/problem/client/Client.ts @@ -57,6 +57,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -116,6 +117,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -179,6 +181,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -246,6 +249,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/trace/no-zurg-trace/src/api/resources/v2/resources/v3/resources/problem/client/Client.ts b/seed/ts-sdk/trace/no-zurg-trace/src/api/resources/v2/resources/v3/resources/problem/client/Client.ts index 06168349c35..ba340e6de5b 100644 --- a/seed/ts-sdk/trace/no-zurg-trace/src/api/resources/v2/resources/v3/resources/problem/client/Client.ts +++ b/seed/ts-sdk/trace/no-zurg-trace/src/api/resources/v2/resources/v3/resources/problem/client/Client.ts @@ -57,6 +57,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -116,6 +117,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -179,6 +181,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -246,6 +249,7 @@ export class Problem { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/trace", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/trace/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/trace/no-zurg-trace/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/trace/no-zurg-trace/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/trace/no-zurg-trace/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/trace/no-zurg-trace/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/undiscriminated-unions/no-custom-config/package.json b/seed/ts-sdk/undiscriminated-unions/no-custom-config/package.json index 0d858b4e1f6..6fec1c38d1f 100644 --- a/seed/ts-sdk/undiscriminated-unions/no-custom-config/package.json +++ b/seed/ts-sdk/undiscriminated-unions/no-custom-config/package.json @@ -22,6 +22,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/undiscriminated-unions/no-custom-config/src/api/resources/union/client/Client.ts b/seed/ts-sdk/undiscriminated-unions/no-custom-config/src/api/resources/union/client/Client.ts index 0562b0021aa..1ba34e8f226 100644 --- a/seed/ts-sdk/undiscriminated-unions/no-custom-config/src/api/resources/union/client/Client.ts +++ b/seed/ts-sdk/undiscriminated-unions/no-custom-config/src/api/resources/union/client/Client.ts @@ -44,6 +44,7 @@ export class Union { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/undiscriminated-unions", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/undiscriminated-unions/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -99,6 +100,7 @@ export class Union { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/undiscriminated-unions", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/undiscriminated-unions/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/undiscriminated-unions/no-custom-config/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/undiscriminated-unions/no-custom-config/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/undiscriminated-unions/no-custom-config/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/undiscriminated-unions/no-custom-config/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/undiscriminated-unions/skip-response-validation/package.json b/seed/ts-sdk/undiscriminated-unions/skip-response-validation/package.json index 0d858b4e1f6..6fec1c38d1f 100644 --- a/seed/ts-sdk/undiscriminated-unions/skip-response-validation/package.json +++ b/seed/ts-sdk/undiscriminated-unions/skip-response-validation/package.json @@ -22,6 +22,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/undiscriminated-unions/skip-response-validation/src/api/resources/union/client/Client.ts b/seed/ts-sdk/undiscriminated-unions/skip-response-validation/src/api/resources/union/client/Client.ts index a8b93cd9f8a..6362b29ff08 100644 --- a/seed/ts-sdk/undiscriminated-unions/skip-response-validation/src/api/resources/union/client/Client.ts +++ b/seed/ts-sdk/undiscriminated-unions/skip-response-validation/src/api/resources/union/client/Client.ts @@ -44,6 +44,7 @@ export class Union { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/undiscriminated-unions", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/undiscriminated-unions/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -100,6 +101,7 @@ export class Union { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/undiscriminated-unions", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/undiscriminated-unions/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/undiscriminated-unions/skip-response-validation/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/undiscriminated-unions/skip-response-validation/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/undiscriminated-unions/skip-response-validation/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/undiscriminated-unions/skip-response-validation/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/unions/package.json b/seed/ts-sdk/unions/package.json index 7ada2741d58..0852e9077bf 100644 --- a/seed/ts-sdk/unions/package.json +++ b/seed/ts-sdk/unions/package.json @@ -22,6 +22,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/unions/src/api/resources/union/client/Client.ts b/seed/ts-sdk/unions/src/api/resources/union/client/Client.ts index 08f47ea7668..f99925d968c 100644 --- a/seed/ts-sdk/unions/src/api/resources/union/client/Client.ts +++ b/seed/ts-sdk/unions/src/api/resources/union/client/Client.ts @@ -41,6 +41,7 @@ export class Union { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/unions", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/unions/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -100,6 +101,7 @@ export class Union { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/unions", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/unions/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/unions/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/unions/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/unions/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/unions/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/unknown/no-custom-config/package.json b/seed/ts-sdk/unknown/no-custom-config/package.json index adb4d5bae0d..192b71e8bb1 100644 --- a/seed/ts-sdk/unknown/no-custom-config/package.json +++ b/seed/ts-sdk/unknown/no-custom-config/package.json @@ -22,6 +22,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/unknown/no-custom-config/src/api/resources/unknown/client/Client.ts b/seed/ts-sdk/unknown/no-custom-config/src/api/resources/unknown/client/Client.ts index 43be4a1d47f..c76ae27beb7 100644 --- a/seed/ts-sdk/unknown/no-custom-config/src/api/resources/unknown/client/Client.ts +++ b/seed/ts-sdk/unknown/no-custom-config/src/api/resources/unknown/client/Client.ts @@ -43,6 +43,7 @@ export class Unknown { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/unknown", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/unknown/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -102,6 +103,7 @@ export class Unknown { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/unknown", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/unknown/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/unknown/no-custom-config/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/unknown/no-custom-config/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/unknown/no-custom-config/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/unknown/no-custom-config/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/unknown/unknown-as-any/package.json b/seed/ts-sdk/unknown/unknown-as-any/package.json index adb4d5bae0d..192b71e8bb1 100644 --- a/seed/ts-sdk/unknown/unknown-as-any/package.json +++ b/seed/ts-sdk/unknown/unknown-as-any/package.json @@ -22,6 +22,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/unknown/unknown-as-any/src/api/resources/unknown/client/Client.ts b/seed/ts-sdk/unknown/unknown-as-any/src/api/resources/unknown/client/Client.ts index 1756298f050..a7984bda4db 100644 --- a/seed/ts-sdk/unknown/unknown-as-any/src/api/resources/unknown/client/Client.ts +++ b/seed/ts-sdk/unknown/unknown-as-any/src/api/resources/unknown/client/Client.ts @@ -43,6 +43,7 @@ export class Unknown { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/unknown", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/unknown/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -102,6 +103,7 @@ export class Unknown { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/unknown", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/unknown/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/unknown/unknown-as-any/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/unknown/unknown-as-any/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/unknown/unknown-as-any/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/unknown/unknown-as-any/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/validation/package.json b/seed/ts-sdk/validation/package.json index 7c19426f447..d4f2119f1d6 100644 --- a/seed/ts-sdk/validation/package.json +++ b/seed/ts-sdk/validation/package.json @@ -22,6 +22,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/validation/src/Client.ts b/seed/ts-sdk/validation/src/Client.ts index a51e985e5fb..85b24a8ec27 100644 --- a/seed/ts-sdk/validation/src/Client.ts +++ b/seed/ts-sdk/validation/src/Client.ts @@ -49,6 +49,7 @@ export class SeedValidationClient { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/validation", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/validation/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, @@ -117,6 +118,7 @@ export class SeedValidationClient { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/validation", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/validation/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/validation/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/validation/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/validation/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/validation/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/variables/package.json b/seed/ts-sdk/variables/package.json index eff9ef6ea5a..78a4eb6502c 100644 --- a/seed/ts-sdk/variables/package.json +++ b/seed/ts-sdk/variables/package.json @@ -22,6 +22,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/variables/src/api/resources/service/client/Client.ts b/seed/ts-sdk/variables/src/api/resources/service/client/Client.ts index 61b9c97eb94..dab52ad4340 100644 --- a/seed/ts-sdk/variables/src/api/resources/service/client/Client.ts +++ b/seed/ts-sdk/variables/src/api/resources/service/client/Client.ts @@ -42,6 +42,7 @@ export class Service { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/variables", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/variables/0.0.1", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, }, diff --git a/seed/ts-sdk/variables/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/variables/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/variables/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/variables/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/version-no-default/package.json b/seed/ts-sdk/version-no-default/package.json index 98cfd2da856..7222f60f335 100644 --- a/seed/ts-sdk/version-no-default/package.json +++ b/seed/ts-sdk/version-no-default/package.json @@ -22,6 +22,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/version-no-default/src/api/resources/user/client/Client.ts b/seed/ts-sdk/version-no-default/src/api/resources/user/client/Client.ts index db657b98ca0..b0d12252d38 100644 --- a/seed/ts-sdk/version-no-default/src/api/resources/user/client/Client.ts +++ b/seed/ts-sdk/version-no-default/src/api/resources/user/client/Client.ts @@ -48,6 +48,7 @@ export class User { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/version-no-default", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/version-no-default/0.0.1", "X-API-Version": requestOptions?.xApiVersion ?? this._options.xApiVersion, "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, diff --git a/seed/ts-sdk/version-no-default/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/version-no-default/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/version-no-default/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/version-no-default/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/seed/ts-sdk/version/package.json b/seed/ts-sdk/version/package.json index 2e1abf5c203..622af7c59d1 100644 --- a/seed/ts-sdk/version/package.json +++ b/seed/ts-sdk/version/package.json @@ -22,6 +22,7 @@ "@types/url-join": "4.0.1", "@types/qs": "6.9.8", "@types/node-fetch": "2.6.9", + "fetch-mock-jest": "^1.5.1", "jest": "29.7.0", "@types/jest": "29.5.5", "ts-jest": "29.1.1", diff --git a/seed/ts-sdk/version/src/api/resources/user/client/Client.ts b/seed/ts-sdk/version/src/api/resources/user/client/Client.ts index e4221408041..6a0c5ec9a9a 100644 --- a/seed/ts-sdk/version/src/api/resources/user/client/Client.ts +++ b/seed/ts-sdk/version/src/api/resources/user/client/Client.ts @@ -48,6 +48,7 @@ export class User { "X-Fern-Language": "JavaScript", "X-Fern-SDK-Name": "@fern/version", "X-Fern-SDK-Version": "0.0.1", + "User-Agent": "@fern/version/0.0.1", "X-API-Version": requestOptions?.xApiVersion ?? this._options?.xApiVersion ?? "2.0.0", "X-Fern-Runtime": core.RUNTIME.type, "X-Fern-Runtime-Version": core.RUNTIME.version, diff --git a/seed/ts-sdk/version/tests/unit/fetcher/Fetcher.test.ts b/seed/ts-sdk/version/tests/unit/fetcher/Fetcher.test.ts index db045262b60..0e14a8c77f8 100644 --- a/seed/ts-sdk/version/tests/unit/fetcher/Fetcher.test.ts +++ b/seed/ts-sdk/version/tests/unit/fetcher/Fetcher.test.ts @@ -1,28 +1,7 @@ +import fetchMock from "fetch-mock-jest"; import { Fetcher, fetcherImpl } from "../../../src/core/fetcher/Fetcher"; describe("Test fetcherImpl", () => { - let mockCreateUrl: jest.Mock; - let mockGetBody: jest.Mock; - let mockGetFetchFn: jest.Mock; - let mockRequestWithRetries: jest.Mock; - let mockGetResponseBody: jest.Mock; - - beforeEach(() => { - mockCreateUrl = jest.fn(); - mockGetBody = jest.fn(); - mockGetFetchFn = jest.fn(); - mockRequestWithRetries = jest.fn(); - mockGetResponseBody = jest.fn(); - - jest.mock("../../../src/core/fetcher/Fetcher", () => ({ - createUrl: mockCreateUrl, - getBody: mockGetBody, - getFetchFn: mockGetFetchFn, - requestWithRetries: mockRequestWithRetries, - getResponseBody: mockGetResponseBody, - })); - }); - it("should handle successful request", async () => { const mockArgs: Fetcher.Args = { url: "https://httpbin.org/post", @@ -33,15 +12,14 @@ describe("Test fetcherImpl", () => { requestType: "json", }; - mockCreateUrl.mockReturnValue("https://test.com"); - mockGetBody.mockResolvedValue(JSON.stringify({ data: "test" })); - mockGetFetchFn.mockResolvedValue(() => Promise.resolve()); - mockRequestWithRetries.mockResolvedValue({ status: 200 }); - mockGetResponseBody.mockResolvedValue({ result: "success" }); + fetchMock.mock("https://httpbin.org/post", 200, { + response: JSON.stringify({ data: "test" }), + }); const result = await fetcherImpl(mockArgs); expect(result.ok).toBe(true); - // @ts-expect-error - expect(result.body.json).toEqual({ data: "test" }); + if (result.ok) { + expect(result.body).toEqual({ data: "test" }); + } }); }); diff --git a/test-definitions/fern/apis/grpc-proto/generators.yml b/test-definitions/fern/apis/grpc-proto/generators.yml new file mode 100644 index 00000000000..902c4f50168 --- /dev/null +++ b/test-definitions/fern/apis/grpc-proto/generators.yml @@ -0,0 +1,5 @@ +api: + - proto: + root: proto + target: proto/user/v1/user.proto + local-generation: true \ No newline at end of file diff --git a/test-definitions/fern/apis/grpc-proto/proto/google/api/annotations.proto b/test-definitions/fern/apis/grpc-proto/proto/google/api/annotations.proto new file mode 100644 index 00000000000..8ff42098404 --- /dev/null +++ b/test-definitions/fern/apis/grpc-proto/proto/google/api/annotations.proto @@ -0,0 +1,31 @@ +// Copyright 2015 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/api/http.proto"; +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "AnnotationsProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // See `HttpRule`. + HttpRule http = 72295728; +} \ No newline at end of file diff --git a/test-definitions/fern/apis/grpc-proto/proto/google/api/field_behavior.proto b/test-definitions/fern/apis/grpc-proto/proto/google/api/field_behavior.proto new file mode 100644 index 00000000000..128799c558d --- /dev/null +++ b/test-definitions/fern/apis/grpc-proto/proto/google/api/field_behavior.proto @@ -0,0 +1,104 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "FieldBehaviorProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.FieldOptions { + // A designation of a specific field behavior (required, output only, etc.) + // in protobuf messages. + // + // Examples: + // + // string name = 1 [(google.api.field_behavior) = REQUIRED]; + // State state = 1 [(google.api.field_behavior) = OUTPUT_ONLY]; + // google.protobuf.Duration ttl = 1 + // [(google.api.field_behavior) = INPUT_ONLY]; + // google.protobuf.Timestamp expire_time = 1 + // [(google.api.field_behavior) = OUTPUT_ONLY, + // (google.api.field_behavior) = IMMUTABLE]; + repeated google.api.FieldBehavior field_behavior = 1052; +} + +// An indicator of the behavior of a given field (for example, that a field +// is required in requests, or given as output but ignored as input). +// This **does not** change the behavior in protocol buffers itself; it only +// denotes the behavior and may affect how API tooling handles the field. +// +// Note: This enum **may** receive new values in the future. +enum FieldBehavior { + // Conventional default for enums. Do not use this. + FIELD_BEHAVIOR_UNSPECIFIED = 0; + + // Specifically denotes a field as optional. + // While all fields in protocol buffers are optional, this may be specified + // for emphasis if appropriate. + OPTIONAL = 1; + + // Denotes a field as required. + // This indicates that the field **must** be provided as part of the request, + // and failure to do so will cause an error (usually `INVALID_ARGUMENT`). + REQUIRED = 2; + + // Denotes a field as output only. + // This indicates that the field is provided in responses, but including the + // field in a request does nothing (the server *must* ignore it and + // *must not* throw an error as a result of the field's presence). + OUTPUT_ONLY = 3; + + // Denotes a field as input only. + // This indicates that the field is provided in requests, and the + // corresponding field is not included in output. + INPUT_ONLY = 4; + + // Denotes a field as immutable. + // This indicates that the field may be set once in a request to create a + // resource, but may not be changed thereafter. + IMMUTABLE = 5; + + // Denotes that a (repeated) field is an unordered list. + // This indicates that the service may provide the elements of the list + // in any arbitrary order, rather than the order the user originally + // provided. Additionally, the list's order may or may not be stable. + UNORDERED_LIST = 6; + + // Denotes that this field returns a non-empty default value if not set. + // This indicates that if the user provides the empty value in a request, + // a non-empty value will be returned. The user will not be aware of what + // non-empty value to expect. + NON_EMPTY_DEFAULT = 7; + + // Denotes that the field in a resource (a message annotated with + // google.api.resource) is used in the resource name to uniquely identify the + // resource. For AIP-compliant APIs, this should only be applied to the + // `name` field on the resource. + // + // This behavior should not be applied to references to other resources within + // the message. + // + // The identifier field of resources often have different field behavior + // depending on the request it is embedded in (e.g. for Create methods name + // is optional and unused, while for Update methods it is required). Instead + // of method-specific annotations, only `IDENTIFIER` is required. + IDENTIFIER = 8; +} \ No newline at end of file diff --git a/test-definitions/fern/apis/grpc-proto/proto/google/api/http.proto b/test-definitions/fern/apis/grpc-proto/proto/google/api/http.proto new file mode 100644 index 00000000000..c8392381eb9 --- /dev/null +++ b/test-definitions/fern/apis/grpc-proto/proto/google/api/http.proto @@ -0,0 +1,379 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "HttpProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Defines the HTTP configuration for an API service. It contains a list of +// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method +// to one or more HTTP REST API methods. +message Http { + // A list of HTTP configuration rules that apply to individual API methods. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated HttpRule rules = 1; + + // When set to true, URL path parameters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // + // The default behavior is to not decode RFC 6570 reserved characters in multi + // segment matches. + bool fully_decode_reserved_expansion = 2; +} + +// # gRPC Transcoding +// +// gRPC Transcoding is a feature for mapping between a gRPC method and one or +// more HTTP REST endpoints. It allows developers to build a single API service +// that supports both gRPC APIs and REST APIs. Many systems, including [Google +// APIs](https://github.com/googleapis/googleapis), +// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC +// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), +// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature +// and use it for large scale production services. +// +// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies +// how different portions of the gRPC request message are mapped to the URL +// path, URL query parameters, and HTTP request body. It also controls how the +// gRPC response message is mapped to the HTTP response body. `HttpRule` is +// typically specified as an `google.api.http` annotation on the gRPC method. +// +// Each mapping specifies a URL path template and an HTTP method. The path +// template may refer to one or more fields in the gRPC request message, as long +// as each field is a non-repeated field with a primitive (non-message) type. +// The path template controls how fields of the request message are mapped to +// the URL path. +// +// Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/{name=messages/*}" +// }; +// } +// } +// message GetMessageRequest { +// string name = 1; // Mapped to URL path. +// } +// message Message { +// string text = 1; // The resource content. +// } +// +// This enables an HTTP REST to gRPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` +// +// Any fields in the request message which are not bound by the path template +// automatically become HTTP query parameters if there is no HTTP request body. +// For example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get:"/v1/messages/{message_id}" +// }; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // Mapped to URL path. +// int64 revision = 2; // Mapped to URL query parameter `revision`. +// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. +// } +// +// This enables a HTTP JSON to RPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | +// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: +// "foo"))` +// +// Note that fields which are mapped to URL query parameters must have a +// primitive type or a repeated primitive type or a non-repeated message type. +// In the case of a repeated type, the parameter can be repeated in the URL +// as `...?param=A¶m=B`. In the case of a message type, each field of the +// message is mapped to a separate parameter, such as +// `...?foo.a=A&foo.b=B&foo.c=C`. +// +// For HTTP methods that allow a request body, the `body` field +// specifies the mapping. Consider a REST update method on the +// message resource collection: +// +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } +// +// The following HTTP JSON to RPC mapping is enabled, where the +// representation of the JSON in the request body is determined by +// protos JSON encoding: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" message { text: "Hi!" })` +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. This enables the following alternative definition of +// the update method: +// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } +// +// +// The following HTTP JSON to RPC mapping is enabled: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" text: "Hi!")` +// +// Note that when using `*` in the body mapping, it is not possible to +// have HTTP parameters, as all fields not bound by the path end in +// the body. This makes this option more rarely used in practice when +// defining REST APIs. The common usage of `*` is in custom methods +// which don't use the URL at all for transferring data. +// +// It is possible to define multiple HTTP methods for one RPC by using +// the `additional_bindings` option. Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } +// +// This enables the following two alternative HTTP JSON to RPC mappings: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` +// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: +// "123456")` +// +// ## Rules for HTTP mapping +// +// 1. Leaf request fields (recursive expansion nested messages in the request +// message) are classified into three categories: +// - Fields referred by the path template. They are passed via the URL path. +// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They +// are passed via the HTTP +// request body. +// - All other fields are passed via the URL query parameters, and the +// parameter name is the field path in the request message. A repeated +// field can be represented as multiple query parameters under the same +// name. +// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL +// query parameter, all fields +// are passed via URL path and HTTP request body. +// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP +// request body, all +// fields are passed via URL path and URL query parameters. +// +// ### Path template syntax +// +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +// +// The syntax `*` matches a single URL path segment. The syntax `**` matches +// zero or more URL path segments, which must be the last part of the URL path +// except the `Verb`. +// +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable +// matches a single path segment, its template may be omitted, e.g. `{var}` +// is equivalent to `{var=*}`. +// +// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` +// contains any reserved character, such characters should be percent-encoded +// before the matching. +// +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path on the client +// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The +// server side does the reverse decoding. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{var}`. +// +// If a variable contains multiple path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path on the +// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. +// The server side does the reverse decoding, except "%2F" and "%2f" are left +// unchanged. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{+var}`. +// +// ## Using gRPC API Service Configuration +// +// gRPC API Service Configuration (service config) is a configuration language +// for configuring a gRPC service to become a user-facing product. The +// service config is simply the YAML representation of the `google.api.Service` +// proto message. +// +// As an alternative to annotating your proto file, you can configure gRPC +// transcoding in your service config YAML files. You do this by specifying a +// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same +// effect as the proto annotation. This can be particularly useful if you +// have a proto that is reused in multiple services. Note that any transcoding +// specified in the service config will override any matching transcoding +// configuration in the proto. +// +// Example: +// +// http: +// rules: +// # Selects a gRPC method and applies HttpRule to it. +// - selector: example.v1.Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} +// +// ## Special notes +// +// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the +// proto to JSON conversion must follow the [proto3 +// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). +// +// While the single segment variable follows the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String +// Expansion, the multi segment variable **does not** follow RFC 6570 Section +// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding +// for multi segment variables. +// +// The path variables **must not** refer to any repeated or mapped field, +// because client libraries are not capable of handling such variable expansion. +// +// The path variables **must not** capture the leading "/" character. The reason +// is that the most common use case "{var}" does not capture the leading "/" +// character. For consistency, all path variables must share the same behavior. +// +// Repeated message fields must not be mapped to URL query parameters, because +// no client library can support such complicated mapping. +// +// If an API needs to use a JSON array for request or response body, it can map +// the request or response body to a repeated field. However, some gRPC +// Transcoding implementations may not support this feature. +message HttpRule { + // Selects a method to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax + // details. + string selector = 1; + + // Determines the URL pattern is matched by this rules. This pattern can be + // used with any of the {get|put|post|delete|patch} methods. A custom method + // can be defined using the 'custom' field. + oneof pattern { + // Maps to HTTP GET. Used for listing and getting information about + // resources. + string get = 2; + + // Maps to HTTP PUT. Used for replacing a resource. + string put = 3; + + // Maps to HTTP POST. Used for creating a resource or performing an action. + string post = 4; + + // Maps to HTTP DELETE. Used for deleting a resource. + string delete = 5; + + // Maps to HTTP PATCH. Used for updating a resource. + string patch = 6; + + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. + CustomHttpPattern custom = 8; + } + + // The name of the request field whose value is mapped to the HTTP request + // body, or `*` for mapping all request fields not captured by the path + // pattern to the HTTP body, or omitted for not having any HTTP request body. + // + // NOTE: the referred field must be present at the top-level of the request + // message type. + string body = 7; + + // Optional. The name of the response field whose value is mapped to the HTTP + // response body. When omitted, the entire response message will be used + // as the HTTP response body. + // + // NOTE: The referred field must be present at the top-level of the response + // message type. + string response_body = 12; + + // Additional HTTP bindings for the selector. Nested bindings must + // not contain an `additional_bindings` field themselves (that is, + // the nesting may only be one level deep). + repeated HttpRule additional_bindings = 11; +} + +// A custom pattern is used for defining custom HTTP verb. +message CustomHttpPattern { + // The name of this custom HTTP verb. + string kind = 1; + + // The path matched by this custom verb. + string path = 2; +} \ No newline at end of file diff --git a/test-definitions/fern/apis/grpc-proto/proto/user/v1/user.proto b/test-definitions/fern/apis/grpc-proto/proto/user/v1/user.proto new file mode 100644 index 00000000000..a3ad0195bd4 --- /dev/null +++ b/test-definitions/fern/apis/grpc-proto/proto/user/v1/user.proto @@ -0,0 +1,39 @@ +syntax = "proto3"; + +package user.v1; + +import "google/api/annotations.proto"; +import "google/api/field_behavior.proto"; +import "google/protobuf/struct.proto"; + +option go_package = "user/v1"; +option csharp_namespace = "User.V1"; + +message UserModel { + string username = 1; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateRequest { + string username = 1; + string email = 2; + uint32 age = 3; + float weight = 4; + google.protobuf.Struct metadata = 5; +} + +message CreateResponse { + UserModel user = 1; +} + +service User { + rpc Create(CreateRequest) returns (CreateResponse) { + option (google.api.http) = { + post: "/users" + body: "*" + }; + } +} \ No newline at end of file diff --git a/test-definitions/fern/apis/grpc/definition/api.yml b/test-definitions/fern/apis/grpc/definition/api.yml new file mode 100644 index 00000000000..15a9f823724 --- /dev/null +++ b/test-definitions/fern/apis/grpc/definition/api.yml @@ -0,0 +1,4 @@ +name: api + +error-discrimination: + strategy: status-code \ No newline at end of file diff --git a/test-definitions/fern/apis/grpc/definition/user.yml b/test-definitions/fern/apis/grpc/definition/user.yml new file mode 100644 index 00000000000..80223300afc --- /dev/null +++ b/test-definitions/fern/apis/grpc/definition/user.yml @@ -0,0 +1,61 @@ +types: + Metadata: + type: map> + encoding: + proto: + type: google.protobuf.Struct + + MetadataValue: + discriminated: false + union: + - double + - string + - boolean + - list + encoding: + proto: + type: google.protobuf.Value + + User: + properties: + id: string + username: string + email: optional + age: optional + weight: optional + metadata: optional + + CreateUserResponse: + properties: + user: User + +service: + auth: false + base-path: / + transport: + grpc: + service-name: UserService + endpoints: + createUser: + method: POST + path: /users + request: + name: CreateUserRequest + body: + properties: + username: string + email: optional + age: optional + weight: optional + response: CreateUserResponse + + getUser: + method: GET + path: /users + request: + name: GetUserRequest + query-parameters: + username: optional + age: optional + weight: optional + response: User diff --git a/test-definitions/fern/apis/grpc/generators.yml b/test-definitions/fern/apis/grpc/generators.yml new file mode 100644 index 00000000000..9e26dfeeb6e --- /dev/null +++ b/test-definitions/fern/apis/grpc/generators.yml @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 9dd5d6e5512..f0ad237195b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -117,6 +117,16 @@ __metadata: languageName: node linkType: hard +"@babel/code-frame@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/code-frame@npm:7.24.7" + dependencies: + "@babel/highlight": ^7.24.7 + picocolors: ^1.0.0 + checksum: 830e62cd38775fdf84d612544251ce773d544a8e63df667728cc9e0126eeef14c6ebda79be0f0bc307e8318316b7f58c27ce86702e0a1f5c321d842eb38ffda4 + languageName: node + linkType: hard + "@babel/compat-data@npm:^7.19.1": version: 7.19.1 resolution: "@babel/compat-data@npm:7.19.1" @@ -145,6 +155,36 @@ __metadata: languageName: node linkType: hard +"@babel/compat-data@npm:^7.25.2": + version: 7.25.2 + resolution: "@babel/compat-data@npm:7.25.2" + checksum: b61bc9da7cfe249f19d08da00f4f0c20550cd9ad5bffcde787c2bf61a8a6fa5b66d92bbd89031f3a6e5495a799a2a2499f2947b6cc7964be41979377473ab132 + languageName: node + linkType: hard + +"@babel/core@npm:^7.0.0": + version: 7.25.2 + resolution: "@babel/core@npm:7.25.2" + dependencies: + "@ampproject/remapping": ^2.2.0 + "@babel/code-frame": ^7.24.7 + "@babel/generator": ^7.25.0 + "@babel/helper-compilation-targets": ^7.25.2 + "@babel/helper-module-transforms": ^7.25.2 + "@babel/helpers": ^7.25.0 + "@babel/parser": ^7.25.0 + "@babel/template": ^7.25.0 + "@babel/traverse": ^7.25.2 + "@babel/types": ^7.25.2 + convert-source-map: ^2.0.0 + debug: ^4.1.0 + gensync: ^1.0.0-beta.2 + json5: ^2.2.3 + semver: ^6.3.1 + checksum: 9a1ef604a7eb62195f70f9370cec45472a08114e3934e3eaaedee8fd754edf0730e62347c7b4b5e67d743ce57b5bb8cf3b92459482ca94d06e06246ef021390a + languageName: node + linkType: hard + "@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3": version: 7.19.1 resolution: "@babel/core@npm:7.19.1" @@ -249,6 +289,18 @@ __metadata: languageName: node linkType: hard +"@babel/generator@npm:^7.25.0": + version: 7.25.0 + resolution: "@babel/generator@npm:7.25.0" + dependencies: + "@babel/types": ^7.25.0 + "@jridgewell/gen-mapping": ^0.3.5 + "@jridgewell/trace-mapping": ^0.3.25 + jsesc: ^2.5.1 + checksum: bf25649dde4068bff8e387319bf820f2cb3b1af7b8c0cfba0bd90880656427c8bad96cd5cb6db7058d20cffe93149ee59da16567018ceaa21ecaefbf780a785c + languageName: node + linkType: hard + "@babel/helper-annotate-as-pure@npm:^7.18.6": version: 7.18.6 resolution: "@babel/helper-annotate-as-pure@npm:7.18.6" @@ -316,6 +368,19 @@ __metadata: languageName: node linkType: hard +"@babel/helper-compilation-targets@npm:^7.25.2": + version: 7.25.2 + resolution: "@babel/helper-compilation-targets@npm:7.25.2" + dependencies: + "@babel/compat-data": ^7.25.2 + "@babel/helper-validator-option": ^7.24.8 + browserslist: ^4.23.1 + lru-cache: ^5.1.1 + semver: ^6.3.1 + checksum: aed33c5496cb9db4b5e2d44e26bf8bc474074cc7f7bb5ebe1d4a20fdeb362cb3ba9e1596ca18c7484bcd6e5c3a155ab975e420d520c0ae60df81f9de04d0fd16 + languageName: node + linkType: hard + "@babel/helper-create-class-features-plugin@npm:^7.22.15": version: 7.22.15 resolution: "@babel/helper-create-class-features-plugin@npm:7.22.15" @@ -504,6 +569,16 @@ __metadata: languageName: node linkType: hard +"@babel/helper-module-imports@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-module-imports@npm:7.24.7" + dependencies: + "@babel/traverse": ^7.24.7 + "@babel/types": ^7.24.7 + checksum: 8ac15d96d262b8940bc469052a048e06430bba1296369be695fabdf6799f201dd0b00151762b56012a218464e706bc033f27c07f6cec20c6f8f5fd6543c67054 + languageName: node + linkType: hard + "@babel/helper-module-transforms@npm:^7.19.0": version: 7.19.0 resolution: "@babel/helper-module-transforms@npm:7.19.0" @@ -550,6 +625,20 @@ __metadata: languageName: node linkType: hard +"@babel/helper-module-transforms@npm:^7.25.2": + version: 7.25.2 + resolution: "@babel/helper-module-transforms@npm:7.25.2" + dependencies: + "@babel/helper-module-imports": ^7.24.7 + "@babel/helper-simple-access": ^7.24.7 + "@babel/helper-validator-identifier": ^7.24.7 + "@babel/traverse": ^7.25.2 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 282d4e3308df6746289e46e9c39a0870819630af5f84d632559171e4fae6045684d771a65f62df3d569e88ccf81dc2def78b8338a449ae3a94bb421aa14fc367 + languageName: node + linkType: hard + "@babel/helper-optimise-call-expression@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-optimise-call-expression@npm:7.22.5" @@ -650,6 +739,16 @@ __metadata: languageName: node linkType: hard +"@babel/helper-simple-access@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-simple-access@npm:7.24.7" + dependencies: + "@babel/traverse": ^7.24.7 + "@babel/types": ^7.24.7 + checksum: ddbf55f9dea1900213f2a1a8500fabfd21c5a20f44dcfa957e4b0d8638c730f88751c77f678644f754f1a1dc73f4eb8b766c300deb45a9daad000e4247957819 + languageName: node + linkType: hard + "@babel/helper-skip-transparent-expression-wrappers@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.22.5" @@ -698,6 +797,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-string-parser@npm:^7.24.8": + version: 7.24.8 + resolution: "@babel/helper-string-parser@npm:7.24.8" + checksum: 39b03c5119216883878655b149148dc4d2e284791e969b19467a9411fccaa33f7a713add98f4db5ed519535f70ad273cdadfd2eb54d47ebbdeac5083351328ce + languageName: node + linkType: hard + "@babel/helper-validator-identifier@npm:^7.18.6": version: 7.18.6 resolution: "@babel/helper-validator-identifier@npm:7.18.6" @@ -719,6 +825,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-validator-identifier@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/helper-validator-identifier@npm:7.24.7" + checksum: 6799ab117cefc0ecd35cd0b40ead320c621a298ecac88686a14cffceaac89d80cdb3c178f969861bf5fa5e4f766648f9161ea0752ecfe080d8e89e3147270257 + languageName: node + linkType: hard + "@babel/helper-validator-option@npm:^7.18.6": version: 7.18.6 resolution: "@babel/helper-validator-option@npm:7.18.6" @@ -740,6 +853,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-validator-option@npm:^7.24.8": + version: 7.24.8 + resolution: "@babel/helper-validator-option@npm:7.24.8" + checksum: a52442dfa74be6719c0608fee3225bd0493c4057459f3014681ea1a4643cd38b68ff477fe867c4b356da7330d085f247f0724d300582fa4ab9a02efaf34d107c + languageName: node + linkType: hard + "@babel/helper-wrap-function@npm:^7.22.20": version: 7.22.20 resolution: "@babel/helper-wrap-function@npm:7.22.20" @@ -784,6 +904,16 @@ __metadata: languageName: node linkType: hard +"@babel/helpers@npm:^7.25.0": + version: 7.25.0 + resolution: "@babel/helpers@npm:7.25.0" + dependencies: + "@babel/template": ^7.25.0 + "@babel/types": ^7.25.0 + checksum: 739e3704ff41a30f5eaac469b553f4d3ab02be6ced083f5925851532dfbd9efc5c347728e77b754ed0b262a4e5e384e60932a62c192d338db7e4b7f3adf9f4a7 + languageName: node + linkType: hard + "@babel/highlight@npm:^7.18.6": version: 7.18.6 resolution: "@babel/highlight@npm:7.18.6" @@ -817,6 +947,18 @@ __metadata: languageName: node linkType: hard +"@babel/highlight@npm:^7.24.7": + version: 7.24.7 + resolution: "@babel/highlight@npm:7.24.7" + dependencies: + "@babel/helper-validator-identifier": ^7.24.7 + chalk: ^2.4.2 + js-tokens: ^4.0.0 + picocolors: ^1.0.0 + checksum: 5cd3a89f143671c4ac129960024ba678b669e6fc673ce078030f5175002d1d3d52bc10b22c5b916a6faf644b5028e9a4bd2bb264d053d9b05b6a98690f1d46f1 + languageName: node + linkType: hard + "@babel/parser@npm:7.22.5": version: 7.22.5 resolution: "@babel/parser@npm:7.22.5" @@ -871,6 +1013,17 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.25.0": + version: 7.25.3 + resolution: "@babel/parser@npm:7.25.3" + dependencies: + "@babel/types": ^7.25.2 + bin: + parser: ./bin/babel-parser.js + checksum: b55aba64214fa1d66ccd0d29f476d2e55a48586920d280f88c546f81cbbececc0e01c9d05a78d6bf206e8438b9c426caa344942c1a581eecc4d365beaab8a20e + languageName: node + linkType: hard + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.23.3": version: 7.23.3 resolution: "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:7.23.3" @@ -2690,6 +2843,15 @@ __metadata: languageName: node linkType: hard +"@babel/runtime@npm:^7.0.0": + version: 7.25.0 + resolution: "@babel/runtime@npm:7.25.0" + dependencies: + regenerator-runtime: ^0.14.0 + checksum: 4a2a374a58eb01aaa65c5762606e90b3a1f448e0c637d42278b6cc0b42a9f5399b5f381ba9f237ee087da2860d14dd2d1de7bddcbe18be6a3cafba97e44bed64 + languageName: node + linkType: hard + "@babel/runtime@npm:^7.8.4": version: 7.20.13 resolution: "@babel/runtime@npm:7.20.13" @@ -2743,6 +2905,17 @@ __metadata: languageName: node linkType: hard +"@babel/template@npm:^7.25.0": + version: 7.25.0 + resolution: "@babel/template@npm:7.25.0" + dependencies: + "@babel/code-frame": ^7.24.7 + "@babel/parser": ^7.25.0 + "@babel/types": ^7.25.0 + checksum: 3f2db568718756d0daf2a16927b78f00c425046b654cd30b450006f2e84bdccaf0cbe6dc04994aa1f5f6a4398da2f11f3640a4d3ee31722e43539c4c919c817b + languageName: node + linkType: hard + "@babel/traverse@npm:7.23.2": version: 7.23.2 resolution: "@babel/traverse@npm:7.23.2" @@ -2816,6 +2989,17 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.24.7, @babel/types@npm:^7.25.0, @babel/types@npm:^7.25.2": + version: 7.25.2 + resolution: "@babel/types@npm:7.25.2" + dependencies: + "@babel/helper-string-parser": ^7.24.8 + "@babel/helper-validator-identifier": ^7.24.7 + to-fast-properties: ^2.0.0 + checksum: f73f66ba903c6f7e38f519a33d53a67d49c07e208e59ea65250362691dc546c6da7ab90ec66ee79651ef697329872f6f97eb19a6dfcacc026fd05e76a563c5d2 + languageName: node + linkType: hard + "@balena/dockerignore@npm:^1.0.2": version: 1.0.2 resolution: "@balena/dockerignore@npm:1.0.2" @@ -3453,7 +3637,7 @@ __metadata: resolution: "@fern-api/configuration@workspace:packages/cli/configuration" dependencies: "@fern-api/core-utils": "workspace:*" - "@fern-api/fdr-sdk": 0.98.16-3955e989a + "@fern-api/fdr-sdk": 0.98.18-aaf13f7f5 "@fern-api/fs-utils": "workspace:*" "@fern-api/task-context": "workspace:*" "@fern-fern/fiddle-sdk": 0.0.584 @@ -3502,7 +3686,7 @@ __metadata: version: 0.0.0-use.local resolution: "@fern-api/core@workspace:packages/core" dependencies: - "@fern-api/fdr-sdk": 0.98.16-3955e989a + "@fern-api/fdr-sdk": 0.98.18-aaf13f7f5 "@fern-api/venus-api-sdk": 0.0.38 "@fern-fern/fdr-test-sdk": ^0.0.5297 "@fern-fern/fiddle-sdk": 0.0.584 @@ -3563,7 +3747,7 @@ __metadata: version: 0.0.0-use.local resolution: "@fern-api/docs-markdown-utils@workspace:packages/cli/docs-markdown-utils" dependencies: - "@fern-api/fdr-sdk": 0.98.16-3955e989a + "@fern-api/fdr-sdk": 0.98.18-aaf13f7f5 "@fern-api/fs-utils": "workspace:*" "@fern-api/task-context": "workspace:*" "@types/diff": ^5.2.1 @@ -3589,7 +3773,7 @@ __metadata: resolution: "@fern-api/docs-preview@workspace:packages/cli/docs-preview" dependencies: "@fern-api/docs-resolver": "workspace:*" - "@fern-api/fdr-sdk": 0.98.16-3955e989a + "@fern-api/fdr-sdk": 0.98.18-aaf13f7f5 "@fern-api/fs-utils": "workspace:*" "@fern-api/ir-sdk": "workspace:*" "@fern-api/logger": "workspace:*" @@ -3629,7 +3813,7 @@ __metadata: "@fern-api/configuration": "workspace:*" "@fern-api/core-utils": "workspace:*" "@fern-api/docs-markdown-utils": "workspace:*" - "@fern-api/fdr-sdk": 0.98.16-3955e989a + "@fern-api/fdr-sdk": 0.98.18-aaf13f7f5 "@fern-api/fs-utils": "workspace:*" "@fern-api/ir-generator": "workspace:*" "@fern-api/ir-sdk": "workspace:*" @@ -3670,10 +3854,12 @@ __metadata: eslint: ^8.56.0 file-type: ^19.0.0 jest: ^29.7.0 - next-mdx-remote: ^4.4.1 + next-mdx-remote: ^5.0.0 organize-imports-cli: ^0.10.0 prettier: ^2.7.1 - remark-gfm: ^3.0.1 + rehype-katex: ^7.0.0 + remark-gfm: ^4.0.0 + remark-math: ^6.0.0 tinycolor2: ^1.6.0 typescript: 4.6.4 zod: ^3.22.3 @@ -3685,7 +3871,7 @@ __metadata: resolution: "@fern-api/ete-tests@workspace:packages/cli/ete-tests" dependencies: "@fern-api/configuration": "workspace:*" - "@fern-api/fdr-sdk": 0.98.16-3955e989a + "@fern-api/fdr-sdk": 0.98.18-aaf13f7f5 "@fern-api/fs-utils": "workspace:*" "@fern-api/logging-execa": "workspace:*" "@fern-typescript/fetcher": "workspace:*" @@ -3709,9 +3895,9 @@ __metadata: languageName: unknown linkType: soft -"@fern-api/fdr-sdk@npm:0.98.16-3955e989a": - version: 0.98.16-3955e989a - resolution: "@fern-api/fdr-sdk@npm:0.98.16-3955e989a" +"@fern-api/fdr-sdk@npm:0.98.18-aaf13f7f5": + version: 0.98.18-aaf13f7f5 + resolution: "@fern-api/fdr-sdk@npm:0.98.18-aaf13f7f5" dependencies: dayjs: ^1.11.11 fast-deep-equal: ^3.1.3 @@ -3724,7 +3910,7 @@ __metadata: title: ^3.5.3 ts-essentials: ^10.0.1 url-join: 5.0.0 - checksum: d3a2269365ec238ff5e114af3dadc585e9b0e413523d1f70e89b9b3fb7214c3ad385ac7a2042ee8de705f5bdedfd2d2112cf412e23add90f1f001060f9ee155e + checksum: 2cab59acf600fafa03086973493ea22e71e65e23a0db6f3c9ae2fabfc3f6f668e4893d5520fde685c1e4d5cf300056fb21f4ab57b37c599c549bde7facaf40fe languageName: node linkType: hard @@ -4368,7 +4554,7 @@ __metadata: "@fern-api/configuration": "workspace:*" "@fern-api/core": "workspace:*" "@fern-api/core-utils": "workspace:*" - "@fern-api/fdr-sdk": 0.98.16-3955e989a + "@fern-api/fdr-sdk": 0.98.18-aaf13f7f5 "@fern-api/ir-generator": "workspace:*" "@fern-api/ir-sdk": "workspace:*" "@fern-api/task-context": "workspace:*" @@ -4395,7 +4581,7 @@ __metadata: "@fern-api/core": "workspace:*" "@fern-api/core-utils": "workspace:*" "@fern-api/docs-resolver": "workspace:*" - "@fern-api/fdr-sdk": 0.98.16-3955e989a + "@fern-api/fdr-sdk": 0.98.18-aaf13f7f5 "@fern-api/fs-utils": "workspace:*" "@fern-api/ir-generator": "workspace:*" "@fern-api/ir-migrations": "workspace:*" @@ -4674,6 +4860,7 @@ __metadata: "@fern-api/core-utils": "workspace:*" "@fern-api/fs-utils": "workspace:*" "@fern-api/logger": "workspace:*" + "@fern-api/logging-execa": "workspace:*" "@fern-api/openapi-ir-to-fern": "workspace:*" "@fern-api/openapi-parser": "workspace:*" "@fern-api/semver-utils": "workspace:*" @@ -4684,6 +4871,7 @@ __metadata: "@types/js-yaml": ^4.0.8 "@types/lodash-es": ^4.17.12 "@types/node": ^18.7.18 + "@types/object-hash": ^3.0.6 "@types/tar": ^6.1.11 axios: ^0.28.0 chalk: ^5.3.0 @@ -4692,6 +4880,7 @@ __metadata: jest: ^29.7.0 js-yaml: ^4.1.0 lodash-es: ^4.17.21 + object-hash: ^3.0.0 organize-imports-cli: ^0.10.0 prettier: ^2.7.1 tar: ^6.2.1 @@ -4858,10 +5047,10 @@ __metadata: languageName: node linkType: hard -"@fern-fern/ir-sdk@npm:53.1.0": - version: 53.1.0 - resolution: "@fern-fern/ir-sdk@npm:53.1.0" - checksum: 813f60b02dd91f6b8c8734a4e3a20478cc67f7ee32ba5ec2c05809f406d0817f5fe41a95eeeaaf30abf60c2d0336ee0ee109b3cc4bbdc91c5dfc073b65a782b4 +"@fern-fern/ir-sdk@npm:53.4.0": + version: 53.4.0 + resolution: "@fern-fern/ir-sdk@npm:53.4.0" + checksum: 1201f9f39c5593cfe8daa3b912a5788c26330042704eb843df8ccc88815059f2044f3638cd90915987ca3797551536bc31796877f5ccb4168268b460119d1925 languageName: node linkType: hard @@ -5300,7 +5489,7 @@ __metadata: "@fern-api/generator-commons": "workspace:*" "@fern-api/logger": "workspace:*" "@fern-fern/generator-exec-sdk": ^0.0.898 - "@fern-fern/ir-sdk": 53.1.0 + "@fern-fern/ir-sdk": 53.4.0 "@fern-typescript/commons": "workspace:*" "@fern-typescript/contexts": "workspace:*" "@types/jest": ^29.5.12 @@ -5391,7 +5580,7 @@ __metadata: "@fern-api/fs-utils": "workspace:*" "@fern-api/logger": "workspace:*" "@fern-api/logging-execa": "workspace:*" - "@fern-fern/ir-sdk": 53.1.0 + "@fern-fern/ir-sdk": 53.4.0 "@fern-typescript/fetcher": "workspace:*" "@fern-typescript/zurg": "workspace:*" "@types/decompress": ^4.2.7 @@ -5428,7 +5617,7 @@ __metadata: dependencies: "@fern-api/logger": "workspace:*" "@fern-fern/generator-exec-sdk": ^0.0.898 - "@fern-fern/ir-sdk": 53.1.0 + "@fern-fern/ir-sdk": 53.4.0 "@fern-typescript/commons": "workspace:*" "@types/jest": ^29.5.12 "@types/node": ^18.7.18 @@ -5446,7 +5635,7 @@ __metadata: version: 0.0.0-use.local resolution: "@fern-typescript/endpoint-error-union-generator@workspace:generators/typescript/sdk/endpoint-error-union-generator" dependencies: - "@fern-fern/ir-sdk": 53.1.0 + "@fern-fern/ir-sdk": 53.4.0 "@fern-typescript/commons": "workspace:*" "@fern-typescript/contexts": "workspace:*" "@fern-typescript/resolvers": "workspace:*" @@ -5467,7 +5656,7 @@ __metadata: version: 0.0.0-use.local resolution: "@fern-typescript/environments-generator@workspace:generators/typescript/sdk/environments-generator" dependencies: - "@fern-fern/ir-sdk": 53.1.0 + "@fern-fern/ir-sdk": 53.4.0 "@fern-typescript/commons": "workspace:*" "@fern-typescript/contexts": "workspace:*" "@types/jest": ^29.5.12 @@ -5487,7 +5676,7 @@ __metadata: resolution: "@fern-typescript/express-endpoint-type-schemas-generator@workspace:generators/typescript/express/express-endpoint-type-schemas-generator" dependencies: "@fern-api/core-utils": "workspace:*" - "@fern-fern/ir-sdk": 53.1.0 + "@fern-fern/ir-sdk": 53.4.0 "@fern-typescript/abstract-schema-generator": "workspace:*" "@fern-typescript/commons": "workspace:*" "@fern-typescript/contexts": "workspace:*" @@ -5507,7 +5696,7 @@ __metadata: version: 0.0.0-use.local resolution: "@fern-typescript/express-error-generator@workspace:generators/typescript/express/express-error-generator" dependencies: - "@fern-fern/ir-sdk": 53.1.0 + "@fern-fern/ir-sdk": 53.4.0 "@fern-typescript/abstract-error-class-generator": "workspace:*" "@fern-typescript/commons": "workspace:*" "@fern-typescript/contexts": "workspace:*" @@ -5528,7 +5717,7 @@ __metadata: resolution: "@fern-typescript/express-error-schema-generator@workspace:generators/typescript/express/express-error-schema-generator" dependencies: "@fern-api/core-utils": "workspace:*" - "@fern-fern/ir-sdk": 53.1.0 + "@fern-fern/ir-sdk": 53.4.0 "@fern-typescript/abstract-schema-generator": "workspace:*" "@fern-typescript/commons": "workspace:*" "@fern-typescript/contexts": "workspace:*" @@ -5549,7 +5738,7 @@ __metadata: resolution: "@fern-typescript/express-generator-cli@workspace:generators/typescript/express/cli" dependencies: "@fern-fern/generator-exec-sdk": ^0.0.898 - "@fern-fern/ir-sdk": 53.1.0 + "@fern-fern/ir-sdk": 53.4.0 "@fern-typescript/abstract-generator-cli": "workspace:*" "@fern-typescript/commons": "workspace:*" "@fern-typescript/contexts": "workspace:*" @@ -5578,7 +5767,7 @@ __metadata: dependencies: "@fern-api/core-utils": "workspace:*" "@fern-api/fs-utils": "workspace:*" - "@fern-fern/ir-sdk": 53.1.0 + "@fern-fern/ir-sdk": 53.4.0 "@fern-typescript/commons": "workspace:*" "@fern-typescript/contexts": "workspace:*" "@fern-typescript/express-endpoint-type-schemas-generator": "workspace:*" @@ -5610,7 +5799,7 @@ __metadata: version: 0.0.0-use.local resolution: "@fern-typescript/express-inlined-request-body-generator@workspace:generators/typescript/express/express-inlined-request-body-generator" dependencies: - "@fern-fern/ir-sdk": 53.1.0 + "@fern-fern/ir-sdk": 53.4.0 "@fern-typescript/commons": "workspace:*" "@fern-typescript/contexts": "workspace:*" "@types/jest": ^29.5.12 @@ -5628,7 +5817,7 @@ __metadata: version: 0.0.0-use.local resolution: "@fern-typescript/express-inlined-request-schema-generator@workspace:generators/typescript/express/express-inlined-request-body-schema-generator" dependencies: - "@fern-fern/ir-sdk": 53.1.0 + "@fern-fern/ir-sdk": 53.4.0 "@fern-typescript/abstract-schema-generator": "workspace:*" "@fern-typescript/commons": "workspace:*" "@fern-typescript/contexts": "workspace:*" @@ -5648,7 +5837,7 @@ __metadata: version: 0.0.0-use.local resolution: "@fern-typescript/express-register-generator@workspace:generators/typescript/express/express-register-generator" dependencies: - "@fern-fern/ir-sdk": 53.1.0 + "@fern-fern/ir-sdk": 53.4.0 "@fern-typescript/commons": "workspace:*" "@fern-typescript/contexts": "workspace:*" "@fern-typescript/resolvers": "workspace:*" @@ -5670,7 +5859,7 @@ __metadata: version: 0.0.0-use.local resolution: "@fern-typescript/express-service-generator@workspace:generators/typescript/express/express-service-generator" dependencies: - "@fern-fern/ir-sdk": 53.1.0 + "@fern-fern/ir-sdk": 53.4.0 "@fern-typescript/commons": "workspace:*" "@fern-typescript/contexts": "workspace:*" "@fern-typescript/resolvers": "workspace:*" @@ -5701,6 +5890,7 @@ __metadata: depcheck: ^1.4.6 eslint: ^8.56.0 express: ^4.19.2 + fetch-mock-jest: ^1.5.1 form-data: 4.0.0 form-data-encoder: ^4.0.2 formdata-node: ^6.0.3 @@ -5759,7 +5949,7 @@ __metadata: resolution: "@fern-typescript/request-wrapper-generator@workspace:generators/typescript/sdk/request-wrapper-generator" dependencies: "@fern-api/core-utils": "workspace:*" - "@fern-fern/ir-sdk": 53.1.0 + "@fern-fern/ir-sdk": 53.4.0 "@fern-typescript/commons": "workspace:*" "@fern-typescript/contexts": "workspace:*" "@types/jest": ^29.5.12 @@ -5778,7 +5968,7 @@ __metadata: version: 0.0.0-use.local resolution: "@fern-typescript/resolvers@workspace:generators/typescript/utils/resolvers" dependencies: - "@fern-fern/ir-sdk": 53.1.0 + "@fern-fern/ir-sdk": 53.4.0 "@fern-typescript/commons": "workspace:*" "@types/jest": ^29.5.12 "@types/node": ^18.7.18 @@ -5796,7 +5986,7 @@ __metadata: resolution: "@fern-typescript/sdk-client-class-generator@workspace:generators/typescript/sdk/client-class-generator" dependencies: "@fern-api/core-utils": "workspace:*" - "@fern-fern/ir-sdk": 53.1.0 + "@fern-fern/ir-sdk": 53.4.0 "@fern-typescript/commons": "workspace:*" "@fern-typescript/contexts": "workspace:*" "@fern-typescript/resolvers": "workspace:*" @@ -5818,7 +6008,7 @@ __metadata: resolution: "@fern-typescript/sdk-endpoint-type-schemas-generator@workspace:generators/typescript/sdk/sdk-endpoint-type-schemas-generator" dependencies: "@fern-api/core-utils": "workspace:*" - "@fern-fern/ir-sdk": 53.1.0 + "@fern-fern/ir-sdk": 53.4.0 "@fern-typescript/abstract-schema-generator": "workspace:*" "@fern-typescript/commons": "workspace:*" "@fern-typescript/contexts": "workspace:*" @@ -5840,7 +6030,7 @@ __metadata: version: 0.0.0-use.local resolution: "@fern-typescript/sdk-error-generator@workspace:generators/typescript/sdk/sdk-error-generator" dependencies: - "@fern-fern/ir-sdk": 53.1.0 + "@fern-fern/ir-sdk": 53.4.0 "@fern-typescript/abstract-error-class-generator": "workspace:*" "@fern-typescript/commons": "workspace:*" "@fern-typescript/contexts": "workspace:*" @@ -5861,7 +6051,7 @@ __metadata: resolution: "@fern-typescript/sdk-error-schema-generator@workspace:generators/typescript/sdk/sdk-error-schema-generator" dependencies: "@fern-api/core-utils": "workspace:*" - "@fern-fern/ir-sdk": 53.1.0 + "@fern-fern/ir-sdk": 53.4.0 "@fern-typescript/abstract-schema-generator": "workspace:*" "@fern-typescript/commons": "workspace:*" "@fern-typescript/contexts": "workspace:*" @@ -5883,7 +6073,7 @@ __metadata: dependencies: "@fern-api/fs-utils": "workspace:*" "@fern-api/generator-commons": "workspace:*" - "@fern-fern/ir-sdk": 53.1.0 + "@fern-fern/ir-sdk": 53.4.0 "@fern-typescript/abstract-generator-cli": "workspace:*" "@fern-typescript/commons": "workspace:*" "@fern-typescript/contexts": "workspace:*" @@ -5918,7 +6108,7 @@ __metadata: "@fern-api/logging-execa": "workspace:*" "@fern-fern/generator-cli-sdk": 0.0.56 "@fern-fern/generator-exec-sdk": ^0.0.898 - "@fern-fern/ir-sdk": 53.1.0 + "@fern-fern/ir-sdk": 53.4.0 "@fern-fern/snippet-sdk": ^0.0.5526 "@fern-typescript/commons": "workspace:*" "@fern-typescript/contexts": "workspace:*" @@ -5964,7 +6154,7 @@ __metadata: version: 0.0.0-use.local resolution: "@fern-typescript/sdk-inlined-request-schema-generator@workspace:generators/typescript/sdk/sdk-inlined-request-body-schema-generator" dependencies: - "@fern-fern/ir-sdk": 53.1.0 + "@fern-fern/ir-sdk": 53.4.0 "@fern-typescript/abstract-schema-generator": "workspace:*" "@fern-typescript/commons": "workspace:*" "@fern-typescript/contexts": "workspace:*" @@ -5984,7 +6174,7 @@ __metadata: version: 0.0.0-use.local resolution: "@fern-typescript/type-generator@workspace:generators/typescript/model/type-generator" dependencies: - "@fern-fern/ir-sdk": 53.1.0 + "@fern-fern/ir-sdk": 53.4.0 "@fern-typescript/commons": "workspace:*" "@fern-typescript/contexts": "workspace:*" "@fern-typescript/union-generator": "workspace:*" @@ -6004,7 +6194,7 @@ __metadata: version: 0.0.0-use.local resolution: "@fern-typescript/type-reference-converters@workspace:generators/typescript/model/type-reference-converters" dependencies: - "@fern-fern/ir-sdk": 53.1.0 + "@fern-fern/ir-sdk": 53.4.0 "@fern-typescript/commons": "workspace:*" "@fern-typescript/resolvers": "workspace:*" "@types/jest": ^29.5.12 @@ -6024,7 +6214,7 @@ __metadata: resolution: "@fern-typescript/type-reference-example-generator@workspace:generators/typescript/model/type-reference-example-generator" dependencies: "@fern-api/core-utils": "workspace:*" - "@fern-fern/ir-sdk": 53.1.0 + "@fern-fern/ir-sdk": 53.4.0 "@fern-typescript/commons": "workspace:*" "@fern-typescript/contexts": "workspace:*" "@types/jest": ^29.5.12 @@ -6043,7 +6233,7 @@ __metadata: version: 0.0.0-use.local resolution: "@fern-typescript/type-schema-generator@workspace:generators/typescript/model/type-schema-generator" dependencies: - "@fern-fern/ir-sdk": 53.1.0 + "@fern-fern/ir-sdk": 53.4.0 "@fern-typescript/abstract-schema-generator": "workspace:*" "@fern-typescript/commons": "workspace:*" "@fern-typescript/contexts": "workspace:*" @@ -6065,7 +6255,7 @@ __metadata: resolution: "@fern-typescript/union-generator@workspace:generators/typescript/model/union-generator" dependencies: "@fern-api/core-utils": "workspace:*" - "@fern-fern/ir-sdk": 53.1.0 + "@fern-fern/ir-sdk": 53.4.0 "@fern-typescript/commons": "workspace:*" "@fern-typescript/contexts": "workspace:*" "@types/jest": ^29.5.12 @@ -6084,7 +6274,7 @@ __metadata: version: 0.0.0-use.local resolution: "@fern-typescript/union-schema-generator@workspace:generators/typescript/model/union-schema-generator" dependencies: - "@fern-fern/ir-sdk": 53.1.0 + "@fern-fern/ir-sdk": 53.4.0 "@fern-typescript/abstract-schema-generator": "workspace:*" "@fern-typescript/commons": "workspace:*" "@fern-typescript/contexts": "workspace:*" @@ -6527,6 +6717,17 @@ __metadata: languageName: node linkType: hard +"@jridgewell/gen-mapping@npm:^0.3.5": + version: 0.3.5 + resolution: "@jridgewell/gen-mapping@npm:0.3.5" + dependencies: + "@jridgewell/set-array": ^1.2.1 + "@jridgewell/sourcemap-codec": ^1.4.10 + "@jridgewell/trace-mapping": ^0.3.24 + checksum: ff7a1764ebd76a5e129c8890aa3e2f46045109dabde62b0b6c6a250152227647178ff2069ea234753a690d8f3c4ac8b5e7b267bbee272bffb7f3b0a370ab6e52 + languageName: node + linkType: hard + "@jridgewell/resolve-uri@npm:^3.0.3": version: 3.0.7 resolution: "@jridgewell/resolve-uri@npm:3.0.7" @@ -6548,6 +6749,13 @@ __metadata: languageName: node linkType: hard +"@jridgewell/set-array@npm:^1.2.1": + version: 1.2.1 + resolution: "@jridgewell/set-array@npm:1.2.1" + checksum: 832e513a85a588f8ed4f27d1279420d8547743cc37fcad5a5a76fc74bb895b013dfe614d0eed9cb860048e6546b798f8f2652020b4b2ba0561b05caa8c654b10 + languageName: node + linkType: hard + "@jridgewell/sourcemap-codec@npm:^1.4.10": version: 1.4.13 resolution: "@jridgewell/sourcemap-codec@npm:1.4.13" @@ -6592,6 +6800,16 @@ __metadata: languageName: node linkType: hard +"@jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25": + version: 0.3.25 + resolution: "@jridgewell/trace-mapping@npm:0.3.25" + dependencies: + "@jridgewell/resolve-uri": ^3.1.0 + "@jridgewell/sourcemap-codec": ^1.4.14 + checksum: 9d3c40d225e139987b50c48988f8717a54a8c994d8a948ee42e1412e08988761d0754d7d10b803061cc3aebf35f92a5dbbab493bd0e1a9ef9e89a2130e83ba34 + languageName: node + linkType: hard + "@jsdevtools/ono@npm:^7.1.3": version: 7.1.3 resolution: "@jsdevtools/ono@npm:7.1.3" @@ -6608,40 +6826,46 @@ __metadata: languageName: node linkType: hard -"@mdx-js/mdx@npm:^2.2.1": - version: 2.3.0 - resolution: "@mdx-js/mdx@npm:2.3.0" +"@mdx-js/mdx@npm:^3.0.1": + version: 3.0.1 + resolution: "@mdx-js/mdx@npm:3.0.1" dependencies: + "@types/estree": ^1.0.0 "@types/estree-jsx": ^1.0.0 + "@types/hast": ^3.0.0 "@types/mdx": ^2.0.0 - estree-util-build-jsx: ^2.0.0 - estree-util-is-identifier-name: ^2.0.0 - estree-util-to-js: ^1.1.0 + collapse-white-space: ^2.0.0 + devlop: ^1.0.0 + estree-util-build-jsx: ^3.0.0 + estree-util-is-identifier-name: ^3.0.0 + estree-util-to-js: ^2.0.0 estree-walker: ^3.0.0 - hast-util-to-estree: ^2.0.0 - markdown-extensions: ^1.0.0 + hast-util-to-estree: ^3.0.0 + hast-util-to-jsx-runtime: ^2.0.0 + markdown-extensions: ^2.0.0 periscopic: ^3.0.0 - remark-mdx: ^2.0.0 - remark-parse: ^10.0.0 - remark-rehype: ^10.0.0 - unified: ^10.0.0 - unist-util-position-from-estree: ^1.0.0 - unist-util-stringify-position: ^3.0.0 - unist-util-visit: ^4.0.0 - vfile: ^5.0.0 - checksum: d918766a326502ec0b54adee61dc2930daf5b748acb9107f9bfd1ab0dbc4d7b1a4d0dbb9e21da9dd2a9fc2f9950b2973a43c6ba62d3a72eb67a30f6c953e5be8 + remark-mdx: ^3.0.0 + remark-parse: ^11.0.0 + remark-rehype: ^11.0.0 + source-map: ^0.7.0 + unified: ^11.0.0 + unist-util-position-from-estree: ^2.0.0 + unist-util-stringify-position: ^4.0.0 + unist-util-visit: ^5.0.0 + vfile: ^6.0.0 + checksum: 82221662279c39a755b88f63b031a30b9bc04365e5bfc3e45590f4fa7bf6bff12364f4caee31c768ae588145eed74fda10c327d53f9272b1a2cffbc8bd537ce6 languageName: node linkType: hard -"@mdx-js/react@npm:^2.2.1": - version: 2.3.0 - resolution: "@mdx-js/react@npm:2.3.0" +"@mdx-js/react@npm:^3.0.1": + version: 3.0.1 + resolution: "@mdx-js/react@npm:3.0.1" dependencies: "@types/mdx": ^2.0.0 - "@types/react": ">=16" peerDependencies: + "@types/react": ">=16" react: ">=16" - checksum: f45fe779556e6cd9a787f711274480e0638b63c460f192ebdcd77cc07ffa61e23c98cb46dd46e577093e1cb4997a232a848d1fb0ba850ae204422cf603add524 + checksum: 1063a597264f6a8840aa13274a99beef8983a88dd45b0c5b8e48e6216bc23d33e247da8e2d95d6e1874483f8b4e0903b166ce5046874aa7ffa2b1333057dcddf languageName: node linkType: hard @@ -7343,15 +7567,6 @@ __metadata: languageName: node linkType: hard -"@types/hast@npm:^2.0.0": - version: 2.3.5 - resolution: "@types/hast@npm:2.3.5" - dependencies: - "@types/unist": ^2 - checksum: e435e9fbf6afc616ade377d2246a632fb75f4064be4bfd619b67a1ba0d9935d75968a18fbdb66535dfb5e77ef81f4b9b56fd8f35c1cffa34b48ddb0287fec91e - languageName: node - linkType: hard - "@types/hast@npm:^3.0.0": version: 3.0.4 resolution: "@types/hast@npm:3.0.4" @@ -7441,13 +7656,6 @@ __metadata: languageName: node linkType: hard -"@types/js-yaml@npm:^4.0.0": - version: 4.0.5 - resolution: "@types/js-yaml@npm:4.0.5" - checksum: 7dcac8c50fec31643cc9d6444b5503239a861414cdfaa7ae9a38bc22597c4d850c4b8cec3d82d73b3fbca408348ce223b0408d598b32e094470dfffc6d486b4d - languageName: node - linkType: hard - "@types/js-yaml@npm:^4.0.8": version: 4.0.8 resolution: "@types/js-yaml@npm:4.0.8" @@ -7512,6 +7720,13 @@ __metadata: languageName: node linkType: hard +"@types/katex@npm:^0.16.0": + version: 0.16.7 + resolution: "@types/katex@npm:0.16.7" + checksum: 4fd15d93553be97c02c064e16be18d7ccbabf66ec72a9dc7fd5bfa47f0c7581da2f942f693c7cb59499de4c843c2189796e49c9647d336cbd52b777b6722a95a + languageName: node + linkType: hard + "@types/keyv@npm:*, @types/keyv@npm:^3.1.1": version: 3.1.4 resolution: "@types/keyv@npm:3.1.4" @@ -7562,15 +7777,6 @@ __metadata: languageName: node linkType: hard -"@types/mdast@npm:^3.0.0": - version: 3.0.12 - resolution: "@types/mdast@npm:3.0.12" - dependencies: - "@types/unist": ^2 - checksum: 83adb8679b9d139f69f63554d120af921e9f1289e9903a2c99e0554a327c8524a6c0beccdc0721e4fdbccc606e81964fecb0d390d53df0f74360938e22f1a469 - languageName: node - linkType: hard - "@types/mdast@npm:^4.0.0": version: 4.0.3 resolution: "@types/mdast@npm:4.0.3" @@ -7754,17 +7960,6 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:>=16": - version: 18.2.21 - resolution: "@types/react@npm:18.2.21" - dependencies: - "@types/prop-types": "*" - "@types/scheduler": "*" - csstype: ^3.0.2 - checksum: ffed203bfe7aad772b8286f7953305c9181ac3a8f27d3f5400fbbc2a8e27ca8e5bbff818ee014f39ca0d19d2b3bb154e5bdbec7e232c6f80b59069375aa78349 - languageName: node - linkType: hard - "@types/responselike@npm:*, @types/responselike@npm:^1.0.0": version: 1.0.0 resolution: "@types/responselike@npm:1.0.0" @@ -7918,7 +8113,7 @@ __metadata: languageName: node linkType: hard -"@types/unist@npm:^2, @types/unist@npm:^2.0.0": +"@types/unist@npm:^2.0.0": version: 2.0.8 resolution: "@types/unist@npm:2.0.8" checksum: f4852d10a6752dc70df363917ef74453e5d2fd42824c0f6d09d19d530618e1402193977b1207366af4415aaec81d4e262c64d00345402020c4ca179216e553c7 @@ -8284,7 +8479,7 @@ __metadata: languageName: node linkType: hard -"@ungap/structured-clone@npm:^1.2.0": +"@ungap/structured-clone@npm:^1.0.0, @ungap/structured-clone@npm:^1.2.0": version: 1.2.0 resolution: "@ungap/structured-clone@npm:1.2.0" checksum: 4f656b7b4672f2ce6e272f2427d8b0824ed11546a601d8d5412b9d7704e83db38a8d9f402ecdf2b9063fc164af842ad0ec4a55819f621ed7e7ea4d1efcc74524 @@ -9514,6 +9709,20 @@ __metadata: languageName: node linkType: hard +"browserslist@npm:^4.23.1": + version: 4.23.3 + resolution: "browserslist@npm:4.23.3" + dependencies: + caniuse-lite: ^1.0.30001646 + electron-to-chromium: ^1.5.4 + node-releases: ^2.0.18 + update-browserslist-db: ^1.1.0 + bin: + browserslist: cli.js + checksum: 7906064f9970aeb941310b2fcb8b4ace4a1b50aa657c986677c6f1553a8cabcc94ee9c5922f715baffbedaa0e6cf0831b6fed7b059dde6873a4bfadcbe069c7e + languageName: node + linkType: hard + "bser@npm:2.1.1": version: 2.1.1 resolution: "bser@npm:2.1.1" @@ -9864,6 +10073,13 @@ __metadata: languageName: node linkType: hard +"caniuse-lite@npm:^1.0.30001646": + version: 1.0.30001650 + resolution: "caniuse-lite@npm:1.0.30001650" + checksum: 4892c25200aef4dca83feba232bb0d4c1dbbdffb3376394c1be7e75a6be686010e88bc2172cb5cf8dafa5e5de08dc8e7870c4fe97dae3d615979a7be5a091db7 + languageName: node + linkType: hard + "capture-stack-trace@npm:^1.0.0": version: 1.0.2 resolution: "capture-stack-trace@npm:1.0.2" @@ -10276,6 +10492,13 @@ __metadata: languageName: node linkType: hard +"collapse-white-space@npm:^2.0.0": + version: 2.1.0 + resolution: "collapse-white-space@npm:2.1.0" + checksum: c8978b1f4e7d68bf846cfdba6c6689ce8910511df7d331eb6e6757e51ceffb52768d59a28db26186c91dcf9594955b59be9f8ccd473c485790f5d8b90dc6726f + languageName: node + linkType: hard + "collect-v8-coverage@npm:^1.0.0": version: 1.0.1 resolution: "collect-v8-coverage@npm:1.0.1" @@ -10399,7 +10622,7 @@ __metadata: languageName: node linkType: hard -"commander@npm:^8.0.0": +"commander@npm:^8.0.0, commander@npm:^8.3.0": version: 8.3.0 resolution: "commander@npm:8.3.0" checksum: 0f82321821fc27b83bd409510bb9deeebcfa799ff0bf5d102128b500b7af22872c0c92cb6a0ebc5a4cf19c6b550fba9cedfa7329d18c6442a625f851377bacf0 @@ -10569,6 +10792,13 @@ __metadata: languageName: node linkType: hard +"core-js@npm:^3.0.0": + version: 3.38.0 + resolution: "core-js@npm:3.38.0" + checksum: 71ef0598da69daee2b46fa1f82f074019981656f7cae26fed2b7f076c611e330a99ba5c70156ae37682f59a8d6ec6486119c70cb283c9fff25bd4f20db7fdc27 + languageName: node + linkType: hard + "core-util-is@npm:1.0.2": version: 1.0.2 resolution: "core-util-is@npm:1.0.2" @@ -11272,13 +11502,6 @@ __metadata: languageName: node linkType: hard -"diff@npm:^5.0.0": - version: 5.1.0 - resolution: "diff@npm:5.1.0" - checksum: c7bf0df7c9bfbe1cf8a678fd1b2137c4fb11be117a67bc18a0e03ae75105e8533dbfb1cda6b46beb3586ef5aed22143ef9d70713977d5fb1f9114e21455fba90 - languageName: node - linkType: hard - "diff@npm:^5.2.0": version: 5.2.0 resolution: "diff@npm:5.2.0" @@ -11478,6 +11701,13 @@ __metadata: languageName: node linkType: hard +"electron-to-chromium@npm:^1.5.4": + version: 1.5.5 + resolution: "electron-to-chromium@npm:1.5.5" + checksum: fcdd2797ece1ece6764b88b5fc36cfc6a571e08b832c6777d8bbefa19cae22a36614411aacc5687d9fea7e1db86469f53c3952ca2579c5fe705dea7ed270d8cc + languageName: node + linkType: hard + "emittery@npm:^0.13.1": version: 0.13.1 resolution: "emittery@npm:0.13.1" @@ -12260,6 +12490,13 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard +"escalade@npm:^3.1.2": + version: 3.1.2 + resolution: "escalade@npm:3.1.2" + checksum: 1ec0977aa2772075493002bdbd549d595ff6e9393b1cb0d7d6fcaf78c750da0c158f180938365486f75cb69fba20294351caddfce1b46552a7b6c3cde52eaa02 + languageName: node + linkType: hard + "escape-html@npm:~1.0.3": version: 1.0.3 resolution: "escape-html@npm:1.0.3" @@ -12676,30 +12913,24 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"estree-util-attach-comments@npm:^2.0.0": - version: 2.1.1 - resolution: "estree-util-attach-comments@npm:2.1.1" +"estree-util-attach-comments@npm:^3.0.0": + version: 3.0.0 + resolution: "estree-util-attach-comments@npm:3.0.0" dependencies: "@types/estree": ^1.0.0 - checksum: c5c2c41c9a55a169fb4fba9627057843f0d2e21e47a2e3e24318a11ffcf6bc704c0f96f405a529bddac7969b7c44f6cf86711505faaf0c5862c2024419b19704 + checksum: 56254eaef39659e6351919ebc2ae53a37a09290a14571c19e373e9d5fad343a3403d9ad0c23ae465d6e7d08c3e572fd56fb8c793efe6434a261bf1489932dbd5 languageName: node linkType: hard -"estree-util-build-jsx@npm:^2.0.0": - version: 2.2.2 - resolution: "estree-util-build-jsx@npm:2.2.2" +"estree-util-build-jsx@npm:^3.0.0": + version: 3.0.1 + resolution: "estree-util-build-jsx@npm:3.0.1" dependencies: "@types/estree-jsx": ^1.0.0 - estree-util-is-identifier-name: ^2.0.0 + devlop: ^1.0.0 + estree-util-is-identifier-name: ^3.0.0 estree-walker: ^3.0.0 - checksum: d008ac36a45d797eadca696f41b4c1ac0587ec0e0b52560cfb0e76d14ef15fc18e526f9023b6e5457dafa9cf3f010c9bb1dfc9c727ebd7cf0ba2ebbaa43919ac - languageName: node - linkType: hard - -"estree-util-is-identifier-name@npm:^2.0.0": - version: 2.1.0 - resolution: "estree-util-is-identifier-name@npm:2.1.0" - checksum: cab317a071fafb99cf83b57df7924bccd2e6ab4e252688739e49f00b16cefd168e279c171442b0557c80a1c80ffaa927d670dadea65bb3c9b151efb8e772e89d + checksum: 185eff060eda2ba32cecd15904db4f5ba0681159fbdf54f0f6586cd9411e77e733861a833d0aee3415e1d1fd4b17edf08bc9e9872cee98e6ec7b0800e1a85064 languageName: node linkType: hard @@ -12710,24 +12941,14 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"estree-util-to-js@npm:^1.1.0": - version: 1.2.0 - resolution: "estree-util-to-js@npm:1.2.0" +"estree-util-to-js@npm:^2.0.0": + version: 2.0.0 + resolution: "estree-util-to-js@npm:2.0.0" dependencies: "@types/estree-jsx": ^1.0.0 astring: ^1.8.0 source-map: ^0.7.0 - checksum: 93a75e1051a6a4f5c631597ecd2ed95129fadbc80a58a10475d6d6b1b076a69393ba4a8d2bb71f698401f64ccca47e3f3828dd0042cac81439b988fae0f5f8e0 - languageName: node - linkType: hard - -"estree-util-visit@npm:^1.0.0": - version: 1.2.1 - resolution: "estree-util-visit@npm:1.2.1" - dependencies: - "@types/estree-jsx": ^1.0.0 - "@types/unist": ^2.0.0 - checksum: 6feea4fdc43b0ba0f79faf1d57cf32373007e146d4810c7c09c13f5a9c1b8600c1ac57a8d949967cedd2a9a91dddd246e19b59bacfc01e417168b4ebf220f691 + checksum: 833edc94ab9978e0918f90261e0a3361bf4564fec4901f326d2237a9235d3f5fc6482da3be5acc545e702c8c7cb8bc5de5c7c71ba3b080eb1975bcfdf3923d79 languageName: node linkType: hard @@ -13104,6 +13325,43 @@ env-cmd@toddbluhm/env-cmd: languageName: unknown linkType: soft +"fetch-mock-jest@npm:^1.5.1": + version: 1.5.1 + resolution: "fetch-mock-jest@npm:1.5.1" + dependencies: + fetch-mock: ^9.11.0 + peerDependencies: + node-fetch: "*" + peerDependenciesMeta: + node-fetch: + optional: true + checksum: 371b1c4a1fb1cf507ace0bf1a505ae0a9b0184ef0edfa6225c3d93866ba80fa89b47d412eb077315007e0a73028e2ed5be4b88b85e992e53a834ecbd44c4a3be + languageName: node + linkType: hard + +"fetch-mock@npm:^9.11.0": + version: 9.11.0 + resolution: "fetch-mock@npm:9.11.0" + dependencies: + "@babel/core": ^7.0.0 + "@babel/runtime": ^7.0.0 + core-js: ^3.0.0 + debug: ^4.1.1 + glob-to-regexp: ^0.4.0 + is-subset: ^0.1.1 + lodash.isequal: ^4.5.0 + path-to-regexp: ^2.2.1 + querystring: ^0.2.0 + whatwg-url: ^6.5.0 + peerDependencies: + node-fetch: "*" + peerDependenciesMeta: + node-fetch: + optional: true + checksum: debc4dd83bcda79b0aa71c38d08da6036906cdc49393343eb3426112314a7e57557255664f745d2e3f0b9b2a6e852bd3a564ae3f08332c27e422d3441bb865bd + languageName: node + linkType: hard + "figgy-pudding@npm:^3.4.1, figgy-pudding@npm:^3.5.1, figgy-pudding@npm:^3.5.2": version: 3.5.2 resolution: "figgy-pudding@npm:3.5.2" @@ -13806,6 +14064,13 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard +"glob-to-regexp@npm:^0.4.0": + version: 0.4.1 + resolution: "glob-to-regexp@npm:0.4.1" + checksum: e795f4e8f06d2a15e86f76e4d92751cf8bbfcf0157cea5c2f0f35678a8195a750b34096b1256e436f0cebc1883b5ff0888c47348443e69546a5a87f9e1eb1167 + languageName: node + linkType: hard + "glob@npm:7.1.6": version: 7.1.6 resolution: "glob@npm:7.1.6" @@ -14224,73 +14489,195 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"hast-util-to-estree@npm:^2.0.0": - version: 2.3.3 - resolution: "hast-util-to-estree@npm:2.3.3" +"hast-util-from-dom@npm:^5.0.0": + version: 5.0.0 + resolution: "hast-util-from-dom@npm:5.0.0" dependencies: - "@types/estree": ^1.0.0 - "@types/estree-jsx": ^1.0.0 - "@types/hast": ^2.0.0 - "@types/unist": ^2.0.0 - comma-separated-tokens: ^2.0.0 - estree-util-attach-comments: ^2.0.0 - estree-util-is-identifier-name: ^2.0.0 - hast-util-whitespace: ^2.0.0 - mdast-util-mdx-expression: ^1.0.0 - mdast-util-mdxjs-esm: ^1.0.0 - property-information: ^6.0.0 - space-separated-tokens: ^2.0.0 - style-to-object: ^0.4.1 - unist-util-position: ^4.0.0 - zwitch: ^2.0.0 - checksum: a09de0214db4d71f11cbd6f18663a8032116f82cb076b05d2d735444c05a9692902dae1023b70d0a254fc0a776f81e97450ca396bb9252c8fd631c3ba2e12f24 + "@types/hast": ^3.0.0 + hastscript: ^8.0.0 + web-namespaces: ^2.0.0 + checksum: bf8f96c480a598b42156227be2210bbb7a08da519ae4d57814385c8560b01e2b6b5fbde2afce808ce7ba7c5cd172822d4285b8f5edde2d13089bc9c3177c0d09 languageName: node linkType: hard -"hast-util-whitespace@npm:^2.0.0": - version: 2.0.1 - resolution: "hast-util-whitespace@npm:2.0.1" - checksum: 431be6b2f35472f951615540d7a53f69f39461e5e080c0190268bdeb2be9ab9b1dddfd1f467dd26c1de7e7952df67beb1307b6ee940baf78b24a71b5e0663868 +"hast-util-from-html-isomorphic@npm:^2.0.0": + version: 2.0.0 + resolution: "hast-util-from-html-isomorphic@npm:2.0.0" + dependencies: + "@types/hast": ^3.0.0 + hast-util-from-dom: ^5.0.0 + hast-util-from-html: ^2.0.0 + unist-util-remove-position: ^5.0.0 + checksum: a98d02890bd1b5a804a1b2aaacd0332a6563f2a8df620450e38ab8962728cda0485cd29435824840621d1e653943776864e912d78d24cce6a7f484011ee7cef0 languageName: node linkType: hard -"homedir-polyfill@npm:^1.0.1": - version: 1.0.3 - resolution: "homedir-polyfill@npm:1.0.3" +"hast-util-from-html@npm:^2.0.0": + version: 2.0.1 + resolution: "hast-util-from-html@npm:2.0.1" dependencies: - parse-passwd: ^1.0.0 - checksum: 18dd4db87052c6a2179d1813adea0c4bfcfa4f9996f0e226fefb29eb3d548e564350fa28ec46b0bf1fbc0a1d2d6922ceceb80093115ea45ff8842a4990139250 + "@types/hast": ^3.0.0 + devlop: ^1.1.0 + hast-util-from-parse5: ^8.0.0 + parse5: ^7.0.0 + vfile: ^6.0.0 + vfile-message: ^4.0.0 + checksum: 8decdec1f2750d3d8d4933a4d06d78846a9fb3c97cded07395d160adae22bacfc69eaf113fd95a6ad696d1e5877580f2ac83a4161fa9f3becb0fafe2cec8b0ea languageName: node linkType: hard -"hosted-git-info@npm:^2.1.4, hosted-git-info@npm:^2.7.1, hosted-git-info@npm:^2.8.9": - version: 2.8.9 - resolution: "hosted-git-info@npm:2.8.9" - checksum: c955394bdab888a1e9bb10eb33029e0f7ce5a2ac7b3f158099dc8c486c99e73809dca609f5694b223920ca2174db33d32b12f9a2a47141dc59607c29da5a62dd +"hast-util-from-parse5@npm:^8.0.0": + version: 8.0.1 + resolution: "hast-util-from-parse5@npm:8.0.1" + dependencies: + "@types/hast": ^3.0.0 + "@types/unist": ^3.0.0 + devlop: ^1.0.0 + hastscript: ^8.0.0 + property-information: ^6.0.0 + vfile: ^6.0.0 + vfile-location: ^5.0.0 + web-namespaces: ^2.0.0 + checksum: fdd1ab8b03af13778ecb94ef9a58b1e3528410cdfceb3d6bb7600508967d0d836b451bc7bc3baf66efb7c730d3d395eea4bb1b30352b0162823d9f0de976774b languageName: node linkType: hard -"hosted-git-info@npm:^4.0.1": - version: 4.1.0 - resolution: "hosted-git-info@npm:4.1.0" +"hast-util-is-element@npm:^3.0.0": + version: 3.0.0 + resolution: "hast-util-is-element@npm:3.0.0" dependencies: - lru-cache: "npm:^6.0.0" - checksum: c3f87b3c2f7eb8c2748c8f49c0c2517c9a95f35d26f4bf54b2a8cba05d2e668f3753548b6ea366b18ec8dadb4e12066e19fa382a01496b0ffa0497eb23cbe461 + "@types/hast": ^3.0.0 + checksum: 82569a420eda5877c52fdbbdbe26675f012c02d70813dfd19acffdee328e42e4bd0b7ae34454cfcbcb932b2bedbd7ddc119f943a0cfb234120f9456d6c0c4331 languageName: node linkType: hard -"html-encoding-sniffer@npm:^3.0.0": - version: 3.0.0 - resolution: "html-encoding-sniffer@npm:3.0.0" +"hast-util-parse-selector@npm:^4.0.0": + version: 4.0.0 + resolution: "hast-util-parse-selector@npm:4.0.0" dependencies: - whatwg-encoding: ^2.0.0 - checksum: 8d806aa00487e279e5ccb573366a951a9f68f65c90298eac9c3a2b440a7ffe46615aff2995a2f61c6746c639234e6179a97e18ca5ccbbf93d3725ef2099a4502 + "@types/hast": ^3.0.0 + checksum: 76087670d3b0b50b23a6cb70bca53a6176d6608307ccdbb3ed18b650b82e7c3513bfc40348f1389dc0c5ae872b9a768851f4335f44654abd7deafd6974c52402 languageName: node linkType: hard -"html-escaper@npm:^2.0.0": - version: 2.0.2 - resolution: "html-escaper@npm:2.0.2" +"hast-util-to-estree@npm:^3.0.0": + version: 3.1.0 + resolution: "hast-util-to-estree@npm:3.1.0" + dependencies: + "@types/estree": ^1.0.0 + "@types/estree-jsx": ^1.0.0 + "@types/hast": ^3.0.0 + comma-separated-tokens: ^2.0.0 + devlop: ^1.0.0 + estree-util-attach-comments: ^3.0.0 + estree-util-is-identifier-name: ^3.0.0 + hast-util-whitespace: ^3.0.0 + mdast-util-mdx-expression: ^2.0.0 + mdast-util-mdx-jsx: ^3.0.0 + mdast-util-mdxjs-esm: ^2.0.0 + property-information: ^6.0.0 + space-separated-tokens: ^2.0.0 + style-to-object: ^0.4.0 + unist-util-position: ^5.0.0 + zwitch: ^2.0.0 + checksum: 61272f7c18c9d2a5e34df7cfd2c97cbf12f6e9d05114d60e4dedd64e5576565eb1e35c78b9213c909bb8f984f0f8e9c49b568f04bdb444b83d0bca9159e14f3c + languageName: node + linkType: hard + +"hast-util-to-jsx-runtime@npm:^2.0.0": + version: 2.3.0 + resolution: "hast-util-to-jsx-runtime@npm:2.3.0" + dependencies: + "@types/estree": ^1.0.0 + "@types/hast": ^3.0.0 + "@types/unist": ^3.0.0 + comma-separated-tokens: ^2.0.0 + devlop: ^1.0.0 + estree-util-is-identifier-name: ^3.0.0 + hast-util-whitespace: ^3.0.0 + mdast-util-mdx-expression: ^2.0.0 + mdast-util-mdx-jsx: ^3.0.0 + mdast-util-mdxjs-esm: ^2.0.0 + property-information: ^6.0.0 + space-separated-tokens: ^2.0.0 + style-to-object: ^1.0.0 + unist-util-position: ^5.0.0 + vfile-message: ^4.0.0 + checksum: 599a97c6ec61c1430776813d7fb42e6f96032bf4a04dfcbb8eceef3bc8d1845ecf242387a4426b9d3f52320dbbfa26450643b81124b3d6a0b9bbb0fff4d0ba83 + languageName: node + linkType: hard + +"hast-util-to-text@npm:^4.0.0": + version: 4.0.2 + resolution: "hast-util-to-text@npm:4.0.2" + dependencies: + "@types/hast": ^3.0.0 + "@types/unist": ^3.0.0 + hast-util-is-element: ^3.0.0 + unist-util-find-after: ^5.0.0 + checksum: 72cce08666b86511595d3eef52236b86897cfbac166f2a0752b70b16d1f590b5aa91ea1a553c0d1603f9e0c7e373ceacab381be3d8f176129ad6e301d2a56d94 + languageName: node + linkType: hard + +"hast-util-whitespace@npm:^3.0.0": + version: 3.0.0 + resolution: "hast-util-whitespace@npm:3.0.0" + dependencies: + "@types/hast": ^3.0.0 + checksum: 41d93ccce218ba935dc3c12acdf586193c35069489c8c8f50c2aa824c00dec94a3c78b03d1db40fa75381942a189161922e4b7bca700b3a2cc779634c351a1e4 + languageName: node + linkType: hard + +"hastscript@npm:^8.0.0": + version: 8.0.0 + resolution: "hastscript@npm:8.0.0" + dependencies: + "@types/hast": ^3.0.0 + comma-separated-tokens: ^2.0.0 + hast-util-parse-selector: ^4.0.0 + property-information: ^6.0.0 + space-separated-tokens: ^2.0.0 + checksum: ae3c20223e7b847320c0f98b6fb3c763ebe1bf3913c5805fbc176cf84553a9db1117ca34cf842a5235890b4b9ae0e94501bfdc9a9b870a5dbf5fc52426db1097 + languageName: node + linkType: hard + +"homedir-polyfill@npm:^1.0.1": + version: 1.0.3 + resolution: "homedir-polyfill@npm:1.0.3" + dependencies: + parse-passwd: ^1.0.0 + checksum: 18dd4db87052c6a2179d1813adea0c4bfcfa4f9996f0e226fefb29eb3d548e564350fa28ec46b0bf1fbc0a1d2d6922ceceb80093115ea45ff8842a4990139250 + languageName: node + linkType: hard + +"hosted-git-info@npm:^2.1.4, hosted-git-info@npm:^2.7.1, hosted-git-info@npm:^2.8.9": + version: 2.8.9 + resolution: "hosted-git-info@npm:2.8.9" + checksum: c955394bdab888a1e9bb10eb33029e0f7ce5a2ac7b3f158099dc8c486c99e73809dca609f5694b223920ca2174db33d32b12f9a2a47141dc59607c29da5a62dd + languageName: node + linkType: hard + +"hosted-git-info@npm:^4.0.1": + version: 4.1.0 + resolution: "hosted-git-info@npm:4.1.0" + dependencies: + lru-cache: "npm:^6.0.0" + checksum: c3f87b3c2f7eb8c2748c8f49c0c2517c9a95f35d26f4bf54b2a8cba05d2e668f3753548b6ea366b18ec8dadb4e12066e19fa382a01496b0ffa0497eb23cbe461 + languageName: node + linkType: hard + +"html-encoding-sniffer@npm:^3.0.0": + version: 3.0.0 + resolution: "html-encoding-sniffer@npm:3.0.0" + dependencies: + whatwg-encoding: ^2.0.0 + checksum: 8d806aa00487e279e5ccb573366a951a9f68f65c90298eac9c3a2b440a7ffe46615aff2995a2f61c6746c639234e6179a97e18ca5ccbbf93d3725ef2099a4502 + languageName: node + linkType: hard + +"html-escaper@npm:^2.0.0": + version: 2.0.2 + resolution: "html-escaper@npm:2.0.2" checksum: d2df2da3ad40ca9ee3a39c5cc6475ef67c8f83c234475f24d8e9ce0dc80a2c82df8e1d6fa78ddd1e9022a586ea1bd247a615e80a5cd9273d90111ddda7d9e974 languageName: node linkType: hard @@ -14619,6 +15006,13 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard +"inline-style-parser@npm:0.2.3": + version: 0.2.3 + resolution: "inline-style-parser@npm:0.2.3" + checksum: ed6454de80759e7faef511f51b5716b33c40a6b05b8a8f5383dc01e8a087c6fd5df877446d05e8e3961ae0751e028e25e180f5cffc192a5ce7822edef6810ade + languageName: node + linkType: hard + "inquirer@npm:^9.2.23": version: 9.2.23 resolution: "inquirer@npm:9.2.23" @@ -14785,13 +15179,6 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"is-buffer@npm:^2.0.0": - version: 2.0.5 - resolution: "is-buffer@npm:2.0.5" - checksum: 764c9ad8b523a9f5a32af29bdf772b08eb48c04d2ad0a7240916ac2688c983bf5f8504bf25b35e66240edeb9d9085461f9b5dae1f3d2861c6b06a65fe983de42 - languageName: node - linkType: hard - "is-callable@npm:^1.1.3, is-callable@npm:^1.2.7": version: 1.2.7 resolution: "is-callable@npm:1.2.7" @@ -15204,6 +15591,13 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard +"is-subset@npm:^0.1.1": + version: 0.1.1 + resolution: "is-subset@npm:0.1.1" + checksum: 97b8d7852af165269b7495095691a6ce6cf20bdfa1f846f97b4560ee190069686107af4e277fbd93aa0845c4d5db704391460ff6e9014aeb73264ba87893df44 + languageName: node + linkType: hard + "is-symbol@npm:^1.0.2, is-symbol@npm:^1.0.3": version: 1.0.4 resolution: "is-symbol@npm:1.0.4" @@ -16047,7 +16441,7 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"js-yaml@npm:^4.0.0, js-yaml@npm:^4.1.0": +"js-yaml@npm:^4.1.0": version: 4.1.0 resolution: "js-yaml@npm:4.1.0" dependencies: @@ -16296,6 +16690,17 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard +"katex@npm:^0.16.0": + version: 0.16.11 + resolution: "katex@npm:0.16.11" + dependencies: + commander: ^8.3.0 + bin: + katex: cli.js + checksum: 49d9340705f4922ee22aacedad45664971449e5ca65e42a70228961336c8d4746c37c3c719bcc2114b6ad21182800c7d3d8bea28fe6f951fc45fe7e8322ea3bd + languageName: node + linkType: hard + "keyv@npm:^4.0.0": version: 4.3.3 resolution: "keyv@npm:4.3.3" @@ -16329,13 +16734,6 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"kleur@npm:^4.0.3": - version: 4.1.5 - resolution: "kleur@npm:4.1.5" - checksum: 1dc476e32741acf0b1b5b0627ffd0d722e342c1b0da14de3e8ae97821327ca08f9fb944542fb3c126d90ac5f27f9d804edbe7c585bf7d12ef495d115e0f22c12 - languageName: node - linkType: hard - "known-css-properties@npm:^0.25.0": version: 0.25.0 resolution: "known-css-properties@npm:0.25.0" @@ -17124,10 +17522,10 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"markdown-extensions@npm:^1.0.0": - version: 1.1.1 - resolution: "markdown-extensions@npm:1.1.1" - checksum: 8a6dd128be1c524049ea6a41a9193715c2835d3d706af4b8b714ff2043a82786dbcd4a8f1fa9ddd28facbc444426c97515aef2d1f3dd11d5e2d63749ba577b1e +"markdown-extensions@npm:^2.0.0": + version: 2.0.0 + resolution: "markdown-extensions@npm:2.0.0" + checksum: ec4ffcb0768f112e778e7ac74cb8ef22a966c168c3e6c29829f007f015b0a0b5c79c73ee8599a0c72e440e7f5cfdbf19e80e2d77b9a313b8f66e180a330cf1b2 languageName: node linkType: hard @@ -17145,46 +17543,15 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"mdast-util-definitions@npm:^5.0.0": - version: 5.1.2 - resolution: "mdast-util-definitions@npm:5.1.2" - dependencies: - "@types/mdast": ^3.0.0 - "@types/unist": ^2.0.0 - unist-util-visit: ^4.0.0 - checksum: 2544daccab744ea1ede76045c2577ae4f1cc1b9eb1ea51ab273fe1dca8db5a8d6f50f87759c0ce6484975914b144b7f40316f805cb9c86223a78db8de0b77bae - languageName: node - linkType: hard - -"mdast-util-find-and-replace@npm:^2.0.0": - version: 2.2.2 - resolution: "mdast-util-find-and-replace@npm:2.2.2" +"mdast-util-find-and-replace@npm:^3.0.0": + version: 3.0.1 + resolution: "mdast-util-find-and-replace@npm:3.0.1" dependencies: - "@types/mdast": ^3.0.0 + "@types/mdast": ^4.0.0 escape-string-regexp: ^5.0.0 - unist-util-is: ^5.0.0 - unist-util-visit-parents: ^5.0.0 - checksum: b4ce463c43fe6e1c38a53a89703f755c84ab5437f49bff9a0ac751279733332ca11c85ed0262aa6c17481f77b555d26ca6d64e70d6814f5b8d12d34a3e53a60b - languageName: node - linkType: hard - -"mdast-util-from-markdown@npm:^1.0.0, mdast-util-from-markdown@npm:^1.1.0": - version: 1.3.1 - resolution: "mdast-util-from-markdown@npm:1.3.1" - dependencies: - "@types/mdast": ^3.0.0 - "@types/unist": ^2.0.0 - decode-named-character-reference: ^1.0.0 - mdast-util-to-string: ^3.1.0 - micromark: ^3.0.0 - micromark-util-decode-numeric-character-reference: ^1.0.0 - micromark-util-decode-string: ^1.0.0 - micromark-util-normalize-identifier: ^1.0.0 - micromark-util-symbol: ^1.0.0 - micromark-util-types: ^1.0.0 - unist-util-stringify-position: ^3.0.0 - uvu: ^0.5.0 - checksum: c2fac225167e248d394332a4ea39596e04cbde07d8cdb3889e91e48972c4c3462a02b39fda3855345d90231eb17a90ac6e082fb4f012a77c1d0ddfb9c7446940 + unist-util-is: ^6.0.0 + unist-util-visit-parents: ^6.0.0 + checksum: 05d5c4ff02e31db2f8a685a13bcb6c3f44e040bd9dfa54c19a232af8de5268334c8755d79cb456ed4cced1300c4fb83e88444c7ae8ee9ff16869a580f29d08cd languageName: node linkType: hard @@ -17228,86 +17595,95 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"mdast-util-gfm-autolink-literal@npm:^1.0.0": - version: 1.0.3 - resolution: "mdast-util-gfm-autolink-literal@npm:1.0.3" +"mdast-util-gfm-autolink-literal@npm:^2.0.0": + version: 2.0.0 + resolution: "mdast-util-gfm-autolink-literal@npm:2.0.0" dependencies: - "@types/mdast": ^3.0.0 + "@types/mdast": ^4.0.0 ccount: ^2.0.0 - mdast-util-find-and-replace: ^2.0.0 - micromark-util-character: ^1.0.0 - checksum: 1748a8727cfc533bac0c287d6e72d571d165bfa77ae0418be4828177a3ec73c02c3f2ee534d87eb75cbaffa00c0866853bbcc60ae2255babb8210f7636ec2ce2 + devlop: ^1.0.0 + mdast-util-find-and-replace: ^3.0.0 + micromark-util-character: ^2.0.0 + checksum: 10322662e5302964bed7c9829c5fd3b0c9899d4f03e63fb8620ab141cf4f3de9e61fcb4b44d46aacc8a23f82bcd5d900980a211825dfe026b1dab5fdbc3e8742 languageName: node linkType: hard -"mdast-util-gfm-footnote@npm:^1.0.0": - version: 1.0.2 - resolution: "mdast-util-gfm-footnote@npm:1.0.2" +"mdast-util-gfm-footnote@npm:^2.0.0": + version: 2.0.0 + resolution: "mdast-util-gfm-footnote@npm:2.0.0" dependencies: - "@types/mdast": ^3.0.0 - mdast-util-to-markdown: ^1.3.0 - micromark-util-normalize-identifier: ^1.0.0 - checksum: 2d77505f9377ed7e14472ef5e6b8366c3fec2cf5f936bb36f9fbe5b97ccb7cce0464d9313c236fa86fb844206fd585db05707e4fcfb755e4fc1864194845f1f6 + "@types/mdast": ^4.0.0 + devlop: ^1.1.0 + mdast-util-from-markdown: ^2.0.0 + mdast-util-to-markdown: ^2.0.0 + micromark-util-normalize-identifier: ^2.0.0 + checksum: 45d26b40e7a093712e023105791129d76e164e2168d5268e113298a22de30c018162683fb7893cdc04ab246dac0087eed708b2a136d1d18ed2b32b3e0cae4a79 languageName: node linkType: hard -"mdast-util-gfm-strikethrough@npm:^1.0.0": - version: 1.0.3 - resolution: "mdast-util-gfm-strikethrough@npm:1.0.3" +"mdast-util-gfm-strikethrough@npm:^2.0.0": + version: 2.0.0 + resolution: "mdast-util-gfm-strikethrough@npm:2.0.0" dependencies: - "@types/mdast": ^3.0.0 - mdast-util-to-markdown: ^1.3.0 - checksum: 17003340ff1bba643ec4a59fd4370fc6a32885cab2d9750a508afa7225ea71449fb05acaef60faa89c6378b8bcfbd86a9d94b05f3c6651ff27a60e3ddefc2549 + "@types/mdast": ^4.0.0 + mdast-util-from-markdown: ^2.0.0 + mdast-util-to-markdown: ^2.0.0 + checksum: fe9b1d0eba9b791ff9001c008744eafe3dd7a81b085f2bf521595ce4a8e8b1b44764ad9361761ad4533af3e5d913d8ad053abec38172031d9ee32a8ebd1c7dbd languageName: node linkType: hard -"mdast-util-gfm-table@npm:^1.0.0": - version: 1.0.7 - resolution: "mdast-util-gfm-table@npm:1.0.7" +"mdast-util-gfm-table@npm:^2.0.0": + version: 2.0.0 + resolution: "mdast-util-gfm-table@npm:2.0.0" dependencies: - "@types/mdast": ^3.0.0 + "@types/mdast": ^4.0.0 + devlop: ^1.0.0 markdown-table: ^3.0.0 - mdast-util-from-markdown: ^1.0.0 - mdast-util-to-markdown: ^1.3.0 - checksum: 8b8c401bb4162e53f072a2dff8efbca880fd78d55af30601c791315ab6722cb2918176e8585792469a0c530cebb9df9b4e7fede75fdc4d83df2839e238836692 + mdast-util-from-markdown: ^2.0.0 + mdast-util-to-markdown: ^2.0.0 + checksum: 063a627fd0993548fd63ca0c24c437baf91ba7d51d0a38820bd459bc20bf3d13d7365ef8d28dca99176dd5eb26058f7dde51190479c186dfe6af2e11202957c9 languageName: node linkType: hard -"mdast-util-gfm-task-list-item@npm:^1.0.0": - version: 1.0.2 - resolution: "mdast-util-gfm-task-list-item@npm:1.0.2" +"mdast-util-gfm-task-list-item@npm:^2.0.0": + version: 2.0.0 + resolution: "mdast-util-gfm-task-list-item@npm:2.0.0" dependencies: - "@types/mdast": ^3.0.0 - mdast-util-to-markdown: ^1.3.0 - checksum: c9b86037d6953b84f11fb2fc3aa23d5b8e14ca0dfcb0eb2fb289200e172bb9d5647bfceb4f86606dc6d935e8d58f6a458c04d3e55e87ff8513c7d4ade976200b + "@types/mdast": ^4.0.0 + devlop: ^1.0.0 + mdast-util-from-markdown: ^2.0.0 + mdast-util-to-markdown: ^2.0.0 + checksum: 37db90c59b15330fc54d790404abf5ef9f2f83e8961c53666fe7de4aab8dd5e6b3c296b6be19797456711a89a27840291d8871ff0438e9b4e15c89d170efe072 languageName: node linkType: hard -"mdast-util-gfm@npm:^2.0.0": - version: 2.0.2 - resolution: "mdast-util-gfm@npm:2.0.2" +"mdast-util-gfm@npm:^3.0.0": + version: 3.0.0 + resolution: "mdast-util-gfm@npm:3.0.0" dependencies: - mdast-util-from-markdown: ^1.0.0 - mdast-util-gfm-autolink-literal: ^1.0.0 - mdast-util-gfm-footnote: ^1.0.0 - mdast-util-gfm-strikethrough: ^1.0.0 - mdast-util-gfm-table: ^1.0.0 - mdast-util-gfm-task-list-item: ^1.0.0 - mdast-util-to-markdown: ^1.0.0 - checksum: 7078cb985255208bcbce94a121906417d38353c6b1a9acbe56ee8888010d3500608b5d51c16b0999ac63ca58848fb13012d55f26930ff6c6f3450f053d56514e + mdast-util-from-markdown: ^2.0.0 + mdast-util-gfm-autolink-literal: ^2.0.0 + mdast-util-gfm-footnote: ^2.0.0 + mdast-util-gfm-strikethrough: ^2.0.0 + mdast-util-gfm-table: ^2.0.0 + mdast-util-gfm-task-list-item: ^2.0.0 + mdast-util-to-markdown: ^2.0.0 + checksum: 62039d2f682ae3821ea1c999454863d31faf94d67eb9b746589c7e136076d7fb35fabc67e02f025c7c26fd7919331a0ee1aabfae24f565d9a6a9ebab3371c626 languageName: node linkType: hard -"mdast-util-mdx-expression@npm:^1.0.0": - version: 1.3.2 - resolution: "mdast-util-mdx-expression@npm:1.3.2" +"mdast-util-math@npm:^3.0.0": + version: 3.0.0 + resolution: "mdast-util-math@npm:3.0.0" dependencies: - "@types/estree-jsx": ^1.0.0 - "@types/hast": ^2.0.0 - "@types/mdast": ^3.0.0 - mdast-util-from-markdown: ^1.0.0 - mdast-util-to-markdown: ^1.0.0 - checksum: e4c90f26deaa5eb6217b0a9af559a80de41da02ab3bcd864c56bed3304b056ae703896e9876bc6ded500f4aff59f4de5cbf6a4b109a5ba408f2342805fe6dc05 + "@types/hast": ^3.0.0 + "@types/mdast": ^4.0.0 + devlop: ^1.0.0 + longest-streak: ^3.0.0 + mdast-util-from-markdown: ^2.0.0 + mdast-util-to-markdown: ^2.1.0 + unist-util-remove-position: ^5.0.0 + checksum: dc7dfb14aec2ec143420f2d92f80c5e6d69293d7cfb6b8180e7f411ce4e1314b5cf4a8d3345eefe06ab0ddd95e3c7801c4174b343fd2c26741180ca4dbad5371 languageName: node linkType: hard @@ -17325,26 +17701,6 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"mdast-util-mdx-jsx@npm:^2.0.0": - version: 2.1.4 - resolution: "mdast-util-mdx-jsx@npm:2.1.4" - dependencies: - "@types/estree-jsx": ^1.0.0 - "@types/hast": ^2.0.0 - "@types/mdast": ^3.0.0 - "@types/unist": ^2.0.0 - ccount: ^2.0.0 - mdast-util-from-markdown: ^1.1.0 - mdast-util-to-markdown: ^1.3.0 - parse-entities: ^4.0.0 - stringify-entities: ^4.0.0 - unist-util-remove-position: ^4.0.0 - unist-util-stringify-position: ^3.0.0 - vfile-message: ^3.0.0 - checksum: add3ff2dd1faf2419b506abb630a471da42edc99e16fdcff95f405d27f881cb4890a94b2a7a38de9592f37170bee1c135bc156699a0f74af4b69610f0b5fcf1d - languageName: node - linkType: hard - "mdast-util-mdx-jsx@npm:^3.0.0": version: 3.1.2 resolution: "mdast-util-mdx-jsx@npm:3.1.2" @@ -17366,19 +17722,6 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"mdast-util-mdx@npm:^2.0.0": - version: 2.0.1 - resolution: "mdast-util-mdx@npm:2.0.1" - dependencies: - mdast-util-from-markdown: ^1.0.0 - mdast-util-mdx-expression: ^1.0.0 - mdast-util-mdx-jsx: ^2.0.0 - mdast-util-mdxjs-esm: ^1.0.0 - mdast-util-to-markdown: ^1.0.0 - checksum: 7303149230a26e524e319833b782bffca94e49cdab012996618701259bd056e014ca22a35d25ffa8880ba9064ee126a2a002f01e5c90a31ca726339ed775875e - languageName: node - linkType: hard - "mdast-util-mdx@npm:^3.0.0": version: 3.0.0 resolution: "mdast-util-mdx@npm:3.0.0" @@ -17392,19 +17735,6 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"mdast-util-mdxjs-esm@npm:^1.0.0": - version: 1.3.1 - resolution: "mdast-util-mdxjs-esm@npm:1.3.1" - dependencies: - "@types/estree-jsx": ^1.0.0 - "@types/hast": ^2.0.0 - "@types/mdast": ^3.0.0 - mdast-util-from-markdown: ^1.0.0 - mdast-util-to-markdown: ^1.0.0 - checksum: ee78a4f58adfec38723cbc920f05481201ebb001eff3982f2d0e5f5ce5c75685e732e9d361ad4a1be8b936b4e5de0f2599cb96b92ad4bd92698ac0c4a09bbec3 - languageName: node - linkType: hard - "mdast-util-mdxjs-esm@npm:^2.0.0": version: 2.0.1 resolution: "mdast-util-mdxjs-esm@npm:2.0.1" @@ -17419,16 +17749,6 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"mdast-util-phrasing@npm:^3.0.0": - version: 3.0.1 - resolution: "mdast-util-phrasing@npm:3.0.1" - dependencies: - "@types/mdast": ^3.0.0 - unist-util-is: ^5.0.0 - checksum: c5b616d9b1eb76a6b351d195d94318494722525a12a89d9c8a3b091af7db3dd1fc55d294f9d29266d8159a8267b0df4a7a133bda8a3909d5331c383e1e1ff328 - languageName: node - linkType: hard - "mdast-util-phrasing@npm:^4.0.0": version: 4.1.0 resolution: "mdast-util-phrasing@npm:4.1.0" @@ -17439,39 +17759,24 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"mdast-util-to-hast@npm:^12.1.0": - version: 12.3.0 - resolution: "mdast-util-to-hast@npm:12.3.0" +"mdast-util-to-hast@npm:^13.0.0": + version: 13.2.0 + resolution: "mdast-util-to-hast@npm:13.2.0" dependencies: - "@types/hast": ^2.0.0 - "@types/mdast": ^3.0.0 - mdast-util-definitions: ^5.0.0 - micromark-util-sanitize-uri: ^1.1.0 + "@types/hast": ^3.0.0 + "@types/mdast": ^4.0.0 + "@ungap/structured-clone": ^1.0.0 + devlop: ^1.0.0 + micromark-util-sanitize-uri: ^2.0.0 trim-lines: ^3.0.0 - unist-util-generated: ^2.0.0 - unist-util-position: ^4.0.0 - unist-util-visit: ^4.0.0 - checksum: ea40c9f07dd0b731754434e81c913590c611b1fd753fa02550a1492aadfc30fb3adecaf62345ebb03cea2ddd250c15ab6e578fffde69c19955c9b87b10f2a9bb - languageName: node - linkType: hard - -"mdast-util-to-markdown@npm:^1.0.0, mdast-util-to-markdown@npm:^1.3.0": - version: 1.5.0 - resolution: "mdast-util-to-markdown@npm:1.5.0" - dependencies: - "@types/mdast": ^3.0.0 - "@types/unist": ^2.0.0 - longest-streak: ^3.0.0 - mdast-util-phrasing: ^3.0.0 - mdast-util-to-string: ^3.0.0 - micromark-util-decode-string: ^1.0.0 - unist-util-visit: ^4.0.0 - zwitch: ^2.0.0 - checksum: 64338eb33e49bb0aea417591fd986f72fdd39205052563bb7ce9eb9ecc160824509bfacd740086a05af355c6d5c36353aafe95cab9e6927d674478757cee6259 + unist-util-position: ^5.0.0 + unist-util-visit: ^5.0.0 + vfile: ^6.0.0 + checksum: 7e5231ff3d4e35e1421908437577fd5098141f64918ff5cc8a0f7a8a76c5407f7a3ee88d75f7a1f7afb763989c9f357475fa0ba8296c00aaff1e940098fe86a6 languageName: node linkType: hard -"mdast-util-to-markdown@npm:^2.0.0": +"mdast-util-to-markdown@npm:^2.0.0, mdast-util-to-markdown@npm:^2.1.0": version: 2.1.0 resolution: "mdast-util-to-markdown@npm:2.1.0" dependencies: @@ -17487,15 +17792,6 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"mdast-util-to-string@npm:^3.0.0, mdast-util-to-string@npm:^3.1.0": - version: 3.2.0 - resolution: "mdast-util-to-string@npm:3.2.0" - dependencies: - "@types/mdast": ^3.0.0 - checksum: dc40b544d54339878ae2c9f2b3198c029e1e07291d2126bd00ca28272ee6616d0d2194eb1c9828a7c34d412a79a7e73b26512a734698d891c710a1e73db1e848 - languageName: node - linkType: hard - "mdast-util-to-string@npm:^4.0.0": version: 4.0.0 resolution: "mdast-util-to-string@npm:4.0.0" @@ -17590,30 +17886,6 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"micromark-core-commonmark@npm:^1.0.0, micromark-core-commonmark@npm:^1.0.1": - version: 1.1.0 - resolution: "micromark-core-commonmark@npm:1.1.0" - dependencies: - decode-named-character-reference: ^1.0.0 - micromark-factory-destination: ^1.0.0 - micromark-factory-label: ^1.0.0 - micromark-factory-space: ^1.0.0 - micromark-factory-title: ^1.0.0 - micromark-factory-whitespace: ^1.0.0 - micromark-util-character: ^1.0.0 - micromark-util-chunked: ^1.0.0 - micromark-util-classify-character: ^1.0.0 - micromark-util-html-tag-name: ^1.0.0 - micromark-util-normalize-identifier: ^1.0.0 - micromark-util-resolve-all: ^1.0.0 - micromark-util-subtokenize: ^1.0.0 - micromark-util-symbol: ^1.0.0 - micromark-util-types: ^1.0.1 - uvu: ^0.5.0 - checksum: c6dfedc95889cc73411cb222fc2330b9eda6d849c09c9fd9eb3cd3398af246167e9d3cdb0ae3ce9ae59dd34a14624c8330e380255d41279ad7350cf6c6be6c5b - languageName: node - linkType: hard - "micromark-core-commonmark@npm:^2.0.0": version: 2.0.1 resolution: "micromark-core-commonmark@npm:2.0.1" @@ -17638,112 +17910,111 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"micromark-extension-gfm-autolink-literal@npm:^1.0.0": - version: 1.0.5 - resolution: "micromark-extension-gfm-autolink-literal@npm:1.0.5" +"micromark-extension-gfm-autolink-literal@npm:^2.0.0": + version: 2.1.0 + resolution: "micromark-extension-gfm-autolink-literal@npm:2.1.0" dependencies: - micromark-util-character: ^1.0.0 - micromark-util-sanitize-uri: ^1.0.0 - micromark-util-symbol: ^1.0.0 - micromark-util-types: ^1.0.0 - checksum: ec2f6bc4a3eb238c1b8be9744454ffbc2957e3d8a248697af5a26bb21479862300c0e40e0a92baf17c299ddf70d4bc4470d4eee112cd92322f87d81e45c2e83d + micromark-util-character: ^2.0.0 + micromark-util-sanitize-uri: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: e00a570c70c837b9cbbe94b2c23b787f44e781cd19b72f1828e3453abca2a9fb600fa539cdc75229fa3919db384491063645086e02249481e6ff3ec2c18f767c languageName: node linkType: hard -"micromark-extension-gfm-footnote@npm:^1.0.0": - version: 1.1.2 - resolution: "micromark-extension-gfm-footnote@npm:1.1.2" +"micromark-extension-gfm-footnote@npm:^2.0.0": + version: 2.1.0 + resolution: "micromark-extension-gfm-footnote@npm:2.1.0" dependencies: - micromark-core-commonmark: ^1.0.0 - micromark-factory-space: ^1.0.0 - micromark-util-character: ^1.0.0 - micromark-util-normalize-identifier: ^1.0.0 - micromark-util-sanitize-uri: ^1.0.0 - micromark-util-symbol: ^1.0.0 - micromark-util-types: ^1.0.0 - uvu: ^0.5.0 - checksum: c151a629ee1cd92363c018a50f926a002c944ac481ca72b3720b9529e9c20f1cbef98b0fefdcd2d594af37d0d9743673409cac488af0d2b194210fd16375dcb7 + devlop: ^1.0.0 + micromark-core-commonmark: ^2.0.0 + micromark-factory-space: ^2.0.0 + micromark-util-character: ^2.0.0 + micromark-util-normalize-identifier: ^2.0.0 + micromark-util-sanitize-uri: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: ac6fb039e98395d37b71ebff7c7a249aef52678b5cf554c89c4f716111d4be62ef99a5d715a5bd5d68fa549778c977d85cb671d1d8506dc8a3a1b46e867ae52f languageName: node linkType: hard -"micromark-extension-gfm-strikethrough@npm:^1.0.0": - version: 1.0.7 - resolution: "micromark-extension-gfm-strikethrough@npm:1.0.7" +"micromark-extension-gfm-strikethrough@npm:^2.0.0": + version: 2.1.0 + resolution: "micromark-extension-gfm-strikethrough@npm:2.1.0" dependencies: - micromark-util-chunked: ^1.0.0 - micromark-util-classify-character: ^1.0.0 - micromark-util-resolve-all: ^1.0.0 - micromark-util-symbol: ^1.0.0 - micromark-util-types: ^1.0.0 - uvu: ^0.5.0 - checksum: 169e310a4408feade0df80180f60d48c5cc5b7070e5e75e0bbd914e9100273508162c4bb20b72d53081dc37f1ff5834b3afa137862576f763878552c03389811 + devlop: ^1.0.0 + micromark-util-chunked: ^2.0.0 + micromark-util-classify-character: ^2.0.0 + micromark-util-resolve-all: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: cdb7a38dd6eefb6ceb6792a44a6796b10f951e8e3e45b8579f599f43e7ae26ccd048c0aa7e441b3c29dd0c54656944fe6eb0098de2bc4b5106fbc0a42e9e016c languageName: node linkType: hard -"micromark-extension-gfm-table@npm:^1.0.0": - version: 1.0.7 - resolution: "micromark-extension-gfm-table@npm:1.0.7" +"micromark-extension-gfm-table@npm:^2.0.0": + version: 2.1.0 + resolution: "micromark-extension-gfm-table@npm:2.1.0" dependencies: - micromark-factory-space: ^1.0.0 - micromark-util-character: ^1.0.0 - micromark-util-symbol: ^1.0.0 - micromark-util-types: ^1.0.0 - uvu: ^0.5.0 - checksum: 4853731285224e409d7e2c94c6ec849165093bff819e701221701aa7b7b34c17702c44f2f831e96b49dc27bb07e445b02b025561b68e62f5c3254415197e7af6 + devlop: ^1.0.0 + micromark-factory-space: ^2.0.0 + micromark-util-character: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: 249d695f5f8bd222a0d8a774ec78ea2a2d624cb50a4d008092a54aa87dad1f9d540e151d29696cf849eb1cee380113c4df722aebb3b425a214832a2de5dea1d7 languageName: node linkType: hard -"micromark-extension-gfm-tagfilter@npm:^1.0.0": - version: 1.0.2 - resolution: "micromark-extension-gfm-tagfilter@npm:1.0.2" +"micromark-extension-gfm-tagfilter@npm:^2.0.0": + version: 2.0.0 + resolution: "micromark-extension-gfm-tagfilter@npm:2.0.0" dependencies: - micromark-util-types: ^1.0.0 - checksum: 7d2441df51f890c86f8e7cf7d331a570b69c8105fa1c2fc5b737cb739502c16c8ee01cf35550a8a78f89497c5dfacc97cf82d55de6274e8320f3aec25e2b0dd2 + micromark-util-types: ^2.0.0 + checksum: cf21552f4a63592bfd6c96ae5d64a5f22bda4e77814e3f0501bfe80e7a49378ad140f827007f36044666f176b3a0d5fea7c2e8e7973ce4b4579b77789f01ae95 languageName: node linkType: hard -"micromark-extension-gfm-task-list-item@npm:^1.0.0": - version: 1.0.5 - resolution: "micromark-extension-gfm-task-list-item@npm:1.0.5" +"micromark-extension-gfm-task-list-item@npm:^2.0.0": + version: 2.1.0 + resolution: "micromark-extension-gfm-task-list-item@npm:2.1.0" dependencies: - micromark-factory-space: ^1.0.0 - micromark-util-character: ^1.0.0 - micromark-util-symbol: ^1.0.0 - micromark-util-types: ^1.0.0 - uvu: ^0.5.0 - checksum: 929f05343d272cffb8008899289f4cffe986ef98fc622ebbd1aa4ff11470e6b32ed3e1f18cd294adb69cabb961a400650078f6c12b322cc515b82b5068b31960 + devlop: ^1.0.0 + micromark-factory-space: ^2.0.0 + micromark-util-character: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: b1ad86a4e9d68d9ad536d94fb25a5182acbc85cc79318f4a6316034342f6a71d67983cc13f12911d0290fd09b2bda43cdabe8781a2d9cca2ebe0d421e8b2b8a4 languageName: node linkType: hard -"micromark-extension-gfm@npm:^2.0.0": - version: 2.0.3 - resolution: "micromark-extension-gfm@npm:2.0.3" - dependencies: - micromark-extension-gfm-autolink-literal: ^1.0.0 - micromark-extension-gfm-footnote: ^1.0.0 - micromark-extension-gfm-strikethrough: ^1.0.0 - micromark-extension-gfm-table: ^1.0.0 - micromark-extension-gfm-tagfilter: ^1.0.0 - micromark-extension-gfm-task-list-item: ^1.0.0 - micromark-util-combine-extensions: ^1.0.0 - micromark-util-types: ^1.0.0 - checksum: c4a917c16d7aa5d00d1767b5ce5f3b1a78c0de11dbd5c8f69d2545083568aa6bb13bd9d8e4c7fec5f4da10e7ed8344b15acffc843b33a615c17396a118bc2bc1 +"micromark-extension-gfm@npm:^3.0.0": + version: 3.0.0 + resolution: "micromark-extension-gfm@npm:3.0.0" + dependencies: + micromark-extension-gfm-autolink-literal: ^2.0.0 + micromark-extension-gfm-footnote: ^2.0.0 + micromark-extension-gfm-strikethrough: ^2.0.0 + micromark-extension-gfm-table: ^2.0.0 + micromark-extension-gfm-tagfilter: ^2.0.0 + micromark-extension-gfm-task-list-item: ^2.0.0 + micromark-util-combine-extensions: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: 2060fa62666a09532d6b3a272d413bc1b25bbb262f921d7402795ac021e1362c8913727e33d7528d5b4ccaf26922ec51208c43f795a702964817bc986de886c9 languageName: node linkType: hard -"micromark-extension-mdx-expression@npm:^1.0.0": - version: 1.0.8 - resolution: "micromark-extension-mdx-expression@npm:1.0.8" +"micromark-extension-math@npm:^3.0.0": + version: 3.1.0 + resolution: "micromark-extension-math@npm:3.1.0" dependencies: - "@types/estree": ^1.0.0 - micromark-factory-mdx-expression: ^1.0.0 - micromark-factory-space: ^1.0.0 - micromark-util-character: ^1.0.0 - micromark-util-events-to-acorn: ^1.0.0 - micromark-util-symbol: ^1.0.0 - micromark-util-types: ^1.0.0 - uvu: ^0.5.0 - checksum: 49750d10c1664904a5eb61b8dae2a4ff31eef56176d02ff30de4ee4b5db7ca4598b6f044963c26771f53e2a5a517a9ff7223d87fc0b6e159332d77e4f3486cc3 + "@types/katex": ^0.16.0 + devlop: ^1.0.0 + katex: ^0.16.0 + micromark-factory-space: ^2.0.0 + micromark-util-character: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: 60a9813d456a7bf1ca493b5b9a1f1df3828b5f635fdc72a3b36a0cf1ebded2a9ed12899493d80578a737d1e36e94113da09aed381f99d0103e82467f16989e28 languageName: node linkType: hard @@ -17763,24 +18034,6 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"micromark-extension-mdx-jsx@npm:^1.0.0": - version: 1.0.5 - resolution: "micromark-extension-mdx-jsx@npm:1.0.5" - dependencies: - "@types/acorn": ^4.0.0 - "@types/estree": ^1.0.0 - estree-util-is-identifier-name: ^2.0.0 - micromark-factory-mdx-expression: ^1.0.0 - micromark-factory-space: ^1.0.0 - micromark-util-character: ^1.0.0 - micromark-util-symbol: ^1.0.0 - micromark-util-types: ^1.0.0 - uvu: ^0.5.0 - vfile-message: ^3.0.0 - checksum: 0ddb7b71c2c5f51f1232546d316b6c126ad245d57690b1af7877dd7b678b8b700d85a78587d56525b26a04082a4e833c6c9199c2db2a3379adf014be796123fb - languageName: node - linkType: hard - "micromark-extension-mdx-jsx@npm:^3.0.0": version: 3.0.0 resolution: "micromark-extension-mdx-jsx@npm:3.0.0" @@ -17799,15 +18052,6 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"micromark-extension-mdx-md@npm:^1.0.0": - version: 1.0.1 - resolution: "micromark-extension-mdx-md@npm:1.0.1" - dependencies: - micromark-util-types: ^1.0.0 - checksum: fdeaf8f4f973ec8ebefb74bb4cc1c25d2c3190e3ce4f8197e4cbc1ac325b39ac4dc2723a9f4ec8ff5b179d380e8ba37467acafa13c36dec8d312cd9822a5ab29 - languageName: node - linkType: hard - "micromark-extension-mdx-md@npm:^2.0.0": version: 2.0.0 resolution: "micromark-extension-mdx-md@npm:2.0.0" @@ -17830,47 +18074,36 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"micromark-extension-mdxjs-esm@npm:^1.0.0": - version: 1.0.5 - resolution: "micromark-extension-mdxjs-esm@npm:1.0.5" +"micromark-extension-mdxjs-esm@npm:^3.0.0": + version: 3.0.0 + resolution: "micromark-extension-mdxjs-esm@npm:3.0.0" dependencies: "@types/estree": ^1.0.0 - micromark-core-commonmark: ^1.0.0 - micromark-util-character: ^1.0.0 - micromark-util-events-to-acorn: ^1.0.0 - micromark-util-symbol: ^1.0.0 - micromark-util-types: ^1.0.0 - unist-util-position-from-estree: ^1.1.0 - uvu: ^0.5.0 - vfile-message: ^3.0.0 - checksum: 7006cfa963d63a56c2744a9b03021aeba99a24b0a4f769165a13446439c8df529448a63db5f3ae604d1a4f616bbebde8efd1f495d7be32acb064491878e38fbe + devlop: ^1.0.0 + micromark-core-commonmark: ^2.0.0 + micromark-util-character: ^2.0.0 + micromark-util-events-to-acorn: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + unist-util-position-from-estree: ^2.0.0 + vfile-message: ^4.0.0 + checksum: fb33d850200afce567b95c90f2f7d42259bd33eea16154349e4fa77c3ec934f46c8e5c111acea16321dce3d9f85aaa4c49afe8b810e31b34effc11617aeee8f6 languageName: node linkType: hard -"micromark-extension-mdxjs@npm:^1.0.0": - version: 1.0.1 - resolution: "micromark-extension-mdxjs@npm:1.0.1" +"micromark-extension-mdxjs@npm:^3.0.0": + version: 3.0.0 + resolution: "micromark-extension-mdxjs@npm:3.0.0" dependencies: acorn: ^8.0.0 - acorn-jsx: ^5.0.0 - micromark-extension-mdx-expression: ^1.0.0 - micromark-extension-mdx-jsx: ^1.0.0 - micromark-extension-mdx-md: ^1.0.0 - micromark-extension-mdxjs-esm: ^1.0.0 - micromark-util-combine-extensions: ^1.0.0 - micromark-util-types: ^1.0.0 - checksum: 1e6bf3df765071dbfb80b20f1ca298f6789cf759dfd19d13301c91e33794940363989107b675afeedb78af446e4af590e0f0cf8c1ed63a70682a494c015eba52 - languageName: node - linkType: hard - -"micromark-factory-destination@npm:^1.0.0": - version: 1.1.0 - resolution: "micromark-factory-destination@npm:1.1.0" - dependencies: - micromark-util-character: ^1.0.0 - micromark-util-symbol: ^1.0.0 - micromark-util-types: ^1.0.0 - checksum: 9e2b5fb5fedbf622b687e20d51eb3d56ae90c0e7ecc19b37bd5285ec392c1e56f6e21aa7cfcb3c01eda88df88fe528f3acb91a5f57d7f4cba310bc3cd7f824fa + acorn-jsx: ^5.0.0 + micromark-extension-mdx-expression: ^3.0.0 + micromark-extension-mdx-jsx: ^3.0.0 + micromark-extension-mdx-md: ^2.0.0 + micromark-extension-mdxjs-esm: ^3.0.0 + micromark-util-combine-extensions: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: 7da6f0fb0e1e0270a2f5ad257e7422cc16e68efa7b8214c63c9d55bc264cb872e9ca4ac9a71b9dfd13daf52e010f730bac316086f4340e4fcc6569ec699915bf languageName: node linkType: hard @@ -17885,18 +18118,6 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"micromark-factory-label@npm:^1.0.0": - version: 1.1.0 - resolution: "micromark-factory-label@npm:1.1.0" - dependencies: - micromark-util-character: ^1.0.0 - micromark-util-symbol: ^1.0.0 - micromark-util-types: ^1.0.0 - uvu: ^0.5.0 - checksum: fcda48f1287d9b148c562c627418a2ab759cdeae9c8e017910a0cba94bb759a96611e1fc6df33182e97d28fbf191475237298983bb89ef07d5b02464b1ad28d5 - languageName: node - linkType: hard - "micromark-factory-label@npm:^2.0.0": version: 2.0.0 resolution: "micromark-factory-label@npm:2.0.0" @@ -17909,22 +18130,6 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"micromark-factory-mdx-expression@npm:^1.0.0": - version: 1.0.9 - resolution: "micromark-factory-mdx-expression@npm:1.0.9" - dependencies: - "@types/estree": ^1.0.0 - micromark-util-character: ^1.0.0 - micromark-util-events-to-acorn: ^1.0.0 - micromark-util-symbol: ^1.0.0 - micromark-util-types: ^1.0.0 - unist-util-position-from-estree: ^1.0.0 - uvu: ^0.5.0 - vfile-message: ^3.0.0 - checksum: 7359bf3290bf95c647aff1208d88a58288acdcd15190fe3da8bc56a683615f158a7f0593ace7ae459581079d7a9f7420a68d31ce8f0f1637cadacfb52e7782f0 - languageName: node - linkType: hard - "micromark-factory-mdx-expression@npm:^2.0.0": version: 2.0.1 resolution: "micromark-factory-mdx-expression@npm:2.0.1" @@ -17941,16 +18146,6 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"micromark-factory-space@npm:^1.0.0": - version: 1.1.0 - resolution: "micromark-factory-space@npm:1.1.0" - dependencies: - micromark-util-character: ^1.0.0 - micromark-util-types: ^1.0.0 - checksum: b58435076b998a7e244259a4694eb83c78915581206b6e7fc07b34c6abd36a1726ade63df8972fbf6c8fa38eecb9074f4e17be8d53f942e3b3d23d1a0ecaa941 - languageName: node - linkType: hard - "micromark-factory-space@npm:^2.0.0": version: 2.0.0 resolution: "micromark-factory-space@npm:2.0.0" @@ -17961,18 +18156,6 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"micromark-factory-title@npm:^1.0.0": - version: 1.1.0 - resolution: "micromark-factory-title@npm:1.1.0" - dependencies: - micromark-factory-space: ^1.0.0 - micromark-util-character: ^1.0.0 - micromark-util-symbol: ^1.0.0 - micromark-util-types: ^1.0.0 - checksum: 4432d3dbc828c81f483c5901b0c6591a85d65a9e33f7d96ba7c3ae821617a0b3237ff5faf53a9152d00aaf9afb3a9f185b205590f40ed754f1d9232e0e9157b1 - languageName: node - linkType: hard - "micromark-factory-title@npm:^2.0.0": version: 2.0.0 resolution: "micromark-factory-title@npm:2.0.0" @@ -17985,18 +18168,6 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"micromark-factory-whitespace@npm:^1.0.0": - version: 1.1.0 - resolution: "micromark-factory-whitespace@npm:1.1.0" - dependencies: - micromark-factory-space: ^1.0.0 - micromark-util-character: ^1.0.0 - micromark-util-symbol: ^1.0.0 - micromark-util-types: ^1.0.0 - checksum: ef0fa682c7d593d85a514ee329809dee27d10bc2a2b65217d8ef81173e33b8e83c549049764b1ad851adfe0a204dec5450d9d20a4ca8598f6c94533a73f73fcd - languageName: node - linkType: hard - "micromark-factory-whitespace@npm:^2.0.0": version: 2.0.0 resolution: "micromark-factory-whitespace@npm:2.0.0" @@ -18009,16 +18180,6 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"micromark-util-character@npm:^1.0.0": - version: 1.2.0 - resolution: "micromark-util-character@npm:1.2.0" - dependencies: - micromark-util-symbol: ^1.0.0 - micromark-util-types: ^1.0.0 - checksum: 089e79162a19b4a28731736246579ab7e9482ac93cd681c2bfca9983dcff659212ef158a66a5957e9d4b1dba957d1b87b565d85418a5b009f0294f1f07f2aaac - languageName: node - linkType: hard - "micromark-util-character@npm:^2.0.0": version: 2.1.0 resolution: "micromark-util-character@npm:2.1.0" @@ -18029,15 +18190,6 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"micromark-util-chunked@npm:^1.0.0": - version: 1.1.0 - resolution: "micromark-util-chunked@npm:1.1.0" - dependencies: - micromark-util-symbol: ^1.0.0 - checksum: c435bde9110cb595e3c61b7f54c2dc28ee03e6a57fa0fc1e67e498ad8bac61ee5a7457a2b6a73022ddc585676ede4b912d28dcf57eb3bd6951e54015e14dc20b - languageName: node - linkType: hard - "micromark-util-chunked@npm:^2.0.0": version: 2.0.0 resolution: "micromark-util-chunked@npm:2.0.0" @@ -18047,17 +18199,6 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"micromark-util-classify-character@npm:^1.0.0": - version: 1.1.0 - resolution: "micromark-util-classify-character@npm:1.1.0" - dependencies: - micromark-util-character: ^1.0.0 - micromark-util-symbol: ^1.0.0 - micromark-util-types: ^1.0.0 - checksum: 8499cb0bb1f7fb946f5896285fcca65cd742f66cd3e79ba7744792bd413ec46834f932a286de650349914d02e822946df3b55d03e6a8e1d245d1ddbd5102e5b0 - languageName: node - linkType: hard - "micromark-util-classify-character@npm:^2.0.0": version: 2.0.0 resolution: "micromark-util-classify-character@npm:2.0.0" @@ -18069,16 +18210,6 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"micromark-util-combine-extensions@npm:^1.0.0": - version: 1.1.0 - resolution: "micromark-util-combine-extensions@npm:1.1.0" - dependencies: - micromark-util-chunked: ^1.0.0 - micromark-util-types: ^1.0.0 - checksum: ee78464f5d4b61ccb437850cd2d7da4d690b260bca4ca7a79c4bb70291b84f83988159e373b167181b6716cb197e309bc6e6c96a68cc3ba9d50c13652774aba9 - languageName: node - linkType: hard - "micromark-util-combine-extensions@npm:^2.0.0": version: 2.0.0 resolution: "micromark-util-combine-extensions@npm:2.0.0" @@ -18089,15 +18220,6 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"micromark-util-decode-numeric-character-reference@npm:^1.0.0": - version: 1.1.0 - resolution: "micromark-util-decode-numeric-character-reference@npm:1.1.0" - dependencies: - micromark-util-symbol: ^1.0.0 - checksum: 4733fe75146e37611243f055fc6847137b66f0cde74d080e33bd26d0408c1d6f44cabc984063eee5968b133cb46855e729d555b9ff8d744652262b7b51feec73 - languageName: node - linkType: hard - "micromark-util-decode-numeric-character-reference@npm:^2.0.0": version: 2.0.1 resolution: "micromark-util-decode-numeric-character-reference@npm:2.0.1" @@ -18107,18 +18229,6 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"micromark-util-decode-string@npm:^1.0.0": - version: 1.1.0 - resolution: "micromark-util-decode-string@npm:1.1.0" - dependencies: - decode-named-character-reference: ^1.0.0 - micromark-util-character: ^1.0.0 - micromark-util-decode-numeric-character-reference: ^1.0.0 - micromark-util-symbol: ^1.0.0 - checksum: f1625155db452f15aa472918499689ba086b9c49d1322a08b22bfbcabe918c61b230a3002c8bc3ea9b1f52ca7a9bb1c3dd43ccb548c7f5f8b16c24a1ae77a813 - languageName: node - linkType: hard - "micromark-util-decode-string@npm:^2.0.0": version: 2.0.0 resolution: "micromark-util-decode-string@npm:2.0.0" @@ -18131,13 +18241,6 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"micromark-util-encode@npm:^1.0.0": - version: 1.1.0 - resolution: "micromark-util-encode@npm:1.1.0" - checksum: 4ef29d02b12336918cea6782fa87c8c578c67463925221d4e42183a706bde07f4b8b5f9a5e1c7ce8c73bb5a98b261acd3238fecd152e6dd1cdfa2d1ae11b60a0 - languageName: node - linkType: hard - "micromark-util-encode@npm:^2.0.0": version: 2.0.0 resolution: "micromark-util-encode@npm:2.0.0" @@ -18145,22 +18248,6 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"micromark-util-events-to-acorn@npm:^1.0.0": - version: 1.2.3 - resolution: "micromark-util-events-to-acorn@npm:1.2.3" - dependencies: - "@types/acorn": ^4.0.0 - "@types/estree": ^1.0.0 - "@types/unist": ^2.0.0 - estree-util-visit: ^1.0.0 - micromark-util-symbol: ^1.0.0 - micromark-util-types: ^1.0.0 - uvu: ^0.5.0 - vfile-message: ^3.0.0 - checksum: aba0dadb8689a70fab6223386b843f3084c21db0f96b412ebd7be91d2392bb8571af899c60e13eeb373a5f851d6dcd690b584ed1e09833904ac72ddd0a88a7ab - languageName: node - linkType: hard - "micromark-util-events-to-acorn@npm:^2.0.0": version: 2.0.2 resolution: "micromark-util-events-to-acorn@npm:2.0.2" @@ -18177,13 +18264,6 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"micromark-util-html-tag-name@npm:^1.0.0": - version: 1.2.0 - resolution: "micromark-util-html-tag-name@npm:1.2.0" - checksum: ccf0fa99b5c58676dc5192c74665a3bfd1b536fafaf94723bd7f31f96979d589992df6fcf2862eba290ef18e6a8efb30ec8e1e910d9f3fc74f208871e9f84750 - languageName: node - linkType: hard - "micromark-util-html-tag-name@npm:^2.0.0": version: 2.0.0 resolution: "micromark-util-html-tag-name@npm:2.0.0" @@ -18191,15 +18271,6 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"micromark-util-normalize-identifier@npm:^1.0.0": - version: 1.1.0 - resolution: "micromark-util-normalize-identifier@npm:1.1.0" - dependencies: - micromark-util-symbol: ^1.0.0 - checksum: 8655bea41ffa4333e03fc22462cb42d631bbef9c3c07b625fd852b7eb442a110f9d2e5902a42e65188d85498279569502bf92f3434a1180fc06f7c37edfbaee2 - languageName: node - linkType: hard - "micromark-util-normalize-identifier@npm:^2.0.0": version: 2.0.0 resolution: "micromark-util-normalize-identifier@npm:2.0.0" @@ -18209,15 +18280,6 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"micromark-util-resolve-all@npm:^1.0.0": - version: 1.1.0 - resolution: "micromark-util-resolve-all@npm:1.1.0" - dependencies: - micromark-util-types: ^1.0.0 - checksum: 1ce6c0237cd3ca061e76fae6602cf95014e764a91be1b9f10d36cb0f21ca88f9a07de8d49ab8101efd0b140a4fbfda6a1efb72027ab3f4d5b54c9543271dc52c - languageName: node - linkType: hard - "micromark-util-resolve-all@npm:^2.0.0": version: 2.0.0 resolution: "micromark-util-resolve-all@npm:2.0.0" @@ -18227,17 +18289,6 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"micromark-util-sanitize-uri@npm:^1.0.0, micromark-util-sanitize-uri@npm:^1.1.0": - version: 1.2.0 - resolution: "micromark-util-sanitize-uri@npm:1.2.0" - dependencies: - micromark-util-character: ^1.0.0 - micromark-util-encode: ^1.0.0 - micromark-util-symbol: ^1.0.0 - checksum: 6663f365c4fe3961d622a580f4a61e34867450697f6806f027f21cf63c92989494895fcebe2345d52e249fe58a35be56e223a9776d084c9287818b40c779acc1 - languageName: node - linkType: hard - "micromark-util-sanitize-uri@npm:^2.0.0": version: 2.0.0 resolution: "micromark-util-sanitize-uri@npm:2.0.0" @@ -18249,18 +18300,6 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"micromark-util-subtokenize@npm:^1.0.0": - version: 1.1.0 - resolution: "micromark-util-subtokenize@npm:1.1.0" - dependencies: - micromark-util-chunked: ^1.0.0 - micromark-util-symbol: ^1.0.0 - micromark-util-types: ^1.0.0 - uvu: ^0.5.0 - checksum: 4a9d780c4d62910e196ea4fd886dc4079d8e424e5d625c0820016da0ed399a281daff39c50f9288045cc4bcd90ab47647e5396aba500f0853105d70dc8b1fc45 - languageName: node - linkType: hard - "micromark-util-subtokenize@npm:^2.0.0": version: 2.0.1 resolution: "micromark-util-subtokenize@npm:2.0.1" @@ -18273,13 +18312,6 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"micromark-util-symbol@npm:^1.0.0": - version: 1.1.0 - resolution: "micromark-util-symbol@npm:1.1.0" - checksum: 02414a753b79f67ff3276b517eeac87913aea6c028f3e668a19ea0fc09d98aea9f93d6222a76ca783d20299af9e4b8e7c797fe516b766185dcc6e93290f11f88 - languageName: node - linkType: hard - "micromark-util-symbol@npm:^2.0.0": version: 2.0.0 resolution: "micromark-util-symbol@npm:2.0.0" @@ -18287,13 +18319,6 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"micromark-util-types@npm:^1.0.0, micromark-util-types@npm:^1.0.1": - version: 1.1.0 - resolution: "micromark-util-types@npm:1.1.0" - checksum: b0ef2b4b9589f15aec2666690477a6a185536927ceb7aa55a0f46475852e012d75a1ab945187e5c7841969a842892164b15d58ff8316b8e0d6cc920cabd5ede7 - languageName: node - linkType: hard - "micromark-util-types@npm:^2.0.0": version: 2.0.0 resolution: "micromark-util-types@npm:2.0.0" @@ -18301,31 +18326,6 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"micromark@npm:^3.0.0": - version: 3.2.0 - resolution: "micromark@npm:3.2.0" - dependencies: - "@types/debug": ^4.0.0 - debug: ^4.0.0 - decode-named-character-reference: ^1.0.0 - micromark-core-commonmark: ^1.0.1 - micromark-factory-space: ^1.0.0 - micromark-util-character: ^1.0.0 - micromark-util-chunked: ^1.0.0 - micromark-util-combine-extensions: ^1.0.0 - micromark-util-decode-numeric-character-reference: ^1.0.0 - micromark-util-encode: ^1.0.0 - micromark-util-normalize-identifier: ^1.0.0 - micromark-util-resolve-all: ^1.0.0 - micromark-util-sanitize-uri: ^1.0.0 - micromark-util-subtokenize: ^1.0.0 - micromark-util-symbol: ^1.0.0 - micromark-util-types: ^1.0.1 - uvu: ^0.5.0 - checksum: 56c15851ad3eb8301aede65603473443e50c92a54849cac1dadd57e4ec33ab03a0a77f3df03de47133e6e8f695dae83b759b514586193269e98c0bf319ecd5e4 - languageName: node - linkType: hard - "micromark@npm:^4.0.0": version: 4.0.0 resolution: "micromark@npm:4.0.0" @@ -18657,13 +18657,6 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"mri@npm:^1.1.0": - version: 1.2.0 - resolution: "mri@npm:1.2.0" - checksum: 83f515abbcff60150873e424894a2f65d68037e5a7fcde8a9e2b285ee9c13ac581b63cfc1e6826c4732de3aeb84902f7c1e16b7aff46cd3f897a0f757a894e85 - languageName: node - linkType: hard - "mrlint@npm:0.1.0": version: 0.1.0 resolution: "mrlint@npm:0.1.0" @@ -18788,18 +18781,19 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"next-mdx-remote@npm:^4.4.1": - version: 4.4.1 - resolution: "next-mdx-remote@npm:4.4.1" +"next-mdx-remote@npm:^5.0.0": + version: 5.0.0 + resolution: "next-mdx-remote@npm:5.0.0" dependencies: - "@mdx-js/mdx": ^2.2.1 - "@mdx-js/react": ^2.2.1 - vfile: ^5.3.0 - vfile-matter: ^3.0.1 + "@babel/code-frame": ^7.23.5 + "@mdx-js/mdx": ^3.0.1 + "@mdx-js/react": ^3.0.1 + unist-util-remove: ^3.1.0 + vfile: ^6.0.1 + vfile-matter: ^5.0.0 peerDependencies: - react: ">=16.x <=18.x" - react-dom: ">=16.x <=18.x" - checksum: 95cd77d03ae8ad7ae691cde0e895597b35a2ddac99cbeb31f1307599b2c7e7628f9e2a0fa5ced8d55036f58d8a2006ae312e308d574b8c3b0948df7279fa393d + react: ">=16" + checksum: 7e8dd7279459953bacca7a0c03db8612bd001f03e39530c8dd437a1acb2befab24e4a8ae959031af7036d0a69e2e8a9bbfe674ec6beb26bf365fd272dd95ec37 languageName: node linkType: hard @@ -18922,6 +18916,13 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard +"node-releases@npm:^2.0.18": + version: 2.0.18 + resolution: "node-releases@npm:2.0.18" + checksum: ef55a3d853e1269a6d6279b7692cd6ff3e40bc74947945101138745bfdc9a5edabfe72cb19a31a8e45752e1910c4c65c77d931866af6357f242b172b7283f5b3 + languageName: node + linkType: hard + "node-releases@npm:^2.0.8": version: 2.0.10 resolution: "node-releases@npm:2.0.10" @@ -20042,6 +20043,13 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard +"path-to-regexp@npm:^2.2.1": + version: 2.4.0 + resolution: "path-to-regexp@npm:2.4.0" + checksum: 581175bf2968e51452f2b8c71f10e75c995693668b4ecf7d0b48962fbe0c56830661ca5dd5fd6d8e2f0cc9a045ce07e89af504ab133e1d21887c2712df85b1f4 + languageName: node + linkType: hard + "path-type@npm:^4.0.0": version: 4.0.0 resolution: "path-type@npm:4.0.0" @@ -20088,6 +20096,13 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard +"picocolors@npm:^1.0.1": + version: 1.0.1 + resolution: "picocolors@npm:1.0.1" + checksum: fa68166d1f56009fc02a34cdfd112b0dd3cf1ef57667ac57281f714065558c01828cdf4f18600ad6851cbe0093952ed0660b1e0156bddf2184b6aaf5817553a5 + languageName: node + linkType: hard + "picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.2.3, picomatch@npm:^2.3.1": version: 2.3.1 resolution: "picomatch@npm:2.3.1" @@ -20646,6 +20661,13 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard +"querystring@npm:^0.2.0": + version: 0.2.1 + resolution: "querystring@npm:0.2.1" + checksum: 7b83b45d641e75fd39cd6625ddfd44e7618e741c61e95281b57bbae8fde0afcc12cf851924559e5cc1ef9baa3b1e06e22b164ea1397d65dd94b801f678d9c8ce + languageName: node + linkType: hard + "querystringify@npm:^2.1.1": version: 2.2.0 resolution: "querystringify@npm:2.2.0" @@ -21003,6 +21025,13 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard +"regenerator-runtime@npm:^0.14.0": + version: 0.14.1 + resolution: "regenerator-runtime@npm:0.14.1" + checksum: 9f57c93277b5585d3c83b0cf76be47b473ae8c6d9142a46ce8b0291a04bb2cf902059f0f8445dcabb3fb7378e5fe4bb4ea1e008876343d42e46d3b484534ce38 + languageName: node + linkType: hard + "regenerator-transform@npm:^0.15.2": version: 0.15.2 resolution: "regenerator-transform@npm:0.15.2" @@ -21156,48 +21185,90 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"remark-gfm@npm:^3.0.1": +"rehype-katex@npm:^7.0.0": + version: 7.0.0 + resolution: "rehype-katex@npm:7.0.0" + dependencies: + "@types/hast": ^3.0.0 + "@types/katex": ^0.16.0 + hast-util-from-html-isomorphic: ^2.0.0 + hast-util-to-text: ^4.0.0 + katex: ^0.16.0 + unist-util-visit-parents: ^6.0.0 + vfile: ^6.0.0 + checksum: 3184cf7635e63039a5d455e27718cbc99998cc7bfcc15422badf5da892887f4200f3ee54a6617aa231aa15d46cb678716c112b6b7f9cef11a8653e5d518ad6f0 + languageName: node + linkType: hard + +"remark-gfm@npm:^4.0.0": + version: 4.0.0 + resolution: "remark-gfm@npm:4.0.0" + dependencies: + "@types/mdast": ^4.0.0 + mdast-util-gfm: ^3.0.0 + micromark-extension-gfm: ^3.0.0 + remark-parse: ^11.0.0 + remark-stringify: ^11.0.0 + unified: ^11.0.0 + checksum: 84bea84e388061fbbb697b4b666089f5c328aa04d19dc544c229b607446bc10902e46b67b9594415a1017bbbd7c811c1f0c30d36682c6d1a6718b66a1558261b + languageName: node + linkType: hard + +"remark-math@npm:^6.0.0": + version: 6.0.0 + resolution: "remark-math@npm:6.0.0" + dependencies: + "@types/mdast": ^4.0.0 + mdast-util-math: ^3.0.0 + micromark-extension-math: ^3.0.0 + unified: ^11.0.0 + checksum: fef489acb6cae6e40af05012367dc22a846ce16301e8a96006c6d78935887bdb3e6c5018b6514884ecee57f9c7a51f97a10862526ab0a0f5f7b7d339fe0eb20f + languageName: node + linkType: hard + +"remark-mdx@npm:^3.0.0": version: 3.0.1 - resolution: "remark-gfm@npm:3.0.1" + resolution: "remark-mdx@npm:3.0.1" dependencies: - "@types/mdast": ^3.0.0 - mdast-util-gfm: ^2.0.0 - micromark-extension-gfm: ^2.0.0 - unified: ^10.0.0 - checksum: 02254f74d67b3419c2c9cf62d799ec35f6c6cd74db25c001361751991552a7ce86049a972107bff8122d85d15ae4a8d1a0618f3bc01a7df837af021ae9b2a04e + mdast-util-mdx: ^3.0.0 + micromark-extension-mdxjs: ^3.0.0 + checksum: e7fcffbe1ccb0c7dfcb01c6d9dbc48df9c668c8321745455db7346f4860c43dbcb98e36e3398a5117d773426ab5ef656a95c78a21208c59e92571f021b8e678e languageName: node linkType: hard -"remark-mdx@npm:^2.0.0": - version: 2.3.0 - resolution: "remark-mdx@npm:2.3.0" +"remark-parse@npm:^11.0.0": + version: 11.0.0 + resolution: "remark-parse@npm:11.0.0" dependencies: - mdast-util-mdx: ^2.0.0 - micromark-extension-mdxjs: ^1.0.0 - checksum: 98486986c5b6f6a8321eb2f3b13c70fcd5644821428c77b7bfeb5ee5d4605b9761b322b2f6b531e83883cd2d5bc7bc4623427149aee00e1eba012f538b3d5627 + "@types/mdast": ^4.0.0 + mdast-util-from-markdown: ^2.0.0 + micromark-util-types: ^2.0.0 + unified: ^11.0.0 + checksum: d83d245290fa84bb04fb3e78111f09c74f7417e7c012a64dd8dc04fccc3699036d828fbd8eeec8944f774b6c30cc1d925c98f8c46495ebcee7c595496342ab7f languageName: node linkType: hard -"remark-parse@npm:^10.0.0": - version: 10.0.2 - resolution: "remark-parse@npm:10.0.2" +"remark-rehype@npm:^11.0.0": + version: 11.1.0 + resolution: "remark-rehype@npm:11.1.0" dependencies: - "@types/mdast": ^3.0.0 - mdast-util-from-markdown: ^1.0.0 - unified: ^10.0.0 - checksum: 5041b4b44725f377e69986e02f8f072ae2222db5e7d3b6c80829756b842e811343ffc2069cae1f958a96bfa36104ab91a57d7d7e2f0cef521e210ab8c614d5c7 + "@types/hast": ^3.0.0 + "@types/mdast": ^4.0.0 + mdast-util-to-hast: ^13.0.0 + unified: ^11.0.0 + vfile: ^6.0.0 + checksum: f0c731f0ab92a122e7f9c9bcbd10d6a31fdb99f0ea3595d232ddd9f9d11a308c4ec0aff4d56e1d0d256042dfad7df23b9941e50b5038da29786959a5926814e1 languageName: node linkType: hard -"remark-rehype@npm:^10.0.0": - version: 10.1.0 - resolution: "remark-rehype@npm:10.1.0" +"remark-stringify@npm:^11.0.0": + version: 11.0.0 + resolution: "remark-stringify@npm:11.0.0" dependencies: - "@types/hast": ^2.0.0 - "@types/mdast": ^3.0.0 - mdast-util-to-hast: ^12.1.0 - unified: ^10.0.0 - checksum: b9ac8acff3383b204dfdc2599d0bdf86e6ca7e837033209584af2e6aaa6a9013e519a379afa3201299798cab7298c8f4b388de118c312c67234c133318aec084 + "@types/mdast": ^4.0.0 + mdast-util-to-markdown: ^2.0.0 + unified: ^11.0.0 + checksum: 59e07460eb629d6c3b3c0f438b0b236e7e6858fd5ab770303078f5a556ec00354d9c7fb9ef6d5f745a4617ac7da1ab618b170fbb4dac120e183fecd9cc86bce6 languageName: node linkType: hard @@ -21669,15 +21740,6 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"sade@npm:^1.7.3": - version: 1.8.1 - resolution: "sade@npm:1.8.1" - dependencies: - mri: ^1.1.0 - checksum: 0756e5b04c51ccdc8221ebffd1548d0ce5a783a44a0fa9017a026659b97d632913e78f7dca59f2496aa996a0be0b0c322afd87ca72ccd909406f49dbffa0f45d - languageName: node - linkType: hard - "safe-array-concat@npm:^1.0.0": version: 1.1.0 resolution: "safe-array-concat@npm:1.1.0" @@ -22885,12 +22947,21 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"style-to-object@npm:^0.4.1": - version: 0.4.2 - resolution: "style-to-object@npm:0.4.2" +"style-to-object@npm:^0.4.0": + version: 0.4.4 + resolution: "style-to-object@npm:0.4.4" dependencies: inline-style-parser: 0.1.1 - checksum: 314a80bcfadde41c2b9c8d717a4b1f2220954561040c2c7740496715da5cb95f99920a8eeefe2d4a862149875f352a12eda9bbef5816d7e0a71910da00d1521f + checksum: 41656c06f93ac0a7ac260ebc2f9d09a8bd74b8ec1836f358cc58e169235835a3a356977891d2ebbd76f0e08a53616929069199f9cce543214d3dc98346e19c9a + languageName: node + linkType: hard + +"style-to-object@npm:^1.0.0": + version: 1.0.6 + resolution: "style-to-object@npm:1.0.6" + dependencies: + inline-style-parser: 0.2.3 + checksum: 5b58295dcc2c21f1da1b9308de1e81b4a987b876a177e677453a76b2e3151a0e21afc630e99c1ea6c82dd8dbec0d01a8b1a51a829422aca055162b03e52572a9 languageName: node linkType: hard @@ -24130,18 +24201,18 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"unified@npm:^10.0.0": - version: 10.1.2 - resolution: "unified@npm:10.1.2" +"unified@npm:^11.0.0": + version: 11.0.5 + resolution: "unified@npm:11.0.5" dependencies: - "@types/unist": ^2.0.0 + "@types/unist": ^3.0.0 bail: ^2.0.0 + devlop: ^1.0.0 extend: ^3.0.0 - is-buffer: ^2.0.0 is-plain-obj: ^4.0.0 trough: ^2.0.0 - vfile: ^5.0.0 - checksum: 053e7c65ede644607f87bd625a299e4b709869d2f76ec8138569e6e886903b6988b21cd9699e471eda42bee189527be0a9dac05936f1d069a5e65d0125d5d756 + vfile: ^6.0.0 + checksum: b3bf7fd6f568cc261e074dae21188483b0f2a8ab858d62e6e85b75b96cc655f59532906ae3c64d56a9b257408722d71f1d4135292b3d7ee02907c8b592fb3cf0 languageName: node linkType: hard @@ -24172,10 +24243,13 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"unist-util-generated@npm:^2.0.0": - version: 2.0.1 - resolution: "unist-util-generated@npm:2.0.1" - checksum: 6221ad0571dcc9c8964d6b054f39ef6571ed59cc0ce3e88ae97ea1c70afe76b46412a5ffaa91f96814644ac8477e23fb1b477d71f8d70e625728c5258f5c0d99 +"unist-util-find-after@npm:^5.0.0": + version: 5.0.0 + resolution: "unist-util-find-after@npm:5.0.0" + dependencies: + "@types/unist": ^3.0.0 + unist-util-is: ^6.0.0 + checksum: e64bd5ebee7ac021cf990bf33e9ec29fc6452159187d4a7fa0f77334bea8e378fea7a7fb0bcf957300b2ffdba902ff25b62c165fc8b86309613da35ad793ada0 languageName: node linkType: hard @@ -24197,15 +24271,6 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"unist-util-position-from-estree@npm:^1.0.0, unist-util-position-from-estree@npm:^1.1.0": - version: 1.1.2 - resolution: "unist-util-position-from-estree@npm:1.1.2" - dependencies: - "@types/unist": ^2.0.0 - checksum: e3f4060e2a9e894c6ed63489c5a7cb58ff282e5dae9497cbc2073033ca74d6e412af4d4d342c97aea08d997c908b8bce2fe43a2062aafc2bb3f266533016588b - languageName: node - linkType: hard - "unist-util-position-from-estree@npm:^2.0.0": version: 2.0.0 resolution: "unist-util-position-from-estree@npm:2.0.0" @@ -24215,22 +24280,12 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"unist-util-position@npm:^4.0.0": - version: 4.0.4 - resolution: "unist-util-position@npm:4.0.4" - dependencies: - "@types/unist": ^2.0.0 - checksum: e7487b6cec9365299695e3379ded270a1717074fa11fd2407c9b934fb08db6fe1d9077ddeaf877ecf1813665f8ccded5171693d3d9a7a01a125ec5cdd5e88691 - languageName: node - linkType: hard - -"unist-util-remove-position@npm:^4.0.0": - version: 4.0.2 - resolution: "unist-util-remove-position@npm:4.0.2" +"unist-util-position@npm:^5.0.0": + version: 5.0.0 + resolution: "unist-util-position@npm:5.0.0" dependencies: - "@types/unist": ^2.0.0 - unist-util-visit: ^4.0.0 - checksum: 989831da913d09a82a99ed9b47b78471b6409bde95942cde47e09da54b7736516f17e3c7e026af468684c1efcec5fb52df363381b2f9dc7fd96ce791c5a2fa4a + "@types/unist": ^3.0.0 + checksum: f89b27989b19f07878de9579cd8db2aa0194c8360db69e2c99bd2124a480d79c08f04b73a64daf01a8fb3af7cba65ff4b45a0b978ca243226084ad5f5d441dde languageName: node linkType: hard @@ -24244,12 +24299,14 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"unist-util-stringify-position@npm:^3.0.0": - version: 3.0.3 - resolution: "unist-util-stringify-position@npm:3.0.3" +"unist-util-remove@npm:^3.1.0": + version: 3.1.1 + resolution: "unist-util-remove@npm:3.1.1" dependencies: "@types/unist": ^2.0.0 - checksum: dbd66c15183607ca942a2b1b7a9f6a5996f91c0d30cf8966fb88955a02349d9eefd3974e9010ee67e71175d784c5a9fea915b0aa0b0df99dcb921b95c4c9e124 + unist-util-is: ^5.0.0 + unist-util-visit-parents: ^5.0.0 + checksum: ed7c762941e6a9b6db230e9417697c8eb7d36093240b6f6d4ec265c4237d33e332a96a18307c8fb322a1842e3feb2a7564b032b5535fa0634eb1e075a6e344cb languageName: node linkType: hard @@ -24262,7 +24319,7 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"unist-util-visit-parents@npm:^5.0.0, unist-util-visit-parents@npm:^5.1.1": +"unist-util-visit-parents@npm:^5.0.0": version: 5.1.3 resolution: "unist-util-visit-parents@npm:5.1.3" dependencies: @@ -24282,17 +24339,6 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"unist-util-visit@npm:^4.0.0": - version: 4.1.2 - resolution: "unist-util-visit@npm:4.1.2" - dependencies: - "@types/unist": ^2.0.0 - unist-util-is: ^5.0.0 - unist-util-visit-parents: ^5.1.1 - checksum: 95a34e3f7b5b2d4b68fd722b6229972099eb97b6df18913eda44a5c11df8b1e27efe7206dd7b88c4ed244a48c474a5b2e2629ab79558ff9eb936840295549cee - languageName: node - linkType: hard - "unist-util-visit@npm:^5.0.0": version: 5.0.0 resolution: "unist-util-visit@npm:5.0.0" @@ -24374,6 +24420,20 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard +"update-browserslist-db@npm:^1.1.0": + version: 1.1.0 + resolution: "update-browserslist-db@npm:1.1.0" + dependencies: + escalade: ^3.1.2 + picocolors: ^1.0.1 + peerDependencies: + browserslist: ">= 4.21.0" + bin: + update-browserslist-db: cli.js + checksum: 7b74694d96f0c360f01b702e72353dc5a49df4fe6663d3ee4e5c628f061576cddf56af35a3a886238c01dd3d8f231b7a86a8ceaa31e7a9220ae31c1c1238e562 + languageName: node + linkType: hard + "update-notifier@npm:^2.3.0, update-notifier@npm:^2.5.0": version: 2.5.0 resolution: "update-notifier@npm:2.5.0" @@ -24482,20 +24542,6 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"uvu@npm:^0.5.0": - version: 0.5.6 - resolution: "uvu@npm:0.5.6" - dependencies: - dequal: ^2.0.0 - diff: ^5.0.0 - kleur: ^4.0.3 - sade: ^1.7.3 - bin: - uvu: bin.js - checksum: 09460a37975627de9fcad396e5078fb844d01aaf64a6399ebfcfd9e55f1c2037539b47611e8631f89be07656962af0cf48c334993db82b9ae9c3d25ce3862168 - languageName: node - linkType: hard - "v8-compile-cache-lib@npm:^3.0.1": version: 3.0.1 resolution: "v8-compile-cache-lib@npm:3.0.1" @@ -24565,24 +24611,23 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"vfile-matter@npm:^3.0.1": - version: 3.0.1 - resolution: "vfile-matter@npm:3.0.1" +"vfile-location@npm:^5.0.0": + version: 5.0.3 + resolution: "vfile-location@npm:5.0.3" dependencies: - "@types/js-yaml": ^4.0.0 - is-buffer: ^2.0.0 - js-yaml: ^4.0.0 - checksum: ced55ed7d79291b6c9321557d685b3c0072321f3de44790b72005f1e232394dd9ae68311b99286e327ec4f1d168d5bada986eaa1d475757e17b7e24150f503ac + "@types/unist": ^3.0.0 + vfile: ^6.0.0 + checksum: bfb3821b6981b6e9aa369bed67a40090b800562064ea312e84437762562df3225a0ca922695389cc0ef1e115f19476c363f53e3ed44dec17c50678b7670b5f2b languageName: node linkType: hard -"vfile-message@npm:^3.0.0": - version: 3.1.4 - resolution: "vfile-message@npm:3.1.4" +"vfile-matter@npm:^5.0.0": + version: 5.0.0 + resolution: "vfile-matter@npm:5.0.0" dependencies: - "@types/unist": ^2.0.0 - unist-util-stringify-position: ^3.0.0 - checksum: d0ee7da1973ad76513c274e7912adbed4d08d180eaa34e6bd40bc82459f4b7bc50fcaff41556135e3339995575eac5f6f709aba9332b80f775618ea4880a1367 + vfile: ^6.0.0 + yaml: ^2.0.0 + checksum: 713327640ebc9579f82e8fb1586613bad4d43d969e29feceaf5c4231402d320abef67d2f36369e68c52329cc1d7971a3aba2e6f992103af7ef0d1159f301a4c0 languageName: node linkType: hard @@ -24596,15 +24641,14 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard -"vfile@npm:^5.0.0, vfile@npm:^5.3.0": - version: 5.3.7 - resolution: "vfile@npm:5.3.7" +"vfile@npm:^6.0.0, vfile@npm:^6.0.1": + version: 6.0.2 + resolution: "vfile@npm:6.0.2" dependencies: - "@types/unist": ^2.0.0 - is-buffer: ^2.0.0 - unist-util-stringify-position: ^3.0.0 - vfile-message: ^3.0.0 - checksum: 642cce703afc186dbe7cabf698dc954c70146e853491086f5da39e1ce850676fc96b169fcf7898aa3ff245e9313aeec40da93acd1e1fcc0c146dc4f6308b4ef9 + "@types/unist": ^3.0.0 + unist-util-stringify-position: ^4.0.0 + vfile-message: ^4.0.0 + checksum: 2f3f405654aa549f1902dfe0cefa5f0d785f9f65cb90989b9ab543166afabf30f9c5c4bda734d78cf08e169dd7cba08af4cdcae5563f89782caf1d4719c57646 languageName: node linkType: hard @@ -24796,6 +24840,13 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard +"web-namespaces@npm:^2.0.0": + version: 2.0.1 + resolution: "web-namespaces@npm:2.0.1" + checksum: b6d9f02f1a43d0ef0848a812d89c83801d5bbad57d8bb61f02eb6d7eb794c3736f6cc2e1191664bb26136594c8218ac609f4069722c6f56d9fc2d808fa9271c6 + languageName: node + linkType: hard + "webidl-conversions@npm:^3.0.0": version: 3.0.1 resolution: "webidl-conversions@npm:3.0.1" @@ -24860,6 +24911,17 @@ env-cmd@toddbluhm/env-cmd: languageName: node linkType: hard +"whatwg-url@npm:^6.5.0": + version: 6.5.0 + resolution: "whatwg-url@npm:6.5.0" + dependencies: + lodash.sortby: ^4.7.0 + tr46: ^1.0.1 + webidl-conversions: ^4.0.2 + checksum: a10bd5e29f4382cd19789c2a7bbce25416e606b6fefc241c7fe34a2449de5bc5709c165bd13634eda433942d917ca7386a52841780b82dc37afa8141c31a8ebd + languageName: node + linkType: hard + "whatwg-url@npm:^7.0.0": version: 7.1.0 resolution: "whatwg-url@npm:7.1.0"