diff --git a/.cspell.json b/.cspell.json index 757f0c48..8b9af8e4 100644 --- a/.cspell.json +++ b/.cspell.json @@ -6,6 +6,7 @@ "**/*.{java,md,xml,yml,yaml,json,txt,properties}" ], "words": [ + "japicmp", "Skyflow", "skyflow", "skyflowapi", @@ -96,9 +97,18 @@ "nocreds", "nodir", "detok", - "qhdmceurtnlz", "ngrok", - "obac" + "obac", + "siom", + "vaultid", + "recordss", + "synthesise", + "synthesised", + "deserialise", + "deserialised", + "unmodelled", + "recordss", + "rarr" ], "languageSettings": [ { @@ -118,7 +128,8 @@ "**/target/**", "*.lock", "Rule/**", - "src/main/java/com/skyflow/generated/**", + "**/src/main/java/com/skyflow/generated/**", + "**/generated/**", "**/*.ts", "**/processed-*", "samples/src/main/java/com/example/credentials.json", diff --git a/.github/workflows/beta-release.yml b/.github/workflows/beta-release.yml index 13fcf623..c6472052 100644 --- a/.github/workflows/beta-release.yml +++ b/.github/workflows/beta-release.yml @@ -15,6 +15,8 @@ jobs: server-password: ${{ secrets.CENTRAL_PUBLISHER_PORTAL_PASSWORD }} gpg-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} gpg-passphrase: ${{ secrets.MAVEN_GPG_PASSPHRASE }} - skyflow-credentials: ${{ secrets.SKYFLOW_CREDENTIALS }} >> .env - test-expired-token: ${{ secrets.TEST_EXPIRED_TOKEN }} >> .env - test-reusable-token: ${{ secrets.TEST_REUSABLE_TOKEN }} >> .env + skyflow-credentials: ${{ secrets.SKYFLOW_CREDENTIALS }} + test-expired-token: ${{ secrets.TEST_EXPIRED_TOKEN }} + test-reusable-token: ${{ secrets.TEST_REUSABLE_TOKEN }} + PAT_ACTIONS: ${{ secrets.PAT_ACTIONS }} + test-credentials-file-string: ${{ secrets.TEST_CREDENTIALS_FILE_STRING }} diff --git a/.github/workflows/contract-tests.yml b/.github/workflows/contract-tests.yml new file mode 100644 index 00000000..bdc19096 --- /dev/null +++ b/.github/workflows/contract-tests.yml @@ -0,0 +1,118 @@ +name: Contract Tests + +on: + pull_request: + branches: + - main + - release/* + +jobs: + contract-tests: + name: Contract Tests + runs-on: ubuntu-latest + + permissions: + contents: read + pull-requests: write + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '11' + cache: 'maven' + + - name: Verify API surface snapshot + run: mvn -B install -pl common,skyvault -am -DskipTests -Dmaven.javadoc.skip=true -Dgpg.skip=true + + - name: Show API surface diff + if: failure() + run: | + echo "### API surface changes detected ###" + echo "See skyvault/target/japicmp/default-cli.diff for the full comparison against skyvault/api-report/skyflow-java.baseline.jar." + echo "If this change is intentional, run scripts/contract-snapshot-update.sh and commit the updated baseline jar." + echo "" + cat skyvault/target/japicmp/default-cli.diff || true + + - name: Upload API surface diff on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: api-surface-diff + path: skyvault/target/japicmp/** + retention-days: 7 + + # The step above only shows a diff when the CURRENT build differs from the + # committed baseline - once someone runs contract-snapshot-update.sh and + # commits the refreshed baseline jar, that check goes green and shows nothing. + # A reviewer looking at a green PR that touches api-report/skyflow-java.baseline.jar + # (a binary file) would otherwise have no way to see WHAT was just approved as + # the new contract. These steps explicitly diff the OLD committed baseline + # (from the PR's base branch) against the NEW committed baseline (from this PR) + # and post it as a PR comment, regardless of whether the check above passed. + - name: Check if contract baseline was updated in this PR + id: baseline-diff-check + if: always() && github.event.pull_request + run: | + git fetch origin "${{ github.event.pull_request.base.ref }}" --depth=1 + if git diff --name-only "origin/${{ github.event.pull_request.base.ref }}" HEAD -- skyvault/api-report/skyflow-java.baseline.jar | grep -q .; then + echo "changed=true" >> "$GITHUB_OUTPUT" + else + echo "changed=false" >> "$GITHUB_OUTPUT" + fi + + - name: Diff old vs new contract baseline + if: always() && steps.baseline-diff-check.outputs.changed == 'true' + run: | + curl -sL -o /tmp/japicmp-cli.jar "https://repo.maven.apache.org/maven2/com/github/siom79/japicmp/japicmp/0.26.0/japicmp-0.26.0-jar-with-dependencies.jar" + mvn -q -B dependency:build-classpath -pl skyvault -Dmdep.outputFile=/tmp/skyvault-classpath.txt -Dmaven.javadoc.skip=true -Dgpg.skip=true + git show "origin/${{ github.event.pull_request.base.ref }}:skyvault/api-report/skyflow-java.baseline.jar" > /tmp/old-baseline.jar + + java -jar /tmp/japicmp-cli.jar \ + -o /tmp/old-baseline.jar \ + -n skyvault/api-report/skyflow-java.baseline.jar \ + -a protected \ + -e "com.skyflow.generated.*;com.skyflow.utils.*" \ + --old-classpath "$(cat /tmp/skyvault-classpath.txt)" \ + --new-classpath "$(cat /tmp/skyvault-classpath.txt)" \ + -m \ + --markdown > /tmp/contract-baseline-diff.md || true + + cat /tmp/contract-baseline-diff.md + + - name: Comment contract baseline change on PR + if: always() && steps.baseline-diff-check.outputs.changed == 'true' + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const summary = fs.readFileSync('/tmp/contract-baseline-diff.md', 'utf8'); + const marker = ''; + const body = `${marker}\n## Contract baseline change detected\n\nThis PR updates \`skyvault/api-report/skyflow-java.baseline.jar\` (the approved public API contract). Here is exactly what it changes, comparing the baseline on \`${{ github.event.pull_request.base.ref }}\` against the baseline committed in this PR:\n\n${summary}`; + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + }); + const existing = comments.find(c => c.body && c.body.includes(marker)); + if (existing) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existing.id, + body, + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body, + }); + } diff --git a/.github/workflows/endorlabsScan.yml b/.github/workflows/endorlabsScan.yml index 3c316417..762b742e 100644 --- a/.github/workflows/endorlabsScan.yml +++ b/.github/workflows/endorlabsScan.yml @@ -36,6 +36,15 @@ jobs: name: "credentials.json" json: ${{ secrets.TEST_CREDENTIALS_FILE_STRING }} + - name: Distribute test fixtures to modules + run: | + # Surefire runs each module's tests with the module directory as the working + # directory, so dotenv and ./credentials.json lookups miss the repo-root copies. + for module in common skyvault flowvault; do + cp .env "$module/.env" + cp credentials.json "$module/credentials.json" + done + - name: Compile Package run: mvn -B package -f pom.xml -Dmaven.javadoc.skip=true diff --git a/.github/workflows/internal-release.yml b/.github/workflows/internal-release.yml index e8b8ee61..7e4852e3 100644 --- a/.github/workflows/internal-release.yml +++ b/.github/workflows/internal-release.yml @@ -1,4 +1,4 @@ -name: Publish package to the JFROG Artifactory +name: Publish module to the JFROG Artifactory on: push: tags-ignore: @@ -7,20 +7,39 @@ on: - "*.md" branches: - release/* + - flowvault-release/* jobs: + resolve-module: + runs-on: ubuntu-latest + outputs: + module: ${{ steps.set-module.outputs.module }} + steps: + - name: Resolve module from branch name + id: set-module + run: | + if [[ "${{ github.ref_name }}" == flowvault-release/* ]]; then + echo "module=flowvault" >> "$GITHUB_OUTPUT" + else + echo "module=skyvault" >> "$GITHUB_OUTPUT" + fi + build-and-deploy: + needs: resolve-module uses: ./.github/workflows/shared-build-and-deploy.yml with: ref: ${{ github.ref_name }} server-id: central profile: jfrog tag: 'internal' + module: ${{ needs.resolve-module.outputs.module }} secrets: server-username: ${{ secrets.ARTIFACTORY_USERNAME }} server-password: ${{ secrets.ARTIFACTORY_PASSWORD }} gpg-key: ${{ secrets.JFROG_GPG_KEY }} gpg-passphrase: ${{ secrets.JFROG_GPG_PASSPHRASE }} - skyflow-credentials: ${{ secrets.SKYFLOW_CREDENTIALS }} >> .env - test-expired-token: ${{ secrets.TEST_EXPIRED_TOKEN }} >> .env - test-reusable-token: ${{ secrets.TEST_REUSABLE_TOKEN }} >> .env + skyflow-credentials: ${{ secrets.SKYFLOW_CREDENTIALS }} + test-expired-token: ${{ secrets.TEST_EXPIRED_TOKEN }} + test-reusable-token: ${{ secrets.TEST_REUSABLE_TOKEN }} + PAT_ACTIONS: ${{ secrets.PAT_ACTIONS }} + test-credentials-file-string: ${{ secrets.TEST_CREDENTIALS_FILE_STRING }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4386de84..5489eeea 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -30,13 +30,65 @@ jobs: echo TEST_EXPIRED_TOKEN=${{ secrets.TEST_EXPIRED_TOKEN }} >> .env echo TEST_REUSABLE_TOKEN=${{ secrets.TEST_REUSABLE_TOKEN }} >> .env + - name: Distribute test fixtures to modules + run: | + # Surefire runs each module's tests with the module directory as the working + # directory, so dotenv and ./credentials.json lookups miss the repo-root copies. + for module in common skyvault flowvault; do + cp .env "$module/.env" + cp credentials.json "$module/credentials.json" + done + - name: Build & Run tests with Maven run: mvn -B package -f pom.xml -Dmaven.javadoc.skip=true - - name: Codecov - uses: codecov/codecov-action@v2.1.0 + # JaCoCo records packages as "com/skyflow/..." with no module prefix, and all three + # modules share the com.skyflow package (deliberate split-package convention). So + # "com/skyflow/config/VaultConfig.java" matches BOTH skyvault and flowvault, Codecov + # resolves it to one of them, and the other module's file silently reports no data. + # Prefixing each report with its own source root makes every path unique and match the + # repo exactly, so all three modules' files are attributed correctly. + - name: Qualify JaCoCo report paths with their module + run: | + for module in common skyvault flowvault; do + report="$module/target/site/jacoco/jacoco.xml" + if [ ! -f "$report" ]; then + echo "Error: expected $report to exist after the build." + exit 1 + fi + tmp="$(mktemp)" + sed "s| "$tmp" + mv "$tmp" "$report" + done + + # One upload per module, each with its own flag, so Codecov reports per-module + # coverage instead of merging three same-named package trees into one number. + - name: Codecov (common) + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_REPO_UPLOAD_TOKEN }} + files: common/target/site/jacoco/jacoco.xml + flags: common + name: codecov-common + verbose: true + fail_ci_if_error: true + + - name: Codecov (skyvault) + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_REPO_UPLOAD_TOKEN }} + files: skyvault/target/site/jacoco/jacoco.xml + flags: skyvault + name: codecov-skyvault + verbose: true + fail_ci_if_error: true + + - name: Codecov (flowvault) + uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_REPO_UPLOAD_TOKEN }} - files: target/site/jacoco/jacoco.xml - name: codecov-skyflow-java + files: flowvault/target/site/jacoco/jacoco.xml + flags: flowvault + name: codecov-flowvault verbose: true + fail_ci_if_error: true diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index bb89e32f..822b1288 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -39,16 +39,68 @@ jobs: echo TEST_EXPIRED_TOKEN=${{ secrets.TEST_EXPIRED_TOKEN }} >> .env echo TEST_REUSABLE_TOKEN=${{ secrets.TEST_REUSABLE_TOKEN }} >> .env + - name: Distribute test fixtures to modules + run: | + # Surefire runs each module's tests with the module directory as the working + # directory, so dotenv and ./credentials.json lookups miss the repo-root copies. + for module in common skyvault flowvault; do + cp .env "$module/.env" + cp credentials.json "$module/credentials.json" + done + - name: Build & Run tests with Maven run: mvn -B package -f pom.xml -Dmaven.javadoc.skip=true - - name: Codecov - uses: codecov/codecov-action@v2.1.0 + # JaCoCo records packages as "com/skyflow/..." with no module prefix, and all three + # modules share the com.skyflow package (deliberate split-package convention). So + # "com/skyflow/config/VaultConfig.java" matches BOTH skyvault and flowvault, Codecov + # resolves it to one of them, and the other module's file silently reports no data. + # Prefixing each report with its own source root makes every path unique and match the + # repo exactly, so all three modules' files are attributed correctly. + - name: Qualify JaCoCo report paths with their module + run: | + for module in common skyvault flowvault; do + report="$module/target/site/jacoco/jacoco.xml" + if [ ! -f "$report" ]; then + echo "Error: expected $report to exist after the build." + exit 1 + fi + tmp="$(mktemp)" + sed "s| "$tmp" + mv "$tmp" "$report" + done + + # One upload per module, each with its own flag, so Codecov reports per-module + # coverage instead of merging three same-named package trees into one number. + - name: Codecov (common) + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_REPO_UPLOAD_TOKEN }} + files: common/target/site/jacoco/jacoco.xml + flags: common + name: codecov-common + verbose: true + fail_ci_if_error: true + + - name: Codecov (skyvault) + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_REPO_UPLOAD_TOKEN }} + files: skyvault/target/site/jacoco/jacoco.xml + flags: skyvault + name: codecov-skyvault + verbose: true + fail_ci_if_error: true + + - name: Codecov (flowvault) + uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_REPO_UPLOAD_TOKEN }} - files: target/site/jacoco/jacoco.xml - name: codecov-skyflow-java + files: flowvault/target/site/jacoco/jacoco.xml + flags: flowvault + name: codecov-flowvault verbose: true + fail_ci_if_error: true spellcheck: name: Run spellcheck diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 720e16d8..9dc3f338 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,6 +15,8 @@ jobs: server-password: ${{ secrets.CENTRAL_PUBLISHER_PORTAL_PASSWORD }} gpg-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} gpg-passphrase: ${{ secrets.MAVEN_GPG_PASSPHRASE }} - skyflow-credentials: ${{ secrets.SKYFLOW_CREDENTIALS }} >> .env - test-expired-token: ${{ secrets.TEST_EXPIRED_TOKEN }} >> .env - test-reusable-token: ${{ secrets.TEST_REUSABLE_TOKEN }} >> .env + skyflow-credentials: ${{ secrets.SKYFLOW_CREDENTIALS }} + test-expired-token: ${{ secrets.TEST_EXPIRED_TOKEN }} + test-reusable-token: ${{ secrets.TEST_REUSABLE_TOKEN }} + PAT_ACTIONS: ${{ secrets.PAT_ACTIONS }} + test-credentials-file-string: ${{ secrets.TEST_CREDENTIALS_FILE_STRING }} diff --git a/.github/workflows/shared-build-and-deploy.yml b/.github/workflows/shared-build-and-deploy.yml index 89e6e8b4..e742485d 100644 --- a/.github/workflows/shared-build-and-deploy.yml +++ b/.github/workflows/shared-build-and-deploy.yml @@ -21,6 +21,13 @@ on: description: 'Release Tag' required: true type: string + + module: + description: 'Module to build and publish' + required: false + type: string + default: '' + secrets: server-username: required: true @@ -43,11 +50,17 @@ on: test-reusable-token: required: true + PAT_ACTIONS: + required: true + + test-credentials-file-string: + required: true + jobs: publish: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: 0 # PAT of the skyflow-service-it admin service account. Persisted by @@ -66,10 +79,10 @@ jobs: server-password: SERVER_PASSWORD gpg-private-key: ${{ secrets.gpg-key }} # Value of the GPG private key to import gpg-passphrase: GPG_PASSPHRASE # env variable for GPG private key passphrase - + - name: Resolve Branch for the Tagged Commit id: resolve-branch - if: ${{ inputs.tag == 'beta' || inputs.tag == 'public' }} + if: ${{ inputs.tag == 'beta' || inputs.tag == 'public' }} run: | TAG_COMMIT=$(git rev-list -n 1 ${{ github.ref_name }}) BRANCH_NAME=$(git for-each-ref --points-at="$TAG_COMMIT" --format='%(refname:short)' refs/remotes/origin | grep -v '/HEAD$' | sed 's#^origin/##' | head -n 1) @@ -90,9 +103,9 @@ jobs: run: | chmod +x ./scripts/bump_version.sh if ${{ inputs.tag == 'internal' }}; then - ./scripts/bump_version.sh "${{ steps.previoustag.outputs.tag }}" "$(git rev-parse --short "$GITHUB_SHA")" + ./scripts/bump_version.sh "${{ steps.previoustag.outputs.tag }}" "$(git rev-parse --short "$GITHUB_SHA")" "${{ inputs.module }}" else - ./scripts/bump_version.sh "${{ steps.previoustag.outputs.tag }}" + ./scripts/bump_version.sh "${{ steps.previoustag.outputs.tag }}" "" "${{ inputs.module }}" fi - name: Commit changes @@ -104,12 +117,9 @@ jobs: git checkout ${{ env.branch_name }} fi - git add pom.xml + git add ${{ inputs.module }}/pom.xml if [[ "${{ inputs.tag }}" == "internal" ]]; then - # [skip ci]: the version-bump push uses a PAT (see checkout above), and PAT-authored - # pushes DO trigger workflows (unlike GITHUB_TOKEN). Without this marker the push - # re-triggers this same internal-release workflow -> bump -> push -> infinite loop. - git commit -m "[AUTOMATED] Private Release ${{ steps.previoustag.outputs.tag }}-dev-$(git rev-parse --short $GITHUB_SHA) [skip ci]" + git commit -m "[AUTOMATED] Private Release ${{ steps.previoustag.outputs.tag }}-dev-$(git rev-parse --short $GITHUB_SHA)" git push origin ${{ github.ref_name }} -f fi if [[ "${{ inputs.tag }}" == "beta" || "${{ inputs.tag }}" == "public" ]]; then @@ -130,12 +140,30 @@ jobs: uses: jsdaniell/create-json@1.1.2 with: name: "credentials.json" - json: ${{ secrets.TEST_CREDENTIALS_FILE_STRING }} + json: ${{ secrets.test-credentials-file-string }} + + - name: Distribute test fixtures to modules + run: | + # Surefire runs each module's tests with the module directory as the working + # directory, so dotenv and ./credentials.json lookups miss the repo-root copies. + for module in common skyvault flowvault; do + cp .env "$module/.env" + cp credentials.json "$module/credentials.json" + done - name: Publish package - run: mvn --batch-mode deploy -P ${{ inputs.profile }} + run: | + if [[ "${{ inputs.tag }}" == "internal" ]]; then + # Javadoc is skipped for internal (JFrog) builds only: these are dev snapshots that + # nobody browses docs for, and strict doclint on the pinned JDK 11 fails the release + # on HTML nits in comments. Beta/public below must keep it — Sonatype requires the + # -javadoc.jar, so any doclint error there is a real break to fix at the source. + mvn --batch-mode -pl ${{ inputs.module }} -am deploy -P jfrog -DskipTests -Dmaven.javadoc.skip=true + elif [[ "${{ inputs.tag }}" == "beta" || "${{ inputs.tag }}" == "public" ]]; then + mvn --batch-mode -pl ${{ inputs.module }} -am deploy -P ${{ inputs.profile }} -Dmaven.javadoc.skip=true + fi env: SERVER_USERNAME: ${{ secrets.server-username }} SERVER_PASSWORD: ${{ secrets.server-password }} - GPG_PASSPHRASE: ${{ secrets.gpg-passphrase }} + GPG_PASSPHRASE: ${{ secrets.gpg-passphrase }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 0efc6bd4..4f10424a 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,6 @@ target RUNNING_SAMPLES.md docs/superpowers/ + +# local sample configuration - may hold credentials +.env diff --git a/codecov.yml b/codecov.yml index 05c58c3e..0191c11b 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,52 +1,146 @@ -comment: false +# Post a summary on every PR, broken down by flag (module) and component so all three of +# common / skyvault / flowvault are visible at a glance. require_changes: false keeps the +# comment on PRs that touch only one module, so the other two still report their coverage. +comment: + layout: "header, diff, flags, components, files, footer" + behavior: default + require_changes: false +# A project + patch status per module, so each of the three gets its own PR check rather +# than being averaged into one repo-wide number. informational keeps them advisory - they +# report the delta without blocking the merge. +coverage: + status: + project: + default: + target: auto + threshold: 1% + common: + flags: + - common + target: auto + threshold: 1% + informational: true + skyvault: + flags: + - skyvault + target: auto + threshold: 1% + informational: true + flowvault: + flags: + - flowvault + target: auto + threshold: 1% + informational: true + patch: + default: + target: auto + threshold: 1% + informational: true + +# One flag per Maven module. Each module is uploaded separately in CI so its coverage is +# reported on its own rather than merged into a single repo-wide number. carryforward keeps +# the last known coverage for a module when a run does not upload it (e.g. a partial build). +flags: + common: + paths: + - common/src/main/java/ + carryforward: true + skyvault: + paths: + - skyvault/src/main/java/ + carryforward: true + flowvault: + paths: + - flowvault/src/main/java/ + carryforward: true + +# Components give two independent breakdowns of the same coverage data: +# - one per module, so a drop can be traced to common / skyvault / flowvault +# - one per package, so a drop can be traced to controllers / data / utils / ... +# +# Package paths are prefixed with **/ (quoted - a bare leading * is a YAML alias) so they +# match every module. All three modules share the com.skyflow package name, so the JaCoCo +# reports are rewritten in CI to carry their module's source root; without that, a path like +# com/skyflow/config/VaultConfig.java is ambiguous and Codecov attributes it to a single +# module while the others report no data. See the "Qualify JaCoCo report paths" CI step. component_management: default_rules: statuses: - type: project target: auto individual_components: + # -- per module ------------------------------------------------------------ + - component_id: module_common + name: "Module: common" + paths: + - "common/src/main/java/**" + - component_id: module_skyvault + name: "Module: skyvault" + paths: + - "skyvault/src/main/java/**" + - component_id: module_flowvault + name: "Module: flowvault" + paths: + - "flowvault/src/main/java/**" + # -- per package, across all modules --------------------------------------- - component_id: service_account name: Service Account paths: - - src/main/java/com/skyflow/serviceaccount/** + - "**/src/main/java/com/skyflow/serviceaccount/**" - component_id: vault_data name: Vault Data paths: - - src/main/java/com/skyflow/vault/data/** + - "**/src/main/java/com/skyflow/vault/data/**" - component_id: vault_tokens name: Vault Tokens paths: - - src/main/java/com/skyflow/vault/tokens/** + - "**/src/main/java/com/skyflow/vault/tokens/**" - component_id: vault_connection name: Vault Connection paths: - - src/main/java/com/skyflow/vault/connection/** + - "**/src/main/java/com/skyflow/vault/connection/**" - component_id: vault_controller name: Vault Controller paths: - - src/main/java/com/skyflow/vault/controller/** + - "**/src/main/java/com/skyflow/vault/controller/**" - component_id: vault_detect name: Detect paths: - - src/main/java/com/skyflow/vault/detect/** + - "**/src/main/java/com/skyflow/vault/detect/**" - component_id: vault_audit name: Audit paths: - - src/main/java/com/skyflow/vault/audit/** + - "**/src/main/java/com/skyflow/vault/audit/**" - component_id: vault_bin name: BIN Lookup paths: - - src/main/java/com/skyflow/vault/bin/** + - "**/src/main/java/com/skyflow/vault/bin/**" - component_id: config name: Config paths: - - src/main/java/com/skyflow/config/** + - "**/src/main/java/com/skyflow/config/**" - component_id: utils name: Utils paths: - - src/main/java/com/skyflow/utils/** + - "**/src/main/java/com/skyflow/utils/**" - component_id: errors name: Errors paths: - - src/main/java/com/skyflow/errors/** + - "**/src/main/java/com/skyflow/errors/**" + - component_id: enums + name: Enums + paths: + - "**/src/main/java/com/skyflow/enums/**" + - component_id: logs + name: Logs + paths: + - "**/src/main/java/com/skyflow/logs/**" + +# Generated REST/auth clients and sample code are not hand-written and are already excluded +# from the JaCoCo reports by the root pom; ignore them here too so they never skew a target. +ignore: + - "**/src/main/java/com/skyflow/generated/**" + - "**/samples/**" + - "**/src/test/**" diff --git a/common/pom.xml b/common/pom.xml new file mode 100644 index 00000000..72fff275 --- /dev/null +++ b/common/pom.xml @@ -0,0 +1,24 @@ + + + 4.0.0 + + com.skyflow + skyflow + 1.0.0 + ../pom.xml + + + common + 1.0.0 + ${project.groupId}:${project.artifactId} + + + + true + 8 + 8 + UTF-8 + + \ No newline at end of file diff --git a/common/src/main/java/com/skyflow/BaseSkyflow.java b/common/src/main/java/com/skyflow/BaseSkyflow.java new file mode 100644 index 00000000..a60dc02b --- /dev/null +++ b/common/src/main/java/com/skyflow/BaseSkyflow.java @@ -0,0 +1,211 @@ +package com.skyflow; + +import com.skyflow.config.BaseVaultConfig; +import com.skyflow.config.Credentials; +import com.skyflow.enums.LogLevel; +import com.skyflow.errors.ErrorCode; +import com.skyflow.errors.ErrorMessage; +import com.skyflow.errors.SkyflowException; +import com.skyflow.logs.ErrorLogs; +import com.skyflow.logs.InfoLogs; +import com.skyflow.utils.BaseUtils; +import com.skyflow.utils.logger.LogUtil; +import com.skyflow.utils.validations.BaseValidations; + +import java.util.LinkedHashMap; +import java.util.Map; + + +abstract class BaseSkyflow, V extends BaseVaultConfig> implements ISkyflow { + protected final BaseSkyflowClientBuilder builder; + + protected BaseSkyflow(BaseSkyflowClientBuilder builder) { + this.builder = builder; + LogUtil.printInfoLog(InfoLogs.CLIENT_INITIALIZED.getLog()); + } + + protected abstract Self self(); + + @Override + public Self addVaultConfig(V vaultConfig) throws SkyflowException { + this.builder.addVaultConfigTemplate(vaultConfig); + return self(); + } + + public V getVaultConfig(String vaultId) { + return this.builder.vaultConfigMap.get(vaultId); + } + + @Override + public Self updateVaultConfig(V vaultConfig) throws SkyflowException { + this.builder.updateVaultConfigTemplate(vaultConfig); + return self(); + } + + @Override + public Self removeVaultConfig(String vaultId) throws SkyflowException { + this.builder.removeVaultConfigTemplate(vaultId); + return self(); + } + + @Override + public Self updateSkyflowCredentials(Credentials credentials) throws SkyflowException { + this.builder.addSkyflowCredentialsTemplate(credentials); + return self(); + } + + @Override + public Self setLogLevel(LogLevel logLevel) { + this.builder.setLogLevel(logLevel); + return self(); + } + + @Override + public LogLevel getLogLevel() { + return this.builder.logLevel; + } + + protected static T resolveOrThrow(Map map, String key, + ErrorLogs errorLog, ErrorMessage errorMessage) throws SkyflowException { + T value = key != null ? map.get(key) : map.values().stream().findFirst().orElse(null); + if (value == null) { + LogUtil.printErrorLog(errorLog.getLog()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), errorMessage.getMessage()); + } + return value; + } + + abstract static class BaseSkyflowClientBuilder { + protected final LinkedHashMap vaultConfigMap = new LinkedHashMap<>(); + protected Credentials skyflowCredentials; + protected LogLevel logLevel = LogLevel.ERROR; + + protected BaseSkyflowClientBuilder() { + } + + public BaseSkyflowClientBuilder addVaultConfig(V vaultConfig) throws SkyflowException { + addVaultConfigTemplate(vaultConfig); + return this; + } + + public BaseSkyflowClientBuilder updateVaultConfig(V vaultConfig) throws SkyflowException { + updateVaultConfigTemplate(vaultConfig); + return this; + } + + public BaseSkyflowClientBuilder removeVaultConfig(String vaultId) throws SkyflowException { + removeVaultConfigTemplate(vaultId); + return this; + } + + public BaseSkyflowClientBuilder addSkyflowCredentials(Credentials credentials) throws SkyflowException { + addSkyflowCredentialsTemplate(credentials); + return this; + } + + protected BaseSkyflowClientBuilder setLogLevel(LogLevel logLevel) { + this.logLevel = logLevel == null ? LogLevel.ERROR : logLevel; + LogUtil.setupLogger(this.logLevel); + LogUtil.printInfoLog(BaseUtils.parameterizedString( + InfoLogs.CURRENT_LOG_LEVEL.getLog(), String.valueOf(this.logLevel) + )); + return this; + } + + protected final void addVaultConfigTemplate(V vaultConfig) throws SkyflowException { + LogUtil.printInfoLog(InfoLogs.VALIDATING_VAULT_CONFIG.getLog()); + validateVaultConfig(vaultConfig); + V vaultConfigCopy = cloneVaultConfig(vaultConfig); + String vaultId = extractVaultId(vaultConfigCopy); + if (hasVaultClient(vaultId)) { + LogUtil.printErrorLog(BaseUtils.parameterizedString( + ErrorLogs.VAULT_CONFIG_EXISTS.getLog(), vaultId + )); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), + ErrorMessage.VaultIdAlreadyInConfigList.getMessage()); + } + onVaultConfigAdded(vaultConfigCopy); + this.vaultConfigMap.put(vaultId, vaultConfigCopy); + } + + protected final void updateVaultConfigTemplate(V vaultConfig) throws SkyflowException { + LogUtil.printInfoLog(InfoLogs.VALIDATING_VAULT_CONFIG.getLog()); + validateVaultConfig(vaultConfig); + String vaultId = extractVaultId(vaultConfig); + if (!hasVaultClient(vaultId)) { + LogUtil.printErrorLog(BaseUtils.parameterizedString( + ErrorLogs.VAULT_CONFIG_DOES_NOT_EXIST.getLog(), vaultId + )); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.VaultIdNotInConfigList.getMessage()); + } + V previousConfig = this.vaultConfigMap.get(vaultId); + V merged = mergeVaultConfig(vaultConfig, cloneVaultConfig(previousConfig)); + onVaultConfigUpdated(merged); + this.vaultConfigMap.put(vaultId, merged); + } + + protected final void removeVaultConfigTemplate(String vaultId) throws SkyflowException { + if (!hasVaultClient(vaultId)) { + LogUtil.printErrorLog(BaseUtils.parameterizedString(ErrorLogs.VAULT_CONFIG_DOES_NOT_EXIST.getLog(), vaultId)); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.VaultIdNotInConfigList.getMessage()); + } + onVaultConfigRemoved(vaultId); + this.vaultConfigMap.remove(vaultId); + } + + protected final void addSkyflowCredentialsTemplate(Credentials credentials) throws SkyflowException { + BaseValidations.validateCredentials(credentials); + Credentials credentialsCopy; + try { + credentialsCopy = (Credentials) credentials.clone(); + } catch (CloneNotSupportedException e) { + throw new SkyflowException(e.getMessage(), e); + } + onCredentialsUpdated(credentialsCopy); + this.skyflowCredentials = credentialsCopy; + } + + protected abstract void validateVaultConfig(V vaultConfig) throws SkyflowException; + + protected abstract boolean hasVaultClient(String vaultId); + + @SuppressWarnings("unchecked") + protected final V cloneVaultConfig(V vaultConfig) throws SkyflowException { + try { + return (V) vaultConfig.clone(); + } catch (CloneNotSupportedException e) { + throw new SkyflowException(e.getMessage(), e); + } + } + + protected final String extractVaultId(V vaultConfig) { + return vaultConfig.getVaultId(); + } + + protected final V mergeVaultConfig(V incoming, V existing) throws SkyflowException { + if (incoming.getEnv() != null) { + existing.setEnv(incoming.getEnv()); + } + if (incoming.getClusterId() != null) { + existing.setClusterId(incoming.getClusterId()); + } + if (incoming.getCredentials() != null) { + try { + existing.setCredentials((Credentials) incoming.getCredentials().clone()); + } catch (CloneNotSupportedException e) { + throw new SkyflowException(e.getMessage(), e); + } + } + return existing; + } + + protected abstract void onVaultConfigAdded(V vaultConfig) throws SkyflowException; + + protected abstract void onVaultConfigUpdated(V updatedConfig) throws SkyflowException; + + protected abstract void onVaultConfigRemoved(String vaultId) throws SkyflowException; + + protected abstract void onCredentialsUpdated(Credentials credentials) throws SkyflowException; + } + +} diff --git a/common/src/main/java/com/skyflow/BaseVaultClient.java b/common/src/main/java/com/skyflow/BaseVaultClient.java new file mode 100644 index 00000000..3d5b0d32 --- /dev/null +++ b/common/src/main/java/com/skyflow/BaseVaultClient.java @@ -0,0 +1,114 @@ +package com.skyflow; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.skyflow.config.BaseCredentials; +import com.skyflow.config.BaseVaultConfig; +import com.skyflow.errors.ErrorCode; +import com.skyflow.errors.ErrorMessage; +import com.skyflow.errors.SkyflowException; +import com.skyflow.logs.ErrorLogs; +import com.skyflow.logs.InfoLogs; +import com.skyflow.serviceaccount.util.Token; +import com.skyflow.utils.BaseConstants; +import com.skyflow.utils.BaseUtils; +import com.skyflow.utils.logger.LogUtil; +import com.skyflow.utils.validations.BaseValidations; +import io.github.cdimascio.dotenv.Dotenv; +import io.github.cdimascio.dotenv.DotenvException; +import okhttp3.ConnectionPool; +import okhttp3.OkHttpClient; +import okhttp3.Request; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +class BaseVaultClient { + protected V vaultConfig; + protected OkHttpClient sharedHttpClient; + protected String currentVaultURL; + protected BaseCredentials commonCredentials; + protected BaseCredentials finalCredentials; + protected String token; + protected String apiKey; + + protected BaseVaultClient(V vaultConfig, BaseCredentials credentials) { + this.vaultConfig = vaultConfig; + this.commonCredentials = credentials; + } + + protected V getVaultConfig() { + return vaultConfig; + } + + protected OkHttpClient buildSharedHttpClient(Supplier tokenSupplier) { + return new OkHttpClient.Builder() + .connectionPool(new ConnectionPool(10, 1, TimeUnit.MINUTES)) + .addInterceptor(chain -> { + Request requestWithAuth = chain.request().newBuilder() + .header("Authorization", "Bearer " + tokenSupplier.get()) + .build(); + return chain.proceed(requestWithAuth); + }) + .build(); + } + + protected synchronized void prioritiseCredentials(BaseCredentials vaultSpecificCredentials) throws SkyflowException { + try { + BaseCredentials original = this.finalCredentials; + if (vaultSpecificCredentials != null) { + this.finalCredentials = vaultSpecificCredentials; + } else if (this.commonCredentials != null) { + this.finalCredentials = this.commonCredentials; + } else { + String sysCredentials = System.getenv(BaseConstants.ENV_CREDENTIALS_KEY_NAME); + if (sysCredentials == null) { + Dotenv dotenv = Dotenv.load(); + sysCredentials = dotenv.get(BaseConstants.ENV_CREDENTIALS_KEY_NAME); + } + if (sysCredentials == null) { + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyCredentials.getMessage()); + } else { + this.finalCredentials = new BaseCredentials(); + this.finalCredentials.setCredentialsString(sysCredentials); + } + } + if (original != null && !original.equals(this.finalCredentials)) { + token = null; + apiKey = null; + } + } catch (DotenvException e) { + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyCredentials.getMessage()); + } catch (SkyflowException e) { + throw e; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + protected synchronized void setBearerToken(BaseCredentials vaultSpecificCredentials) throws SkyflowException { + prioritiseCredentials(vaultSpecificCredentials); + BaseValidations.validateCredentials(this.finalCredentials); + if (this.finalCredentials.getApiKey() != null) { + LogUtil.printInfoLog(InfoLogs.USE_API_KEY.getLog()); + token = this.finalCredentials.getApiKey(); + } else if (token == null || token.trim().isEmpty()) { + token = BaseUtils.generateBearerToken(this.finalCredentials); + } else if (Token.isExpired(token)) { + LogUtil.printInfoLog(InfoLogs.BEARER_TOKEN_EXPIRED.getLog()); + token = BaseUtils.generateBearerToken(this.finalCredentials); + } else { + LogUtil.printInfoLog(InfoLogs.REUSE_BEARER_TOKEN.getLog()); + } + } + + protected static SkyflowException wrapApiException(int statusCode, Throwable cause, + Map> headers, + Object responseBody, ErrorLogs errorLog) { + LogUtil.printErrorLog(errorLog.getLog()); + Gson gson = new GsonBuilder().serializeNulls().create(); + return new SkyflowException(statusCode, cause, headers, gson.toJson(responseBody)); + } +} diff --git a/common/src/main/java/com/skyflow/ISkyflow.java b/common/src/main/java/com/skyflow/ISkyflow.java new file mode 100644 index 00000000..1c29b780 --- /dev/null +++ b/common/src/main/java/com/skyflow/ISkyflow.java @@ -0,0 +1,20 @@ +package com.skyflow; + +import com.skyflow.config.BaseCredentials; +import com.skyflow.config.BaseVaultConfig; +import com.skyflow.enums.LogLevel; +import com.skyflow.errors.SkyflowException; + +public interface ISkyflow, V extends BaseVaultConfig, C extends BaseCredentials> { + Self addVaultConfig(V vaultConfig) throws SkyflowException; + + Self updateVaultConfig(V vaultConfig) throws SkyflowException; + + Self removeVaultConfig(String vaultId) throws SkyflowException; + + Self updateSkyflowCredentials(C credentials) throws SkyflowException; + + Self setLogLevel(LogLevel logLevel); + + LogLevel getLogLevel(); +} diff --git a/src/main/java/com/skyflow/config/Credentials.java b/common/src/main/java/com/skyflow/config/BaseCredentials.java similarity index 71% rename from src/main/java/com/skyflow/config/Credentials.java rename to common/src/main/java/com/skyflow/config/BaseCredentials.java index c2594ef6..48ab70dc 100644 --- a/src/main/java/com/skyflow/config/Credentials.java +++ b/common/src/main/java/com/skyflow/config/BaseCredentials.java @@ -1,20 +1,21 @@ package com.skyflow.config; import java.util.ArrayList; +import java.util.HashMap; import java.util.Map; -public class Credentials { +public class BaseCredentials implements Cloneable { private String path; private ArrayList roles; - private Object context; private String credentialsString; private String token; private String apiKey; + private Object context; - public Credentials() { + public BaseCredentials() { this.path = null; - this.context = null; this.credentialsString = null; + this.context = null; } public String getPath() { @@ -33,18 +34,6 @@ public void setRoles(ArrayList roles) { this.roles = roles; } - public Object getContext() { - return context; - } - - public void setContext(String context) { - this.context = context; - } - - public void setContext(Map context) { - this.context = context; - } - public String getCredentialsString() { return credentialsString; } @@ -68,4 +57,28 @@ public String getApiKey() { public void setApiKey(String apiKey) { this.apiKey = apiKey; } + public Object getContext() { + return context; + } + + public void setContext(String context) { + this.context = context; + } + + public void setContext(Map context) { + this.context = context; + } + + @Override + @SuppressWarnings("unchecked") + public Object clone() throws CloneNotSupportedException { + BaseCredentials copy = (BaseCredentials) super.clone(); + if (this.roles != null) { + copy.roles = new ArrayList<>(this.roles); + } + if (this.context instanceof Map) { + copy.context = new HashMap<>((Map) this.context); + } + return copy; + } } diff --git a/src/main/java/com/skyflow/config/VaultConfig.java b/common/src/main/java/com/skyflow/config/BaseVaultConfig.java similarity index 71% rename from src/main/java/com/skyflow/config/VaultConfig.java rename to common/src/main/java/com/skyflow/config/BaseVaultConfig.java index 4f61af2a..fb1a2a6b 100644 --- a/src/main/java/com/skyflow/config/VaultConfig.java +++ b/common/src/main/java/com/skyflow/config/BaseVaultConfig.java @@ -2,13 +2,13 @@ import com.skyflow.enums.Env; -public class VaultConfig { +public class BaseVaultConfig implements Cloneable { private String vaultId; private String clusterId; private Env env; private Credentials credentials; - public VaultConfig() { + public BaseVaultConfig() { this.vaultId = null; this.clusterId = null; this.env = Env.PROD; @@ -46,4 +46,14 @@ public Credentials getCredentials() { public void setCredentials(Credentials credentials) { this.credentials = credentials; } + + @Override + public Object clone() throws CloneNotSupportedException { + BaseVaultConfig cloned = (BaseVaultConfig) super.clone(); + if (this.credentials != null) { + cloned.credentials = (Credentials) this.credentials.clone(); + } + return cloned; + } + } diff --git a/common/src/main/java/com/skyflow/config/Credentials.java b/common/src/main/java/com/skyflow/config/Credentials.java new file mode 100644 index 00000000..b0d6b888 --- /dev/null +++ b/common/src/main/java/com/skyflow/config/Credentials.java @@ -0,0 +1,7 @@ +package com.skyflow.config; + +public class Credentials extends BaseCredentials { + public Credentials() { + super(); + } +} diff --git a/common/src/main/java/com/skyflow/enums/CustomHeaderKey.java b/common/src/main/java/com/skyflow/enums/CustomHeaderKey.java new file mode 100644 index 00000000..8d3285ae --- /dev/null +++ b/common/src/main/java/com/skyflow/enums/CustomHeaderKey.java @@ -0,0 +1,18 @@ +package com.skyflow.enums; + +public enum CustomHeaderKey { + SkyflowAccountId("x-skyflow-account-id"), + SkyflowAccountName("x-skyflow-account-name"), + RequestIdHeader("x-request-id"); + + private final String value; + + CustomHeaderKey(String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } +} diff --git a/src/main/java/com/skyflow/enums/Env.java b/common/src/main/java/com/skyflow/enums/Env.java similarity index 100% rename from src/main/java/com/skyflow/enums/Env.java rename to common/src/main/java/com/skyflow/enums/Env.java diff --git a/src/main/java/com/skyflow/enums/LogLevel.java b/common/src/main/java/com/skyflow/enums/LogLevel.java similarity index 100% rename from src/main/java/com/skyflow/enums/LogLevel.java rename to common/src/main/java/com/skyflow/enums/LogLevel.java diff --git a/src/main/java/com/skyflow/errors/ErrorCode.java b/common/src/main/java/com/skyflow/errors/ErrorCode.java similarity index 100% rename from src/main/java/com/skyflow/errors/ErrorCode.java rename to common/src/main/java/com/skyflow/errors/ErrorCode.java diff --git a/common/src/main/java/com/skyflow/errors/ErrorMessage.java b/common/src/main/java/com/skyflow/errors/ErrorMessage.java new file mode 100644 index 00000000..2f52addf --- /dev/null +++ b/common/src/main/java/com/skyflow/errors/ErrorMessage.java @@ -0,0 +1,223 @@ +package com.skyflow.errors; + +import com.skyflow.utils.SdkVersion; + +public enum ErrorMessage { + // Client initialization + VaultIdAlreadyInConfigList("%s0 Validation error. VaultId is present in an existing config. Specify a new vaultId in config."), + VaultIdNotInConfigList("%s0 Validation error. VaultId is missing from the config. Specify the vaultIds from configs."), + OnlySingleVaultConfigAllowed("%s0 Validation error. A vault config already exists. Cannot add another vault config."), + ConnectionIdAlreadyInConfigList("%s0 Validation error. ConnectionId is present in an existing config. Specify a connectionId in config."), + ConnectionIdNotInConfigList("%s0 Validation error. ConnectionId is missing from the config. Specify the connectionIds from configs."), + EmptyCredentials("%s0 Validation error. Invalid credentials. Credentials must not be empty."), + TableSpecifiedInRequestAndRecordObject("%s0 Validation error. Table name cannot be specified at both the request and record levels. Please specify the table name in only one place."), + UpsertTableRequestAtRecordLevel("%s0 Validation error. Table name should be present at each record level when upsert is present at record level."), + UpsertTableRequestAtRequestLevel("%s0 Validation error. Upsert should be present at each record level when table name is present at record level."), + TableNotSpecifiedInRequestAndRecordObject("%s0 Validation error. Table name is missing. Table name should be specified at one place either at the request level or record level. Please specify the table name at one place."), + // Vault config + InvalidVaultId("%s0 Initialization failed. Invalid vault ID. Specify a valid vault ID."), + EmptyVaultId("%s0 Initialization failed. Invalid vault ID. Vault ID must not be empty."), + InvalidClusterId("%s0 Initialization failed. Invalid cluster ID. Specify cluster ID."), + EmptyClusterId("%s0 Initialization failed. Invalid cluster ID. Specify a valid cluster ID."), + EmptyVaultUrl("%s0 Initialization failed. Vault URL is empty. Specify a valid vault URL."), + InvalidVaultUrlFormat("%s0 Initialization failed. Vault URL must start with 'https://'."), + EitherVaultUrlOrClusterIdRequired("%s0 Initialization failed. Specify either 'clusterId' or 'vaultURL'."), + + // Connection config + InvalidConnectionId("%s0 Initialization failed. Invalid connection ID. Specify a valid connection ID."), + EmptyConnectionId("%s0 Initialization failed. Invalid connection ID. Connection ID must not be empty."), + InvalidConnectionUrl("%s0 Initialization failed. Invalid connection URL. Specify a valid connection URL."), + EmptyConnectionUrl("%s0 Initialization failed. Invalid connection URL. Connection URL must not be empty."), + InvalidConnectionUrlFormat("%s0 Initialization failed. Connection URL is not a valid URL. Specify a valid connection URL."), + + // Credentials + MultipleTokenGenerationMeansPassed("%s0 Initialization failed. Invalid credentials. Specify only one from 'path', 'credentialsString', 'token' or 'apiKey'."), + NoTokenGenerationMeansPassed("%s0 Initialization failed. Invalid credentials. Specify any one from 'path', 'credentialsString', 'token' or 'apiKey'."), + EmptyCredentialFilePath("%s0 Initialization failed. Invalid credentials. Credentials file path must not be empty."), + EmptyCredentialsString("%s0 Initialization failed. Invalid credentials. Credentials string must not be empty."), + EmptyToken("%s0 Initialization failed. Invalid credentials. Token must not be empty."), + EmptyApikey("%s0 Initialization failed. Invalid credentials. Api key must not be empty."), + InvalidApikey("%s0 Initialization failed. Invalid credentials. Specify valid api key."), + EmptyRoles("%s0 Initialization failed. Invalid roles. Specify at least one role."), + EmptyRoleInRoles("%s0 Initialization failed. Invalid role. Specify a valid role."), + EmptyContext("%s0 Initialization failed. Invalid context. Specify a valid context."), + InvalidContextType("%s0 Initialization failed. Invalid context type. Specify context as a String or Map."), + InvalidContextMapKey("%s0 Initialization failed. Invalid key '%s1' in context map. Keys must contain only alphanumeric characters and underscores."), + + // Bearer token generation + FileNotFound("%s0 Initialization failed. Credential file not found at %s1. Verify the file path."), + FileInvalidJson("%s0 Initialization failed. File at %s1 is not in valid JSON format. Verify the file contents."), + CredentialsStringInvalidJson("%s0 Initialization failed. Credentials string is not in valid JSON format. Verify the credentials string contents."), + InvalidCredentials("%s0 Initialization failed. Invalid credentials provided. Specify valid credentials."), + MissingPrivateKey("%s0 Initialization failed. Unable to read private key in credentials. Verify your private key."), + MissingClientId("%s0 Initialization failed. Unable to read client ID in credentials. Verify your client ID."), + MissingKeyId("%s0 Initialization failed. Unable to read key ID in credentials. Verify your key ID."), + MissingTokenUri("%s0 Initialization failed. Unable to read token URI in credentials. Verify your token URI."), + InvalidTokenUri("%s0 Initialization failed. Token URI in not a valid URL in credentials. Verify your token URI."), + JwtInvalidFormat("%s0 Initialization failed. Invalid private key format. Verify your credentials."), + InvalidAlgorithm("%s0 Initialization failed. Invalid algorithm to parse private key. Specify valid algorithm."), + InvalidKeySpec("%s0 Initialization failed. Unable to parse RSA private key. Verify your credentials."), + JwtDecodeError("%s0 Validation error. Invalid access token. Verify your credentials."), + MissingAccessToken("%s0 Validation error. Access token not present in the response from bearer token generation. Verify your credentials."), + MissingTokenType("%s0 Validation error. Token type not present in the response from bearer token generation. Verify your credentials."), + BearerTokenExpired("%s0 Validation error. Bearer token is invalid or expired. Please provide a valid bearer token."), + + // Insert + InsertRequestNull("%s0 Validation error. InsertRequest object is null. Specify a valid InsertRequest object."), + TableKeyError("%s0 Validation error. 'table' key is missing from the payload. Specify a 'table' key."), + EmptyTable("%s0 Validation error. 'table' can't be empty. Specify a table."), + ValuesKeyError("%s0 Validation error. 'values' key is missing from the payload. Specify a 'values' key."), + EmptyRecords("%s0 Validation error. 'records' can't be empty. Specify records."), + EmptyKeyInRecords("%s0 Validation error. Invalid key in data in records. Specify a valid key."), + EmptyValueInRecords("%s0 Validation error. Invalid value in records. Specify a valid value."), + RecordsKeyError("%s0 Validation error. 'records' key is missing from the payload. Specify a 'records' key."), + EmptyValues("%s0 Validation error. 'values' can't be empty. Specify values."), + EmptyKeyInValues("%s0 Validation error. Invalid key in values. Specify a valid key."), + EmptyValueInValues("%s0 Validation error. Invalid value in values. Specify a valid value."), + TokensKeyError("%s0 Validation error. 'tokens' key is missing from the payload. Specify a 'tokens' key."), + EmptyTokens("%s0 Validation error. The 'tokens' field is empty. Specify tokens for one or more fields."), + EmptyKeyInTokens("%s0 Validation error. Invalid key tokens. Specify a valid key."), + EmptyValueInTokens("%s0 Validation error. Invalid value in tokens. Specify a valid value."), + EmptyUpsert("%s0 Validation error. 'upsert' key can't be empty. Specify an upsert column."), + InvalidUpsertUpdateType("%s0 Validation error. Invalid upsert updateType. Specify either 'UPDATE' or 'REPLACE'."), + EmptyUpsertValues("%s0 Validation error. Upsert column values can't be empty. Specify at least one upsert column."), + HomogenousNotSupportedWithUpsert("%s0 Validation error. 'homogenous' is not supported with 'upsert'. Specify either 'homogenous' or 'upsert'."), + TokensPassedForTokenModeDisable("%s0 Validation error. 'tokenMode' wasn't specified. Set 'tokenMode' to 'ENABLE' to insert tokens."), + NoTokensWithTokenMode("%s0 Validation error. Tokens weren't specified for records while 'tokenMode' was %s1. Specify tokens."), + MismatchOfFieldsAndTokens("%s0 Validation error. 'fields' and 'tokens' have different columns names. Verify that 'fields' and 'tokens' columns match."), + InsufficientTokensPassedForTokenModeEnableStrict("%s0 Validation error. 'tokenMode' is set to 'ENABLE_STRICT', but some fields are missing tokens. Specify tokens for all fields."), + BatchInsertPartialSuccess("%s0 Insert operation completed with partial success."), + BatchInsertFailure("%s0 Insert operation failed."), + RecordSizeExceedError("%s0 Maximum number of records exceeded. The limit is 10000."), + + // Detokenize + InvalidDetokenizeData("%s0 Validation error. Invalid detokenize data. Specify valid detokenize data."), + EmptyDetokenizeData("%s0 Validation error. Invalid data tokens. Specify at least one data token."), + EmptyTokenInDetokenizeData("%s0 Validation error. Invalid data tokens. Specify a valid data token."), + TokensSizeExceedError("%s0 Maximum number of tokens exceeded. The limit is 10000."), + + // Delete Tokens + DeleteTokensRequestNull("%s0 Validation error. DeleteTokensRequest object is null. Specify a valid DeleteTokensRequest object."), + EmptyDeleteTokensData("%s0 Validation error. Tokens list is empty. Specify at least one token to delete."), + EmptyTokenInDeleteTokensData("%s0 Validation error. Invalid token in delete tokens request. Specify a valid token."), + DeleteTokensSizeExceedError("%s0 Maximum number of tokens exceeded. The limit is 10000."), + + // Get + IdsKeyError("%s0 Validation error. 'ids' key is missing from the payload. Specify an 'ids' key."), + EmptyIds("%s0 Validation error. 'ids' can't be empty. Specify at least one id."), + EmptyIdInIds("%s0 Validation error. Invalid id in 'ids'. Specify a valid id."), + EmptyFields("%s0 Validation error. Fields are empty in get payload. Specify at least one field."), + EmptyFieldInFields("%s0 Validation error. Invalid field in 'fields'. Specify a valid field."), + RedactionKeyError("%s0 Validation error. 'redaction' key is missing from the payload. Specify a 'redaction' key."), + RedactionWithTokensNotSupported("%s0 Validation error. 'redaction' can't be used when 'returnTokens' is specified. Remove 'redaction' from payload if 'returnTokens' is specified."), + TokensGetColumnNotSupported("%s0 Validation error. Column name and/or column values can't be used when 'returnTokens' is specified. Remove unique column values or 'returnTokens' from the payload."), + EmptyOffset("%s0 Validation error. 'offset' can't be empty. Specify an offset."), + EmptyLimit("%s0 Validation error. 'limit' can't be empty. Specify a limit."), + UniqueColumnOrIdsKeyError("%s0 Validation error. 'ids' or 'columnName' key is missing from the payload. Specify the ids or unique 'columnName' in payload."), + BothIdsAndColumnDetailsSpecified("%s0 Validation error. Both Skyflow IDs and column details can't be specified. Either specify Skyflow IDs or unique column details."), + ColumnNameKeyError("%s0 Validation error. 'columnName' isn't specified whereas 'columnValues' are specified. Either add 'columnName' or remove 'columnValues'."), + EmptyColumnName("%s0 Validation error. 'columnName' can't be empty. Specify a column name."), + ColumnValuesKeyErrorGet("%s0 Validation error. 'columnValues' aren't specified whereas 'columnName' is specified. Either add 'columnValues' or remove 'columnName'."), + EmptyColumnValues("%s0 Validation error. 'columnValues' can't be empty. Specify at least one column value"), + EmptyValueInColumnValues("%s0 Validation error. Invalid value in column values. Specify a valid column value."), + IdsOrUniqueValuesKeyError("%s0 Validation error. 'ids' or 'uniqueValues' key is missing from the payload. Specify ids or uniqueValues in payload."), + BothIdsAndUniqueValuesSpecified("%s0 Validation error. Both Skyflow IDs and unique values can't be specified. Either specify Skyflow IDs or unique values."), + EmptyUniqueValues("%s0 Validation error. 'uniqueValues' can't be empty. Specify at least one unique value."), + EmptyUniqueValueInUniqueValues("%s0 Validation error. Invalid unique value in 'uniqueValues'. Specify a valid unique value."), + NullColumnRedactions("%s0 Validation error. Column redaction object can not be null. Specify a valid column redaction object."), + NullColumnNameInColumnRedaction("%s0 Validation error. Column name can not be null or empty in column redaction. Specify a valid column name."), + NullRedactionInColumnRedaction("%s0 Validation error. Redaction can not be null or empty in column redaction. Specify a valid redaction."), + BothSingleTableFieldsAndRecordsSpecified("%s0 Validation error. Both single-table lookup fields ('table', 'ids', 'fields', 'uniqueValues', 'columnRedactions') and 'records' can't be specified. Either specify single-table fields or 'records'."), + NullGetRecordRequest("%s0 Validation error. Record in 'records' is null. Specify a valid record."), + + TokenKeyError("%s0 Validation error. 'token' key is missing from the payload. Specify a 'token' key."), + PartialSuccess("%s0 Validation error. Check 'SkyflowError.data' for details."), + + // Update + DataKeyError("%s0 Validation error. 'data' key is missing from the payload. Specify a 'data' key."), + EmptyData("%s0 Validation error. 'data' can't be empty. Specify data."), + SkyflowIdKeyError("%s0 Validation error. 'skyflow_id' is missing from the data payload. Specify a 'skyflow_id'."), + InvalidSkyflowIdType("%s0 Validation error. Invalid type for 'skyflow_id' in data payload. Specify 'skyflow_id' as a string."), + EmptySkyflowId("%s0 Validation error. 'skyflow_id' can't be empty. Specify a skyflow id."), + + // Query + QueryKeyError("%s0 Validation error. 'query' key is missing from the payload. Specify a 'query' key."), + EmptyQuery("%s0 Validation error. 'query' can't be empty. Specify a query"), + + // Tokenize + ColumnValuesKeyErrorTokenize("%s0 Validation error. 'columnValues' key is missing from the payload. Specify a 'columnValues' key."), + EmptyColumnGroupInColumnValue("%s0 Validation error. Invalid column group in column value. Specify a valid column group."), + TokenizeRequestNull("%s0 Validation error. TokenizeRequest object is null. Specify a valid TokenizeRequest object."), + EmptyTokenizeData("%s0 Validation error. Tokenize data is empty. Specify at least one tokenize record."), + TokenizeRecordNull("%s0 Validation error. TokenizeRecord in the list is null. Specify a valid TokenizeRecord object."), + EmptyValueInTokenizeRecord("%s0 Validation error. Value in TokenizeRecord is null or empty. Specify a valid value."), + EmptyTokenGroupNamesInTokenizeRecord("%s0 Validation error. TokenGroupNames in TokenizeRecord is null or empty. Specify at least one token group name."), + EmptyTokenGroupNameInTokenizeRecord("%s0 Validation error. Token group name in TokenizeRecord is null or empty. Specify a valid token group name."), + TokenizeDataSizeExceedError("%s0 Maximum number of tokenize records exceeded. The limit is 10000."), + MissingIndexInBulkTokenizeRecord("%s0 Validation error. Index in BulkTokenizeRequestRecord is null. Specify an index for every record."), + DuplicateIndexInBulkTokenizeRecord("%s0 Validation error. Duplicate index in BulkTokenizeRequestRecord. Specify a unique index for every record."), + + // Connection + InvalidRequestHeaders("%s0 Validation error. Request headers aren't valid. Specify valid request headers."), + EmptyRequestHeaders("%s0 Validation error. Request headers are empty. Specify valid request headers."), + InvalidPathParams("%s0 Validation error. Path parameters aren't valid. Specify valid path parameters."), + EmptyPathParams("%s0 Validation error. Path parameters are empty. Specify valid path parameters."), + InvalidQueryParams("%s0 Validation error. Query parameters aren't valid. Specify valid query parameters."), + EmptyQueryParams("%s0 Validation error. Query parameters are empty. Specify valid query parameters."), + InvalidRequestBody("%s0 Validation error. Invalid request body. Specify the request body as an object."), + EmptyRequestBody("%s0 Validation error. Request body can't be empty. Specify a valid request body."), + + // File upload + ColumnNameKeyErrorFileUpload("%s0 Validation error. columnName is missing from the payload. Specify a columnName key."), + MissingFileSourceInUploadFileRequest("%s0 Validation error. Provide exactly one of filePath, base64, or fileObject."), + FileNameMustBeProvidedWithFileObject("%s0 Validation error. fileName must be provided when using fileObject."), + InvalidFileObject("%s0 Validation error. Invalid file object in file upload request. Specify a valid file object."), + InvalidBase64("%s0 Validation error. Invalid base64 string in file upload request. Specify a valid base64 string."), + + // detect + InvalidTextInDeIdentify("%s0 Validation error. The text field is required and must be a non-empty string. Specify a valid text."), + InvalidTextInReIdentify("%s0 Validation error. The text field is required and must be a non-empty string. Specify a valid text."), + + //Detect Files + InvalidNullFileInDeIdentifyFile("%s0 Validation error. The file field is required and must not be null. Specify a valid file object."), + InvalidFilePath("%s0 Validation error. The file path is invalid. Specify a valid file path."), + BothFileAndFilePathProvided("%s0 Validation error. Both file and filePath are provided. Specify either file object or filePath, not both."), + FileNotFoundToDeidentify("%s0 Validation error. The file to deidentify was not found at the specified path. Verify the file path and try again."), + FileNotReadableToDeidentify("%s0 Validation error. The file to deidentify is not readable. Check the file permissions and try again."), + InvalidPixelDensityToDeidentifyFile("%s0 Validation error. Should be a positive integer. Specify a valid pixel density."), + InvalidMaxResolution("%s0 Validation error. Should be a positive integer. Specify a valid max resolution."), + OutputDirectoryNotFound("%s0 Validation error. The output directory for deidentified files was not found at the specified path. Verify the output directory path and try again."), + InvalidPermission("%s0 Validation error. The output directory for deidentified files is not writable. Check the directory permissions and try again."), + InvalidWaitTime("%s0 Validation error. The wait time for deidentify file operation should be a positive integer. Specify a valid wait time."), + WaitTimeExceedsLimit("%s0 Validation error. The wait time for deidentify file operation exceeds the maximum limit of 64 seconds. Specify a wait time less than or equal to 60 seconds."), + InvalidOrEmptyRunId("%s0 Validation error. The run ID is invalid or empty. Specify a valid run ID."), + FailedToEncodeFile("%s0 Validation error. Failed to encode the file. Ensure the file is in a supported format and try again."), + FailedToDecodeFileFromResponse("%s0 Failed to decode the file from the response. Ensure the response is valid and try again."), + EmptyFileAndFilePathInDeIdentifyFile("%s0 Validation error. Both file and filePath are empty. Specify either file object or filePath, not both."), + VaultTokenFormatIsNotAllowedForFiles("%s0 Validation error. Vault token format is not allowed for deidentify file request."), + PollingForResultsFailed("%s0 API error. Polling for results failed. Unable to retrieve the deidentified file"), + FailedToSaveProcessedFile("%s0 Validation error. Failed to save the processed file. Ensure the output directory is valid and writable."), + InvalidAudioFileType("%s0 Validation error. The file type is not supported. Specify a valid file type mp3 or wav."), + // Generic + ErrorOccurred("%s0 API error. Error occurred."), + + DetokenizeRequestNull("%s0 Validation error. DetokenizeRequest object is null. Specify a valid DetokenizeRequest object."), + + NullTokenGroupRedactions("%s0 Validation error. TokenGroupRedaction in the list is null. Specify a valid TokenGroupRedactions object."), + + NullRedactionInTokenGroup("%s0 Validation error. Redaction in TokenGroupRedactions is null or empty. Specify a valid redaction."), + + NullTokenGroupNameInTokenGroup("%s0 Validation error. TokenGroupName in TokenGroupRedactions is null or empty. Specify a valid tokenGroupName."), + InvalidRecord("%s0 Validation error. InsertRecord object in the list is invalid. Specify a valid InsertRecord object."), + ; + + private final String message; + + ErrorMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message.replace("%s0", SdkVersion.getSdkPrefix()); + } +} diff --git a/src/main/java/com/skyflow/errors/HttpStatus.java b/common/src/main/java/com/skyflow/errors/HttpStatus.java similarity index 100% rename from src/main/java/com/skyflow/errors/HttpStatus.java rename to common/src/main/java/com/skyflow/errors/HttpStatus.java diff --git a/common/src/main/java/com/skyflow/errors/SkyflowException.java b/common/src/main/java/com/skyflow/errors/SkyflowException.java new file mode 100644 index 00000000..676bdd74 --- /dev/null +++ b/common/src/main/java/com/skyflow/errors/SkyflowException.java @@ -0,0 +1,135 @@ +package com.skyflow.errors; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.skyflow.utils.BaseConstants; + +import java.util.List; +import java.util.Map; + +public class SkyflowException extends Exception { + private String requestId; + private Integer grpcCode; + private Integer httpCode; + private String message; + private String httpStatus; + private JsonArray details; + private JsonObject responseBody; + + public SkyflowException(String message) { + super(message); + this.message = message; + } + + public SkyflowException(Throwable cause) { + super(cause); + this.message = cause.getMessage(); + } + + public SkyflowException(String message, Throwable cause) { + super(message, cause); + this.message = message; + } + + public SkyflowException(int code, String message) { + super(message); + this.httpCode = code; + this.message = message; + this.httpStatus = HttpStatus.BAD_REQUEST.getHttpStatus(); + this.details = new JsonArray(); + } + + public SkyflowException(int httpCode, Throwable cause, Map> responseHeaders, String responseBody) { + super(cause); + this.httpCode = httpCode > 0 ? httpCode : 400; + try { + setRequestId(responseHeaders); + setResponseBody(responseBody, responseHeaders); + } catch (Exception e) { + this.httpStatus = HttpStatus.BAD_REQUEST.getHttpStatus(); + String fullMessage = responseBody != null ? responseBody : + (cause.getLocalizedMessage() != null ? cause.getMessage() : ErrorMessage.ErrorOccurred.getMessage()); + this.message = fullMessage.split("HTTP response code:")[0].trim(); + } + } + + private void setResponseBody(String responseBody, Map> responseHeaders) { + this.responseBody = JsonParser.parseString(responseBody).getAsJsonObject(); + if (this.responseBody.get("error") != null) { + setGrpcCode(); + setHttpStatus(); + setMessage(); + setDetails(responseHeaders); + } + } + + public String getRequestId() { + return requestId; + } + + private void setRequestId(Map> responseHeaders) { + List ids = responseHeaders.get(BaseConstants.REQUEST_ID_HEADER_KEY); + this.requestId = ids == null ? null : ids.get(0); + } + + private void setMessage() { + JsonElement messageElement = ((JsonObject) responseBody.get("error")).get("message"); + this.message = messageElement == null ? null : messageElement.getAsString(); + } + + private void setGrpcCode() { + JsonElement grpcElement = ((JsonObject) responseBody.get("error")).get("grpc_code"); + this.grpcCode = grpcElement == null ? null : grpcElement.getAsInt(); + } + + private void setHttpStatus() { + JsonElement statusElement = ((JsonObject) responseBody.get("error")).get("http_status"); + this.httpStatus = statusElement == null ? null : statusElement.getAsString(); + } + + public int getHttpCode() { + return httpCode == null ? 0 : httpCode; + } + + public JsonArray getDetails() { + return details; + } + + private void setDetails(Map> responseHeaders) { + JsonElement detailsElement = ((JsonObject) responseBody.get("error")).get("details"); + List errorFromClientHeader = responseHeaders.get(BaseConstants.ERROR_FROM_CLIENT_HEADER_KEY); + if (detailsElement != null) { + this.details = detailsElement.getAsJsonArray(); + } + if (errorFromClientHeader != null) { + this.details = this.details == null ? new JsonArray() : this.details; + String errorFromClient = errorFromClientHeader.get(0); + JsonObject detailObject = new JsonObject(); + detailObject.addProperty("errorFromClient", errorFromClient); + this.details.add(detailObject); + } + } + + public Integer getGrpcCode() { + return grpcCode; + } + + public String getHttpStatus() { + return httpStatus; + } + + @Override + public String getMessage() { + return message; + } + + @Override + public String toString() { + return String.format( + "%n requestId: %s%n grpcCode: %s%n httpCode: %s%n httpStatus: %s%n message: %s%n details: %s", + this.requestId, this.grpcCode, this.httpCode, this.httpStatus, this.message, this.details + ); + } +} diff --git a/common/src/main/java/com/skyflow/generated/auth/rest/ApiClient.java b/common/src/main/java/com/skyflow/generated/auth/rest/ApiClient.java new file mode 100644 index 00000000..a1dd3aae --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/auth/rest/ApiClient.java @@ -0,0 +1,29 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.auth.rest; + +import com.skyflow.generated.auth.rest.core.ClientOptions; +import com.skyflow.generated.auth.rest.core.Suppliers; +import com.skyflow.generated.auth.rest.resources.authentication.AuthenticationClient; + +import java.util.function.Supplier; + +public class ApiClient { + protected final ClientOptions clientOptions; + + protected final Supplier authenticationClient; + + public ApiClient(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + this.authenticationClient = Suppliers.memoize(() -> new AuthenticationClient(clientOptions)); + } + + public AuthenticationClient authentication() { + return this.authenticationClient.get(); + } + + public static ApiClientBuilder builder() { + return new ApiClientBuilder(); + } +} diff --git a/common/src/main/java/com/skyflow/generated/auth/rest/ApiClientBuilder.java b/common/src/main/java/com/skyflow/generated/auth/rest/ApiClientBuilder.java new file mode 100644 index 00000000..aed3ed24 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/auth/rest/ApiClientBuilder.java @@ -0,0 +1,67 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.auth.rest; + +import com.skyflow.generated.auth.rest.core.ClientOptions; +import com.skyflow.generated.auth.rest.core.Environment; +import okhttp3.OkHttpClient; + +public final class ApiClientBuilder { + private ClientOptions.Builder clientOptionsBuilder = ClientOptions.builder(); + + private String token = null; + + private Environment environment = Environment.PRODUCTION; + + /** + * Sets token + */ + public ApiClientBuilder token(String token) { + this.token = token; + return this; + } + + public ApiClientBuilder environment(Environment environment) { + this.environment = environment; + return this; + } + + public ApiClientBuilder url(String url) { + this.environment = Environment.custom(url); + return this; + } + + /** + * Sets the timeout (in seconds) for the client. Defaults to 60 seconds. + */ + public ApiClientBuilder timeout(int timeout) { + this.clientOptionsBuilder.timeout(timeout); + return this; + } + + /** + * Sets the maximum number of retries for the client. Defaults to 2 retries. + */ + public ApiClientBuilder maxRetries(int maxRetries) { + this.clientOptionsBuilder.maxRetries(maxRetries); + return this; + } + + /** + * Sets the underlying OkHttp client + */ + public ApiClientBuilder httpClient(OkHttpClient httpClient) { + this.clientOptionsBuilder.httpClient(httpClient); + return this; + } + + public ApiClient build() { + if (token == null) { + throw new RuntimeException("Please provide token"); + } + this.clientOptionsBuilder.addHeader("Authorization", "Bearer " + this.token); + clientOptionsBuilder.environment(this.environment); + return new ApiClient(clientOptionsBuilder.build()); + } +} diff --git a/common/src/main/java/com/skyflow/generated/auth/rest/AsyncApiClient.java b/common/src/main/java/com/skyflow/generated/auth/rest/AsyncApiClient.java new file mode 100644 index 00000000..748eb02e --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/auth/rest/AsyncApiClient.java @@ -0,0 +1,29 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.auth.rest; + +import com.skyflow.generated.auth.rest.core.ClientOptions; +import com.skyflow.generated.auth.rest.core.Suppliers; +import com.skyflow.generated.auth.rest.resources.authentication.AsyncAuthenticationClient; + +import java.util.function.Supplier; + +public class AsyncApiClient { + protected final ClientOptions clientOptions; + + protected final Supplier authenticationClient; + + public AsyncApiClient(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + this.authenticationClient = Suppliers.memoize(() -> new AsyncAuthenticationClient(clientOptions)); + } + + public AsyncAuthenticationClient authentication() { + return this.authenticationClient.get(); + } + + public static AsyncApiClientBuilder builder() { + return new AsyncApiClientBuilder(); + } +} diff --git a/common/src/main/java/com/skyflow/generated/auth/rest/AsyncApiClientBuilder.java b/common/src/main/java/com/skyflow/generated/auth/rest/AsyncApiClientBuilder.java new file mode 100644 index 00000000..2e30d45a --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/auth/rest/AsyncApiClientBuilder.java @@ -0,0 +1,67 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.auth.rest; + +import com.skyflow.generated.auth.rest.core.ClientOptions; +import com.skyflow.generated.auth.rest.core.Environment; +import okhttp3.OkHttpClient; + +public final class AsyncApiClientBuilder { + private ClientOptions.Builder clientOptionsBuilder = ClientOptions.builder(); + + private String token = null; + + private Environment environment = Environment.PRODUCTION; + + /** + * Sets token + */ + public AsyncApiClientBuilder token(String token) { + this.token = token; + return this; + } + + public AsyncApiClientBuilder environment(Environment environment) { + this.environment = environment; + return this; + } + + public AsyncApiClientBuilder url(String url) { + this.environment = Environment.custom(url); + return this; + } + + /** + * Sets the timeout (in seconds) for the client. Defaults to 60 seconds. + */ + public AsyncApiClientBuilder timeout(int timeout) { + this.clientOptionsBuilder.timeout(timeout); + return this; + } + + /** + * Sets the maximum number of retries for the client. Defaults to 2 retries. + */ + public AsyncApiClientBuilder maxRetries(int maxRetries) { + this.clientOptionsBuilder.maxRetries(maxRetries); + return this; + } + + /** + * Sets the underlying OkHttp client + */ + public AsyncApiClientBuilder httpClient(OkHttpClient httpClient) { + this.clientOptionsBuilder.httpClient(httpClient); + return this; + } + + public AsyncApiClient build() { + if (token == null) { + throw new RuntimeException("Please provide token"); + } + this.clientOptionsBuilder.addHeader("Authorization", "Bearer " + this.token); + clientOptionsBuilder.environment(this.environment); + return new AsyncApiClient(clientOptionsBuilder.build()); + } +} diff --git a/common/src/main/java/com/skyflow/generated/auth/rest/core/ApiClientApiException.java b/common/src/main/java/com/skyflow/generated/auth/rest/core/ApiClientApiException.java new file mode 100644 index 00000000..53d67f0b --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/auth/rest/core/ApiClientApiException.java @@ -0,0 +1,74 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.auth.rest.core; + +import okhttp3.Response; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * This exception type will be thrown for any non-2XX API responses. + */ +public class ApiClientApiException extends ApiClientException { + /** + * 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; + + private final Map> headers; + + public ApiClientApiException(String message, int statusCode, Object body) { + super(message); + this.statusCode = statusCode; + this.body = body; + this.headers = new HashMap<>(); + } + + public ApiClientApiException(String message, int statusCode, Object body, Response rawResponse) { + super(message); + this.statusCode = statusCode; + this.body = body; + this.headers = new HashMap<>(); + rawResponse.headers().forEach(header -> { + String key = header.component1(); + String value = header.component2(); + this.headers.computeIfAbsent(key, _str -> new ArrayList<>()).add(value); + }); + } + + /** + * @return the statusCode + */ + public int statusCode() { + return this.statusCode; + } + + /** + * @return the body + */ + public Object body() { + return this.body; + } + + /** + * @return the headers + */ + public Map> headers() { + return this.headers; + } + + @Override + public String toString() { + return "ApiClientApiException{" + "message: " + getMessage() + ", statusCode: " + statusCode + ", body: " + body + + "}"; + } +} diff --git a/common/src/main/java/com/skyflow/generated/auth/rest/core/ApiClientException.java b/common/src/main/java/com/skyflow/generated/auth/rest/core/ApiClientException.java new file mode 100644 index 00000000..f08afa2e --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/auth/rest/core/ApiClientException.java @@ -0,0 +1,17 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.auth.rest.core; + +/** + * This class serves as the base exception for all errors in the SDK. + */ +public class ApiClientException extends RuntimeException { + public ApiClientException(String message) { + super(message); + } + + public ApiClientException(String message, Exception e) { + super(message, e); + } +} diff --git a/common/src/main/java/com/skyflow/generated/auth/rest/core/ApiClientHttpResponse.java b/common/src/main/java/com/skyflow/generated/auth/rest/core/ApiClientHttpResponse.java new file mode 100644 index 00000000..8a28d22f --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/auth/rest/core/ApiClientHttpResponse.java @@ -0,0 +1,38 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.auth.rest.core; + +import okhttp3.Response; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public final class ApiClientHttpResponse { + + private final T body; + + private final Map> headers; + + public ApiClientHttpResponse(T body, Response rawResponse) { + this.body = body; + + Map> headers = new HashMap<>(); + rawResponse.headers().forEach(header -> { + String key = header.component1(); + String value = header.component2(); + headers.computeIfAbsent(key, _str -> new ArrayList<>()).add(value); + }); + this.headers = headers; + } + + public T body() { + return this.body; + } + + public Map> headers() { + return headers; + } +} diff --git a/common/src/main/java/com/skyflow/generated/auth/rest/core/ClientOptions.java b/common/src/main/java/com/skyflow/generated/auth/rest/core/ClientOptions.java new file mode 100644 index 00000000..4eee6b92 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/auth/rest/core/ClientOptions.java @@ -0,0 +1,171 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.auth.rest.core; + +import okhttp3.OkHttpClient; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +public final class ClientOptions { + private final Environment environment; + + private final Map headers; + + private final Map> headerSuppliers; + + private final OkHttpClient httpClient; + + private final int timeout; + + private ClientOptions( + Environment environment, + Map headers, + Map> headerSuppliers, + OkHttpClient httpClient, + int timeout) { + this.environment = environment; + this.headers = new HashMap<>(); + this.headers.putAll(headers); + this.headers.putAll(new HashMap() { + { + put("X-Fern-Language", "JAVA"); + put("X-Fern-SDK-Name", "com.skyflow.generated.rest.fern:api-sdk"); + put("X-Fern-SDK-Version", "0.0.279"); + } + }); + this.headerSuppliers = headerSuppliers; + this.httpClient = httpClient; + this.timeout = timeout; + } + + 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 int timeout(RequestOptions requestOptions) { + if (requestOptions == null) { + return this.timeout; + } + return requestOptions.getTimeout().orElse(this.timeout); + } + + 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<>(); + + private int maxRetries = 2; + + private Optional timeout = Optional.empty(); + + private OkHttpClient httpClient = null; + + 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; + } + + /** + * Override the timeout in seconds. Defaults to 60 seconds. + */ + public Builder timeout(int timeout) { + this.timeout = Optional.of(timeout); + return this; + } + + /** + * Override the timeout in seconds. Defaults to 60 seconds. + */ + public Builder timeout(Optional timeout) { + this.timeout = timeout; + return this; + } + + /** + * Override the maximum number of retries. Defaults to 2 retries. + */ + public Builder maxRetries(int maxRetries) { + this.maxRetries = maxRetries; + return this; + } + + public Builder httpClient(OkHttpClient httpClient) { + this.httpClient = httpClient; + return this; + } + + public ClientOptions build() { + OkHttpClient.Builder httpClientBuilder = + this.httpClient != null ? this.httpClient.newBuilder() : new OkHttpClient.Builder(); + + if (this.httpClient != null) { + timeout.ifPresent(timeout -> httpClientBuilder + .callTimeout(timeout, TimeUnit.SECONDS) + .connectTimeout(0, TimeUnit.SECONDS) + .writeTimeout(0, TimeUnit.SECONDS) + .readTimeout(0, TimeUnit.SECONDS)); + } else { + httpClientBuilder + .callTimeout(this.timeout.orElse(60), TimeUnit.SECONDS) + .connectTimeout(0, TimeUnit.SECONDS) + .writeTimeout(0, TimeUnit.SECONDS) + .readTimeout(0, TimeUnit.SECONDS) + .addInterceptor(new RetryInterceptor(this.maxRetries)); + } + + this.httpClient = httpClientBuilder.build(); + this.timeout = Optional.of(httpClient.callTimeoutMillis() / 1000); + + return new ClientOptions(environment, headers, headerSuppliers, httpClient, this.timeout.get()); + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/auth/rest/core/DateTimeDeserializer.java b/common/src/main/java/com/skyflow/generated/auth/rest/core/DateTimeDeserializer.java new file mode 100644 index 00000000..dfa25a82 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/auth/rest/core/DateTimeDeserializer.java @@ -0,0 +1,56 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.auth.rest.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/common/src/main/java/com/skyflow/generated/auth/rest/core/Environment.java b/common/src/main/java/com/skyflow/generated/auth/rest/core/Environment.java new file mode 100644 index 00000000..098667c0 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/auth/rest/core/Environment.java @@ -0,0 +1,24 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.auth.rest.core; + +public final class Environment { + public static final Environment PRODUCTION = new Environment("https://manage.skyflowapis.com"); + + public static final Environment SANDBOX = new Environment("https://manage.skyflowapis-preview.com"); + + 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/common/src/main/java/com/skyflow/generated/auth/rest/core/FileStream.java b/common/src/main/java/com/skyflow/generated/auth/rest/core/FileStream.java new file mode 100644 index 00000000..0e878ed3 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/auth/rest/core/FileStream.java @@ -0,0 +1,61 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.auth.rest.core; + +import okhttp3.MediaType; +import okhttp3.RequestBody; +import org.jetbrains.annotations.Nullable; + +import java.io.InputStream; +import java.util.Objects; + +/** + * Represents a file stream with associated metadata for file uploads. + */ +public class FileStream { + private final InputStream inputStream; + private final String fileName; + private final MediaType contentType; + + /** + * Constructs a FileStream with the given input stream and optional metadata. + * + * @param inputStream The input stream of the file content. Must not be null. + * @param fileName The name of the file, or null if unknown. + * @param contentType The MIME type of the file content, or null if unknown. + * @throws NullPointerException if inputStream is null + */ + public FileStream(InputStream inputStream, @Nullable String fileName, @Nullable MediaType contentType) { + this.inputStream = Objects.requireNonNull(inputStream, "Input stream cannot be null"); + this.fileName = fileName; + this.contentType = contentType; + } + + public FileStream(InputStream inputStream) { + this(inputStream, null, null); + } + + public InputStream getInputStream() { + return inputStream; + } + + @Nullable + public String getFileName() { + return fileName; + } + + @Nullable + public MediaType getContentType() { + return contentType; + } + + /** + * Creates a RequestBody suitable for use with OkHttp client. + * + * @return A RequestBody instance representing this file stream. + */ + public RequestBody toRequestBody() { + return new InputStreamRequestBody(contentType, inputStream); + } +} diff --git a/common/src/main/java/com/skyflow/generated/auth/rest/core/InputStreamRequestBody.java b/common/src/main/java/com/skyflow/generated/auth/rest/core/InputStreamRequestBody.java new file mode 100644 index 00000000..a94faec9 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/auth/rest/core/InputStreamRequestBody.java @@ -0,0 +1,80 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.auth.rest.core; + +import okhttp3.MediaType; +import okhttp3.RequestBody; +import okhttp3.internal.Util; +import okio.BufferedSink; +import okio.Okio; +import okio.Source; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Objects; + +/** + * A custom implementation of OkHttp's RequestBody that wraps an InputStream. + * This class allows streaming of data from an InputStream directly to an HTTP request body, + * which is useful for file uploads or sending large amounts of data without loading it all into memory. + */ +public class InputStreamRequestBody extends RequestBody { + private final InputStream inputStream; + private final MediaType contentType; + + /** + * Constructs an InputStreamRequestBody with the specified content type and input stream. + * + * @param contentType the MediaType of the content, or null if not known + * @param inputStream the InputStream containing the data to be sent + * @throws NullPointerException if inputStream is null + */ + public InputStreamRequestBody(@Nullable MediaType contentType, InputStream inputStream) { + this.contentType = contentType; + this.inputStream = Objects.requireNonNull(inputStream, "inputStream == null"); + } + + /** + * Returns the content type of this request body. + * + * @return the MediaType of the content, or null if not specified + */ + @Nullable + @Override + public MediaType contentType() { + return contentType; + } + + /** + * Returns the content length of this request body, if known. + * This method attempts to determine the length using the InputStream's available() method, + * which may not always accurately reflect the total length of the stream. + * + * @return the content length, or -1 if the length is unknown + * @throws IOException if an I/O error occurs + */ + @Override + public long contentLength() throws IOException { + return inputStream.available() == 0 ? -1 : inputStream.available(); + } + + /** + * Writes the content of the InputStream to the given BufferedSink. + * This method is responsible for transferring the data from the InputStream to the network request. + * + * @param sink the BufferedSink to write the content to + * @throws IOException if an I/O error occurs during writing + */ + @Override + public void writeTo(BufferedSink sink) throws IOException { + Source source = null; + try { + source = Okio.source(inputStream); + sink.writeAll(source); + } finally { + Util.closeQuietly(Objects.requireNonNull(source)); + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/auth/rest/core/MediaTypes.java b/common/src/main/java/com/skyflow/generated/auth/rest/core/MediaTypes.java new file mode 100644 index 00000000..d4374647 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/auth/rest/core/MediaTypes.java @@ -0,0 +1,13 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.auth.rest.core; + +import okhttp3.MediaType; + +public final class MediaTypes { + + public static final MediaType APPLICATION_JSON = MediaType.parse("application/json"); + + private MediaTypes() {} +} diff --git a/common/src/main/java/com/skyflow/generated/auth/rest/core/Nullable.java b/common/src/main/java/com/skyflow/generated/auth/rest/core/Nullable.java new file mode 100644 index 00000000..efabe806 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/auth/rest/core/Nullable.java @@ -0,0 +1,140 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.auth.rest.core; + +import java.util.Optional; +import java.util.function.Function; + +public final class Nullable { + + private final Either, Null> value; + + private Nullable() { + this.value = Either.left(Optional.empty()); + } + + private Nullable(T value) { + if (value == null) { + this.value = Either.right(Null.INSTANCE); + } else { + this.value = Either.left(Optional.of(value)); + } + } + + public static Nullable ofNull() { + return new Nullable<>(null); + } + + public static Nullable of(T value) { + return new Nullable<>(value); + } + + public static Nullable empty() { + return new Nullable<>(); + } + + public static Nullable ofOptional(Optional value) { + if (value.isPresent()) { + return of(value.get()); + } else { + return empty(); + } + } + + public boolean isNull() { + return this.value.isRight(); + } + + public boolean isEmpty() { + return this.value.isLeft() && !this.value.getLeft().isPresent(); + } + + public T get() { + if (this.isNull()) { + return null; + } + + return this.value.getLeft().get(); + } + + public Nullable map(Function mapper) { + if (this.isNull()) { + return Nullable.ofNull(); + } + + return Nullable.ofOptional(this.value.getLeft().map(mapper)); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof Nullable)) { + return false; + } + + if (((Nullable) other).isNull() && this.isNull()) { + return true; + } + + return this.value.getLeft().equals(((Nullable) other).value.getLeft()); + } + + private static final class Either { + private L left = null; + private R right = null; + + private Either(L left, R right) { + if (left != null && right != null) { + throw new IllegalArgumentException("Left and right argument cannot both be non-null."); + } + + if (left == null && right == null) { + throw new IllegalArgumentException("Left and right argument cannot both be null."); + } + + if (left != null) { + this.left = left; + } + + if (right != null) { + this.right = right; + } + } + + public static Either left(L left) { + return new Either<>(left, null); + } + + public static Either right(R right) { + return new Either<>(null, right); + } + + public boolean isLeft() { + return this.left != null; + } + + public boolean isRight() { + return this.right != null; + } + + public L getLeft() { + if (!this.isLeft()) { + throw new IllegalArgumentException("Cannot get left from right Either."); + } + return this.left; + } + + public R getRight() { + if (!this.isRight()) { + throw new IllegalArgumentException("Cannot get right from left Either."); + } + return this.right; + } + } + + private static final class Null { + private static final Null INSTANCE = new Null(); + + private Null() {} + } +} diff --git a/common/src/main/java/com/skyflow/generated/auth/rest/core/NullableNonemptyFilter.java b/common/src/main/java/com/skyflow/generated/auth/rest/core/NullableNonemptyFilter.java new file mode 100644 index 00000000..dd32d66c --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/auth/rest/core/NullableNonemptyFilter.java @@ -0,0 +1,19 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.auth.rest.core; + +import java.util.Optional; + +public final class NullableNonemptyFilter { + @Override + public boolean equals(Object o) { + boolean isOptionalEmpty = isOptionalEmpty(o); + + return isOptionalEmpty; + } + + private boolean isOptionalEmpty(Object o) { + return o instanceof Optional && !((Optional) o).isPresent(); + } +} diff --git a/common/src/main/java/com/skyflow/generated/auth/rest/core/ObjectMappers.java b/common/src/main/java/com/skyflow/generated/auth/rest/core/ObjectMappers.java new file mode 100644 index 00000000..4997b3ca --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/auth/rest/core/ObjectMappers.java @@ -0,0 +1,37 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.auth.rest.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/common/src/main/java/com/skyflow/generated/auth/rest/core/QueryStringMapper.java b/common/src/main/java/com/skyflow/generated/auth/rest/core/QueryStringMapper.java new file mode 100644 index 00000000..8b1f9f7e --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/auth/rest/core/QueryStringMapper.java @@ -0,0 +1,139 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.auth.rest.core; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import okhttp3.HttpUrl; +import okhttp3.MultipartBody; + +import java.util.*; + +public class QueryStringMapper { + + private static final ObjectMapper MAPPER = ObjectMappers.JSON_MAPPER; + + public static void addQueryParameter(HttpUrl.Builder httpUrl, String key, Object value, boolean arraysAsRepeats) { + JsonNode valueNode = MAPPER.valueToTree(value); + + List> flat; + if (valueNode.isObject()) { + flat = flattenObject((ObjectNode) valueNode, arraysAsRepeats); + } else if (valueNode.isArray()) { + flat = flattenArray((ArrayNode) valueNode, "", arraysAsRepeats); + } else { + if (valueNode.isTextual()) { + httpUrl.addQueryParameter(key, valueNode.textValue()); + } else { + httpUrl.addQueryParameter(key, valueNode.toString()); + } + return; + } + + for (Map.Entry field : flat) { + if (field.getValue().isTextual()) { + httpUrl.addQueryParameter(key + field.getKey(), field.getValue().textValue()); + } else { + httpUrl.addQueryParameter(key + field.getKey(), field.getValue().toString()); + } + } + } + + public static void addFormDataPart( + MultipartBody.Builder multipartBody, String key, Object value, boolean arraysAsRepeats) { + JsonNode valueNode = MAPPER.valueToTree(value); + + List> flat; + if (valueNode.isObject()) { + flat = flattenObject((ObjectNode) valueNode, arraysAsRepeats); + } else if (valueNode.isArray()) { + flat = flattenArray((ArrayNode) valueNode, "", arraysAsRepeats); + } else { + if (valueNode.isTextual()) { + multipartBody.addFormDataPart(key, valueNode.textValue()); + } else { + multipartBody.addFormDataPart(key, valueNode.toString()); + } + return; + } + + for (Map.Entry field : flat) { + if (field.getValue().isTextual()) { + multipartBody.addFormDataPart( + key + field.getKey(), field.getValue().textValue()); + } else { + multipartBody.addFormDataPart( + key + field.getKey(), field.getValue().toString()); + } + } + } + + public static List> flattenObject(ObjectNode object, boolean arraysAsRepeats) { + List> flat = new ArrayList<>(); + + Iterator> fields = object.fields(); + while (fields.hasNext()) { + Map.Entry field = fields.next(); + + String key = "[" + field.getKey() + "]"; + + if (field.getValue().isObject()) { + List> flatField = + flattenObject((ObjectNode) field.getValue(), arraysAsRepeats); + addAll(flat, flatField, key); + } else if (field.getValue().isArray()) { + List> flatField = + flattenArray((ArrayNode) field.getValue(), key, arraysAsRepeats); + addAll(flat, flatField, ""); + } else { + flat.add(new AbstractMap.SimpleEntry<>(key, field.getValue())); + } + } + + return flat; + } + + private static List> flattenArray( + ArrayNode array, String key, boolean arraysAsRepeats) { + List> flat = new ArrayList<>(); + + Iterator elements = array.elements(); + + int index = 0; + while (elements.hasNext()) { + JsonNode element = elements.next(); + + String indexKey = key + "[" + index + "]"; + + if (arraysAsRepeats) { + indexKey = key; + } + + if (element.isObject()) { + List> flatField = flattenObject((ObjectNode) element, arraysAsRepeats); + addAll(flat, flatField, indexKey); + } else if (element.isArray()) { + List> flatField = flattenArray((ArrayNode) element, "", arraysAsRepeats); + addAll(flat, flatField, indexKey); + } else { + flat.add(new AbstractMap.SimpleEntry<>(indexKey, element)); + } + + index++; + } + + return flat; + } + + private static void addAll( + List> target, List> source, String prefix) { + for (Map.Entry entry : source) { + Map.Entry entryToAdd = + new AbstractMap.SimpleEntry<>(prefix + entry.getKey(), entry.getValue()); + target.add(entryToAdd); + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/auth/rest/core/RequestOptions.java b/common/src/main/java/com/skyflow/generated/auth/rest/core/RequestOptions.java new file mode 100644 index 00000000..b70c02df --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/auth/rest/core/RequestOptions.java @@ -0,0 +1,101 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.auth.rest.core; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +public final class RequestOptions { + private final String token; + + private final Optional timeout; + + private final TimeUnit timeoutTimeUnit; + + private final Map headers; + + private final Map> headerSuppliers; + + private RequestOptions( + String token, + Optional timeout, + TimeUnit timeoutTimeUnit, + Map headers, + Map> headerSuppliers) { + this.token = token; + this.timeout = timeout; + this.timeoutTimeUnit = timeoutTimeUnit; + this.headers = headers; + this.headerSuppliers = headerSuppliers; + } + + public Optional getTimeout() { + return timeout; + } + + public TimeUnit getTimeoutTimeUnit() { + return timeoutTimeUnit; + } + + public Map getHeaders() { + Map headers = new HashMap<>(); + if (this.token != null) { + headers.put("Authorization", "Bearer " + this.token); + } + headers.putAll(this.headers); + this.headerSuppliers.forEach((key, supplier) -> { + headers.put(key, supplier.get()); + }); + return headers; + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private String token = null; + + private Optional timeout = Optional.empty(); + + private TimeUnit timeoutTimeUnit = TimeUnit.SECONDS; + + private final Map headers = new HashMap<>(); + + private final Map> headerSuppliers = new HashMap<>(); + + public Builder token(String token) { + this.token = token; + return this; + } + + 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 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 RequestOptions build() { + return new RequestOptions(token, timeout, timeoutTimeUnit, headers, headerSuppliers); + } + } +} diff --git a/common/src/main/java/com/skyflow/generated/auth/rest/core/ResponseBodyInputStream.java b/common/src/main/java/com/skyflow/generated/auth/rest/core/ResponseBodyInputStream.java new file mode 100644 index 00000000..1d0a9110 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/auth/rest/core/ResponseBodyInputStream.java @@ -0,0 +1,46 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.auth.rest.core; + +import okhttp3.Response; + +import java.io.FilterInputStream; +import java.io.IOException; + +/** + * 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/common/src/main/java/com/skyflow/generated/auth/rest/core/ResponseBodyReader.java b/common/src/main/java/com/skyflow/generated/auth/rest/core/ResponseBodyReader.java new file mode 100644 index 00000000..f4dfb17b --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/auth/rest/core/ResponseBodyReader.java @@ -0,0 +1,45 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.auth.rest.core; + +import okhttp3.Response; + +import java.io.FilterReader; +import java.io.IOException; + +/** + * 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/common/src/main/java/com/skyflow/generated/auth/rest/core/RetryInterceptor.java b/common/src/main/java/com/skyflow/generated/auth/rest/core/RetryInterceptor.java new file mode 100644 index 00000000..8344b974 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/auth/rest/core/RetryInterceptor.java @@ -0,0 +1,79 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.auth.rest.core; + +import okhttp3.Interceptor; +import okhttp3.Response; + +import java.io.IOException; +import java.time.Duration; +import java.util.Optional; +import java.util.Random; + +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 == 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/common/src/main/java/com/skyflow/generated/auth/rest/core/Stream.java b/common/src/main/java/com/skyflow/generated/auth/rest/core/Stream.java new file mode 100644 index 00000000..4a984faa --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/auth/rest/core/Stream.java @@ -0,0 +1,97 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.auth.rest.core; + +import java.io.Reader; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Scanner; + +/** + * The {@code Stream} class implements {@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 needed 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/common/src/main/java/com/skyflow/generated/auth/rest/core/Suppliers.java b/common/src/main/java/com/skyflow/generated/auth/rest/core/Suppliers.java new file mode 100644 index 00000000..d3ab5e53 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/auth/rest/core/Suppliers.java @@ -0,0 +1,23 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.auth.rest.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/common/src/main/java/com/skyflow/generated/auth/rest/errors/BadRequestError.java b/common/src/main/java/com/skyflow/generated/auth/rest/errors/BadRequestError.java new file mode 100644 index 00000000..4517200d --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/auth/rest/errors/BadRequestError.java @@ -0,0 +1,34 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.auth.rest.errors; + +import com.skyflow.generated.auth.rest.core.ApiClientApiException; +import okhttp3.Response; + +import java.util.Map; + +public final class BadRequestError extends ApiClientApiException { + /** + * The body of the response that triggered the exception. + */ + private final Map body; + + public BadRequestError(Map body) { + super("BadRequestError", 400, body); + this.body = body; + } + + public BadRequestError(Map body, Response rawResponse) { + super("BadRequestError", 400, body, rawResponse); + this.body = body; + } + + /** + * @return the body + */ + @Override + public Map body() { + return this.body; + } +} diff --git a/common/src/main/java/com/skyflow/generated/auth/rest/errors/HttpStatus.java b/common/src/main/java/com/skyflow/generated/auth/rest/errors/HttpStatus.java new file mode 100644 index 00000000..2e1c45d2 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/auth/rest/errors/HttpStatus.java @@ -0,0 +1,15 @@ +package com.skyflow.generated.auth.rest.errors; + +public enum HttpStatus { + BAD_REQUEST("Bad Request"); + + private final String httpStatus; + + HttpStatus(String httpStatus) { + this.httpStatus = httpStatus; + } + + public String getHttpStatus() { + return httpStatus; + } +} diff --git a/common/src/main/java/com/skyflow/generated/auth/rest/errors/NotFoundError.java b/common/src/main/java/com/skyflow/generated/auth/rest/errors/NotFoundError.java new file mode 100644 index 00000000..60a1771c --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/auth/rest/errors/NotFoundError.java @@ -0,0 +1,34 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.auth.rest.errors; + +import com.skyflow.generated.auth.rest.core.ApiClientApiException; +import okhttp3.Response; + +import java.util.Map; + +public final class NotFoundError extends ApiClientApiException { + /** + * The body of the response that triggered the exception. + */ + private final Map body; + + public NotFoundError(Map body) { + super("NotFoundError", 404, body); + this.body = body; + } + + public NotFoundError(Map body, Response rawResponse) { + super("NotFoundError", 404, body, rawResponse); + this.body = body; + } + + /** + * @return the body + */ + @Override + public Map body() { + return this.body; + } +} diff --git a/common/src/main/java/com/skyflow/generated/auth/rest/errors/UnauthorizedError.java b/common/src/main/java/com/skyflow/generated/auth/rest/errors/UnauthorizedError.java new file mode 100644 index 00000000..42f1c624 --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/auth/rest/errors/UnauthorizedError.java @@ -0,0 +1,34 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.auth.rest.errors; + +import com.skyflow.generated.auth.rest.core.ApiClientApiException; +import okhttp3.Response; + +import java.util.Map; + +public final class UnauthorizedError extends ApiClientApiException { + /** + * The body of the response that triggered the exception. + */ + private final Map body; + + public UnauthorizedError(Map body) { + super("UnauthorizedError", 401, body); + this.body = body; + } + + public UnauthorizedError(Map body, Response rawResponse) { + super("UnauthorizedError", 401, body, rawResponse); + this.body = body; + } + + /** + * @return the body + */ + @Override + public Map body() { + return this.body; + } +} diff --git a/src/main/java/com/skyflow/generated/rest/resources/authentication/AsyncAuthenticationClient.java b/common/src/main/java/com/skyflow/generated/auth/rest/resources/authentication/AsyncAuthenticationClient.java similarity index 84% rename from src/main/java/com/skyflow/generated/rest/resources/authentication/AsyncAuthenticationClient.java rename to common/src/main/java/com/skyflow/generated/auth/rest/resources/authentication/AsyncAuthenticationClient.java index 43ffab73..c742b239 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/authentication/AsyncAuthenticationClient.java +++ b/common/src/main/java/com/skyflow/generated/auth/rest/resources/authentication/AsyncAuthenticationClient.java @@ -1,12 +1,13 @@ /** * This file was auto-generated by Fern from our API Definition. */ -package com.skyflow.generated.rest.resources.authentication; +package com.skyflow.generated.auth.rest.resources.authentication; + +import com.skyflow.generated.auth.rest.core.ClientOptions; +import com.skyflow.generated.auth.rest.core.RequestOptions; +import com.skyflow.generated.auth.rest.resources.authentication.requests.V1GetAuthTokenRequest; +import com.skyflow.generated.auth.rest.types.V1GetAuthTokenResponse; -import com.skyflow.generated.rest.core.ClientOptions; -import com.skyflow.generated.rest.core.RequestOptions; -import com.skyflow.generated.rest.resources.authentication.requests.V1GetAuthTokenRequest; -import com.skyflow.generated.rest.types.V1GetAuthTokenResponse; import java.util.concurrent.CompletableFuture; public class AsyncAuthenticationClient { diff --git a/src/main/java/com/skyflow/generated/rest/resources/authentication/AsyncRawAuthenticationClient.java b/common/src/main/java/com/skyflow/generated/auth/rest/resources/authentication/AsyncRawAuthenticationClient.java similarity index 82% rename from src/main/java/com/skyflow/generated/rest/resources/authentication/AsyncRawAuthenticationClient.java rename to common/src/main/java/com/skyflow/generated/auth/rest/resources/authentication/AsyncRawAuthenticationClient.java index eca4ab90..56a47dc0 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/authentication/AsyncRawAuthenticationClient.java +++ b/common/src/main/java/com/skyflow/generated/auth/rest/resources/authentication/AsyncRawAuthenticationClient.java @@ -1,33 +1,22 @@ /** * This file was auto-generated by Fern from our API Definition. */ -package com.skyflow.generated.rest.resources.authentication; +package com.skyflow.generated.auth.rest.resources.authentication; import com.fasterxml.jackson.core.JsonProcessingException; -import com.skyflow.generated.rest.core.ApiClientApiException; -import com.skyflow.generated.rest.core.ApiClientException; -import com.skyflow.generated.rest.core.ApiClientHttpResponse; -import com.skyflow.generated.rest.core.ClientOptions; -import com.skyflow.generated.rest.core.MediaTypes; -import com.skyflow.generated.rest.core.ObjectMappers; -import com.skyflow.generated.rest.core.RequestOptions; -import com.skyflow.generated.rest.errors.BadRequestError; -import com.skyflow.generated.rest.errors.NotFoundError; -import com.skyflow.generated.rest.errors.UnauthorizedError; -import com.skyflow.generated.rest.resources.authentication.requests.V1GetAuthTokenRequest; -import com.skyflow.generated.rest.types.V1GetAuthTokenResponse; +import com.fasterxml.jackson.core.type.TypeReference; +import com.skyflow.generated.auth.rest.core.*; +import com.skyflow.generated.auth.rest.errors.BadRequestError; +import com.skyflow.generated.auth.rest.errors.NotFoundError; +import com.skyflow.generated.auth.rest.errors.UnauthorizedError; +import com.skyflow.generated.auth.rest.resources.authentication.requests.V1GetAuthTokenRequest; +import com.skyflow.generated.auth.rest.types.V1GetAuthTokenResponse; +import okhttp3.*; +import org.jetbrains.annotations.NotNull; + import java.io.IOException; +import java.util.Map; import java.util.concurrent.CompletableFuture; -import okhttp3.Call; -import okhttp3.Callback; -import okhttp3.Headers; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; -import okhttp3.ResponseBody; -import org.jetbrains.annotations.NotNull; public class AsyncRawAuthenticationClient { protected final ClientOptions clientOptions; @@ -88,17 +77,20 @@ public void onResponse(@NotNull Call call, @NotNull Response response) throws IO switch (response.code()) { case 400: future.completeExceptionally(new BadRequestError( - ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class), + ObjectMappers.JSON_MAPPER.readValue( + responseBodyString, new TypeReference>() {}), response)); return; case 401: future.completeExceptionally(new UnauthorizedError( - ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class), + ObjectMappers.JSON_MAPPER.readValue( + responseBodyString, new TypeReference>() {}), response)); return; case 404: future.completeExceptionally(new NotFoundError( - ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class), + ObjectMappers.JSON_MAPPER.readValue( + responseBodyString, new TypeReference>() {}), response)); return; } diff --git a/src/main/java/com/skyflow/generated/rest/resources/authentication/AuthenticationClient.java b/common/src/main/java/com/skyflow/generated/auth/rest/resources/authentication/AuthenticationClient.java similarity index 82% rename from src/main/java/com/skyflow/generated/rest/resources/authentication/AuthenticationClient.java rename to common/src/main/java/com/skyflow/generated/auth/rest/resources/authentication/AuthenticationClient.java index 662bfb3d..a0ba860a 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/authentication/AuthenticationClient.java +++ b/common/src/main/java/com/skyflow/generated/auth/rest/resources/authentication/AuthenticationClient.java @@ -1,12 +1,12 @@ /** * This file was auto-generated by Fern from our API Definition. */ -package com.skyflow.generated.rest.resources.authentication; +package com.skyflow.generated.auth.rest.resources.authentication; -import com.skyflow.generated.rest.core.ClientOptions; -import com.skyflow.generated.rest.core.RequestOptions; -import com.skyflow.generated.rest.resources.authentication.requests.V1GetAuthTokenRequest; -import com.skyflow.generated.rest.types.V1GetAuthTokenResponse; +import com.skyflow.generated.auth.rest.core.ClientOptions; +import com.skyflow.generated.auth.rest.core.RequestOptions; +import com.skyflow.generated.auth.rest.resources.authentication.requests.V1GetAuthTokenRequest; +import com.skyflow.generated.auth.rest.types.V1GetAuthTokenResponse; public class AuthenticationClient { protected final ClientOptions clientOptions; diff --git a/src/main/java/com/skyflow/generated/rest/resources/authentication/RawAuthenticationClient.java b/common/src/main/java/com/skyflow/generated/auth/rest/resources/authentication/RawAuthenticationClient.java similarity index 78% rename from src/main/java/com/skyflow/generated/rest/resources/authentication/RawAuthenticationClient.java rename to common/src/main/java/com/skyflow/generated/auth/rest/resources/authentication/RawAuthenticationClient.java index 3d242eff..6c52c048 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/authentication/RawAuthenticationClient.java +++ b/common/src/main/java/com/skyflow/generated/auth/rest/resources/authentication/RawAuthenticationClient.java @@ -1,29 +1,20 @@ /** * This file was auto-generated by Fern from our API Definition. */ -package com.skyflow.generated.rest.resources.authentication; +package com.skyflow.generated.auth.rest.resources.authentication; import com.fasterxml.jackson.core.JsonProcessingException; -import com.skyflow.generated.rest.core.ApiClientApiException; -import com.skyflow.generated.rest.core.ApiClientException; -import com.skyflow.generated.rest.core.ApiClientHttpResponse; -import com.skyflow.generated.rest.core.ClientOptions; -import com.skyflow.generated.rest.core.MediaTypes; -import com.skyflow.generated.rest.core.ObjectMappers; -import com.skyflow.generated.rest.core.RequestOptions; -import com.skyflow.generated.rest.errors.BadRequestError; -import com.skyflow.generated.rest.errors.NotFoundError; -import com.skyflow.generated.rest.errors.UnauthorizedError; -import com.skyflow.generated.rest.resources.authentication.requests.V1GetAuthTokenRequest; -import com.skyflow.generated.rest.types.V1GetAuthTokenResponse; +import com.fasterxml.jackson.core.type.TypeReference; +import com.skyflow.generated.auth.rest.core.*; +import com.skyflow.generated.auth.rest.errors.BadRequestError; +import com.skyflow.generated.auth.rest.errors.NotFoundError; +import com.skyflow.generated.auth.rest.errors.UnauthorizedError; +import com.skyflow.generated.auth.rest.resources.authentication.requests.V1GetAuthTokenRequest; +import com.skyflow.generated.auth.rest.types.V1GetAuthTokenResponse; +import okhttp3.*; + 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; +import java.util.Map; public class RawAuthenticationClient { protected final ClientOptions clientOptions; @@ -79,13 +70,19 @@ public ApiClientHttpResponse authenticationServiceGetAut switch (response.code()) { case 400: throw new BadRequestError( - ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class), response); + ObjectMappers.JSON_MAPPER.readValue( + responseBodyString, new TypeReference>() {}), + response); case 401: throw new UnauthorizedError( - ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class), response); + ObjectMappers.JSON_MAPPER.readValue( + responseBodyString, new TypeReference>() {}), + response); case 404: throw new NotFoundError( - ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class), response); + ObjectMappers.JSON_MAPPER.readValue( + responseBodyString, new TypeReference>() {}), + response); } } catch (JsonProcessingException ignored) { // unable to map error response, throwing generic error diff --git a/src/main/java/com/skyflow/generated/rest/resources/authentication/requests/V1GetAuthTokenRequest.java b/common/src/main/java/com/skyflow/generated/auth/rest/resources/authentication/requests/V1GetAuthTokenRequest.java similarity index 96% rename from src/main/java/com/skyflow/generated/rest/resources/authentication/requests/V1GetAuthTokenRequest.java rename to common/src/main/java/com/skyflow/generated/auth/rest/resources/authentication/requests/V1GetAuthTokenRequest.java index 182952bc..382c5431 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/authentication/requests/V1GetAuthTokenRequest.java +++ b/common/src/main/java/com/skyflow/generated/auth/rest/resources/authentication/requests/V1GetAuthTokenRequest.java @@ -1,22 +1,17 @@ /** * This file was auto-generated by Fern from our API Definition. */ -package com.skyflow.generated.rest.resources.authentication.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; +package com.skyflow.generated.auth.rest.resources.authentication.requests; + +import com.fasterxml.jackson.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.skyflow.generated.rest.core.ObjectMappers; +import com.skyflow.generated.auth.rest.core.ObjectMappers; +import org.jetbrains.annotations.NotNull; + import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.Optional; -import org.jetbrains.annotations.NotNull; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = V1GetAuthTokenRequest.Builder.class) diff --git a/common/src/main/java/com/skyflow/generated/auth/rest/types/V1GetAuthTokenResponse.java b/common/src/main/java/com/skyflow/generated/auth/rest/types/V1GetAuthTokenResponse.java new file mode 100644 index 00000000..3db2f22b --- /dev/null +++ b/common/src/main/java/com/skyflow/generated/auth/rest/types/V1GetAuthTokenResponse.java @@ -0,0 +1,125 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.auth.rest.types; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.auth.rest.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 = V1GetAuthTokenResponse.Builder.class) +public final class V1GetAuthTokenResponse { + private final Optional accessToken; + + private final Optional tokenType; + + private final Map additionalProperties; + + private V1GetAuthTokenResponse( + Optional accessToken, Optional tokenType, Map additionalProperties) { + this.accessToken = accessToken; + this.tokenType = tokenType; + this.additionalProperties = additionalProperties; + } + + /** + * @return AccessToken. + */ + @JsonProperty("accessToken") + public Optional getAccessToken() { + return accessToken; + } + + /** + * @return TokenType : Bearer. + */ + @JsonProperty("tokenType") + public Optional getTokenType() { + return tokenType; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V1GetAuthTokenResponse && equalTo((V1GetAuthTokenResponse) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(V1GetAuthTokenResponse other) { + return accessToken.equals(other.accessToken) && tokenType.equals(other.tokenType); + } + + @Override + public int hashCode() { + return Objects.hash(this.accessToken, this.tokenType); + } + + @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 accessToken = Optional.empty(); + + private Optional tokenType = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(V1GetAuthTokenResponse other) { + accessToken(other.getAccessToken()); + tokenType(other.getTokenType()); + return this; + } + + /** + *

AccessToken.

+ */ + @JsonSetter(value = "accessToken", nulls = Nulls.SKIP) + public Builder accessToken(Optional accessToken) { + this.accessToken = accessToken; + return this; + } + + public Builder accessToken(String accessToken) { + this.accessToken = Optional.ofNullable(accessToken); + return this; + } + + /** + *

TokenType : Bearer.

+ */ + @JsonSetter(value = "tokenType", nulls = Nulls.SKIP) + public Builder tokenType(Optional tokenType) { + this.tokenType = tokenType; + return this; + } + + public Builder tokenType(String tokenType) { + this.tokenType = Optional.ofNullable(tokenType); + return this; + } + + public V1GetAuthTokenResponse build() { + return new V1GetAuthTokenResponse(accessToken, tokenType, additionalProperties); + } + } +} diff --git a/src/main/java/com/skyflow/logs/ErrorLogs.java b/common/src/main/java/com/skyflow/logs/ErrorLogs.java similarity index 68% rename from src/main/java/com/skyflow/logs/ErrorLogs.java rename to common/src/main/java/com/skyflow/logs/ErrorLogs.java index 47866efc..1a405bb5 100644 --- a/src/main/java/com/skyflow/logs/ErrorLogs.java +++ b/common/src/main/java/com/skyflow/logs/ErrorLogs.java @@ -8,6 +8,7 @@ public enum ErrorLogs { EMPTY_VAULT_ID("Invalid vault config. Vault ID can not be empty."), CLUSTER_ID_IS_REQUIRED("Invalid vault config. Cluster ID is required."), EMPTY_CLUSTER_ID("Invalid vault config. Cluster ID can not be empty."), + EITHER_VAULT_URL_OR_CLUSTER_ID_REQUIRED("Invalid vault config. At least one of \"clusterId\" or \"vaultURL\" must be provided."), CONNECTION_CONFIG_EXISTS("Connection config with connection ID %s1 already exists."), CONNECTION_CONFIG_DOES_NOT_EXIST("Connection config with connection ID %s1 doesn't exist."), CONNECTION_ID_IS_REQUIRED("Invalid connection config. Connection ID is required."), @@ -27,6 +28,8 @@ public enum ErrorLogs { EMPTY_OR_NULL_CONTEXT("Invalid credentials. Context can not be empty."), INVALID_CONTEXT_TYPE("Invalid credentials. Context must be a String or Map."), INVALID_CONTEXT_MAP_KEY("Invalid credentials. Context map key '%s1' contains invalid characters."), + EMPTY_VAULT_URL("Invalid vault config. Vault URL can not be empty."), + INVALID_VAULT_URL_FORMAT("Invalid vault config. Vault URL format is incorrect"), // Bearer token generation INVALID_BEARER_TOKEN("Bearer token is invalid or expired."), @@ -50,12 +53,23 @@ public enum ErrorLogs { TABLE_IS_REQUIRED("Invalid %s1 request. Table is required."), EMPTY_TABLE_NAME("Invalid %s1 request. Table name can not be empty."), VALUES_IS_REQUIRED("Invalid %s1 request. Values are required."), + EMPTY_VALUES("Invalid %s1 request. Values can not be empty."), + INSERT_REQUEST_NULL("Invalid %s1 request. Insert request can not be null."), + RECORDS_IS_REQUIRED("Invalid %s1 request. Records are required."), + EMPTY_RECORDS("Invalid %s1 request. Records can not be empty."), + INVALID_RECORD("Invalid %s1 request. Invalid record. Specify a valid record."), + RECORD_SIZE_EXCEED("Maximum number of records exceeded. The limit is 10000."), + TOKENS_SIZE_EXCEED("Maximum number of tokens exceeded. The limit is 10000."), + EMPTY_OR_NULL_VALUE_IN_VALUES("Invalid %s1 request. Value can not be null or empty in values for key \"%s2\"."), EMPTY_OR_NULL_KEY_IN_VALUES("Invalid %s1 request. Key can not be null or empty in values"), EMPTY_UPSERT("Invalid %s1 request. Upsert can not be empty."), + INVALID_UPSERT_UPDATE_TYPE("Invalid %s1 request. Upsert updateType must be either UPDATE or REPLACE."), + EMPTY_UPSERT_VALUES("Invalid %s1 request. Upsert values can not be empty."), HOMOGENOUS_NOT_SUPPORTED_WITH_UPSERT("Invalid %s1 request. Homogenous is not supported when upsert is passed."), TOKENS_NOT_ALLOWED_WITH_TOKEN_MODE_DISABLE("Invalid %s1 request. Tokens are not allowed when tokenMode is DISABLE."), TOKENS_REQUIRED_WITH_TOKEN_MODE("Invalid %s1 request. Tokens are required when tokenMode is %s2."), EMPTY_TOKENS("Invalid %s1 request. Tokens can not be empty."), + EMPTY_OR_NULL_VALUE_IN_TOKENS("Invalid %s1 request. Value can not be null or empty in tokens for key \"%s2\"."), EMPTY_OR_NULL_KEY_IN_TOKENS("Invalid %s1 request. Key can not be null or empty in tokens."), INSUFFICIENT_TOKENS_PASSED_FOR_TOKEN_MODE_ENABLE_STRICT("Invalid %s1 request. For tokenMode as ENABLE_STRICT, tokens should be passed for all fields."), MISMATCH_OF_FIELDS_AND_TOKENS("Invalid %s1 request. Keys for values and tokens are not matching."), @@ -65,6 +79,15 @@ public enum ErrorLogs { EMPTY_OR_NULL_TOKEN_IN_DETOKENIZE_DATA("Invalid %s1 request. Token can not be null or empty in detokenize data at index %s2."), REDACTION_IS_REQUIRED("Invalid %s1 request. Redaction is required."), DETOKENIZE_REQUEST_REJECTED("Detokenize request resulted in failure."), + DETOKENIZE_REQUEST_NULL("Invalid %s1 request. Detokenize request can not be null."), + + NULL_TOKEN_REDACTION_GROUP_OBJECT("Invalid %s1 request. Token Redaction group object can not be null or empty."), + + NULL_REDACTION_IN_TOKEN_GROUP("Invalid %s1 request. Redaction can not be null in token redaction group"), + + NULL_TOKEN_GROUP_NAME_IN_TOKEN_GROUP("Invalid %s1 request. Token group name can not be null in token redaction group"), + + EMPTY_OR_NULL_REDACTION_IN_TOKEN_GROUP("Invalid %s1 request. Redaction can not be null or empty in token redaction group"), IDS_IS_REQUIRED("Invalid %s1 request. Ids are required."), EMPTY_IDS("Invalid %s1 request. Ids can not be empty."), EMPTY_OR_NULL_ID_IN_IDS("Invalid %s1 request. Id can not be null or empty in ids at index %s2."), @@ -82,6 +105,15 @@ public enum ErrorLogs { EMPTY_COLUMN_VALUES("Invalid %s1 request. Column values can not be empty."), EMPTY_OR_NULL_COLUMN_VALUE_IN_COLUMN_VALUES("Invalid %s1 request. Column value can not by null or empty in column values at index %s2."), GET_REQUEST_REJECTED("Get request resulted in failure."), + NEITHER_IDS_NOR_UNIQUE_VALUES_PASSED("Invalid %s1 request. Neither ids nor unique values are passed."), + BOTH_IDS_AND_UNIQUE_VALUES_PASSED("Invalid %s1 request. Both ids and unique values are passed."), + EMPTY_UNIQUE_VALUES("Invalid %s1 request. Unique values can not be empty."), + EMPTY_OR_NULL_UNIQUE_VALUE_IN_UNIQUE_VALUES("Invalid %s1 request. Unique value can not be null or empty in unique values at index %s2."), + NULL_COLUMN_REDACTION_OBJECT("Invalid %s1 request. Column redaction object can not be null."), + NULL_COLUMN_NAME_IN_COLUMN_REDACTION("Invalid %s1 request. Column name can not be null or empty in column redaction."), + EMPTY_OR_NULL_REDACTION_IN_COLUMN_REDACTION("Invalid %s1 request. Redaction can not be null or empty in column redaction."), + BOTH_SINGLE_TABLE_FIELDS_AND_RECORDS_PASSED("Invalid %s1 request. Both single-table lookup fields and records are passed."), + NULL_GET_RECORD_REQUEST_OBJECT("Invalid %s1 request. Record in records list can not be null."), DATA_IS_REQUIRED("Invalid %s1 request. Data is required."), EMPTY_DATA("Invalid %s1 request. Data can not be empty."), SKYFLOW_ID_IS_REQUIRED("Invalid %s1 request. Skyflow Id is required."), @@ -94,7 +126,21 @@ public enum ErrorLogs { COLUMN_VALUES_IS_REQUIRED_TOKENIZE("Invalid %s1 request. ColumnValues are required."), EMPTY_OR_NULL_COLUMN_GROUP_IN_COLUMN_VALUES("Invalid %s1 request. Column group can not be null or empty in column values at index %s2."), TOKENIZE_REQUEST_REJECTED("Tokenize request resulted in failure."), + TOKENIZE_REQUEST_NULL("Invalid %s1 request. Tokenize request can not be null."), + EMPTY_TOKENIZE_DATA("Invalid %s1 request. Tokenize data can not be empty."), + TOKENIZE_RECORD_NULL("Invalid %s1 request. TokenizeRecord in list can not be null."), + EMPTY_VALUE_IN_TOKENIZE_RECORD("Invalid %s1 request. Value in TokenizeRecord can not be null or empty."), + EMPTY_TOKEN_GROUP_NAMES_IN_TOKENIZE_RECORD("Invalid %s1 request. TokenGroupNames in TokenizeRecord can not be null or empty."), + EMPTY_TOKEN_GROUP_NAME_IN_TOKENIZE_RECORD("Invalid %s1 request. Token group name in TokenizeRecord can not be null or empty at index %s2."), + TOKENIZE_DATA_SIZE_EXCEED("Maximum number of tokenize records exceeded. The limit is 10000."), + MISSING_INDEX_IN_BULK_TOKENIZE_RECORD("Invalid %s1 request. Index in BulkTokenizeRequestRecord can not be null at position %s2."), + DUPLICATE_INDEX_IN_BULK_TOKENIZE_RECORD("Invalid %s1 request. Duplicate index %s2 in BulkTokenizeRequestRecord."), DELETE_REQUEST_REJECTED("Delete request resulted in failure."), + DELETE_TOKENS_REQUEST_NULL("Invalid %s1 request. DeleteTokens request can not be null."), + EMPTY_DELETE_TOKENS_DATA("Invalid %s1 request. Delete tokens data can not be empty."), + EMPTY_OR_NULL_TOKEN_IN_DELETE_TOKENS_DATA("Invalid %s1 request. Token can not be null or empty in delete tokens data at index %s2."), + DELETE_TOKENS_SIZE_EXCEED("Maximum number of tokens exceeded. The limit is 10000."), + DELETE_TOKENS_REQUEST_REJECTED("DeleteTokens request resulted in failure."), // invoke connection interface INVOKE_CONNECTION_INVALID_CONNECTION_URL("Invalid %s1 request. Connection URL is not a valid URL."), @@ -117,7 +163,6 @@ public enum ErrorLogs { MISSING_FILE_SOURCE_IN_UPLOAD_FILE("Invalid %s1 request. Provide exactly one of filePath, base64, or fileObject."), UPLOAD_FILE_REQUEST_REJECTED("Upload file request resulted in failure."), - // detect interface INVALID_TEXT_IN_DEIDENTIFY("Invalid %s1 request. The text field is required and must be a non-empty string. Specify a valid text."), DEIDENTIFY_TEXT_REQUEST_REJECTED("DeIdentify text request resulted in failure."), @@ -134,8 +179,14 @@ public enum ErrorLogs { OUTPUT_DIRECTORY_NOT_FOUND("Invalid %s1 request. The output directory does not exist. Please specify a valid output directory."), INVALID_PERMISSIONS_FOR_OUTPUT_DIRECTORY("Invalid %s1 request. The output directory is not writable. Please check the permissions or specify a valid output directory."), EMPTY_FILE_AND_FILE_PATH_IN_DEIDENTIFY_FILE("Invalid %s1 request. The file and file path fields are both empty. Specify a valid file object or file path."), - ; + UNEXPECTED_ERROR_DURING_BATCH_PROCESSING("Unexpected error occurred during batch processing. Error: %s1"), + + PROCESSING_ERROR_RESPONSE("Processing error response."), + TABLE_SPECIFIED_AT_BOTH_PLACE("Invalid %s1 request. Table name cannot be specified at both the request and record levels. Please specify the table name at only one place."), + TABLE_NOT_SPECIFIED_AT_BOTH_PLACE("Invalid %s1 request. Table name is missing. Table name should be specified at one place either at the request level or record level. Please specify the table name at one place."), + UPSERT_TABLE_REQUEST_AT_RECORD_LEVEL("Invalid %s1 request. Table name should be present at each record level when upsert is present at record level."), + UPSERT_TABLE_REQUEST_AT_REQUEST_LEVEL("Invalid %s1 request. Upsert should be present at each record level when table name is present at record level."); private final String log; ErrorLogs(String log) { diff --git a/src/main/java/com/skyflow/logs/InfoLogs.java b/common/src/main/java/com/skyflow/logs/InfoLogs.java similarity index 92% rename from src/main/java/com/skyflow/logs/InfoLogs.java rename to common/src/main/java/com/skyflow/logs/InfoLogs.java index e747bfa2..6d6a8211 100644 --- a/src/main/java/com/skyflow/logs/InfoLogs.java +++ b/common/src/main/java/com/skyflow/logs/InfoLogs.java @@ -14,12 +14,14 @@ public enum InfoLogs { // Bearer token generation EMPTY_BEARER_TOKEN("Bearer token is empty."), - BEARER_TOKEN_EXPIRED("Bearer token is invalid or expired."), + BEARER_TOKEN_EXPIRED("Bearer token is expired."), GET_BEARER_TOKEN_TRIGGERED("getBearerToken method triggered."), GET_BEARER_TOKEN_SUCCESS("Bearer token generated."), GET_SIGNED_DATA_TOKENS_TRIGGERED("getSignedDataTokens method triggered."), GET_SIGNED_DATA_TOKEN_SUCCESS("Signed data tokens generated."), REUSE_BEARER_TOKEN("Reusing bearer token."), + USE_CLIENT_PROVIDED_BEARER_TOKEN("Using bearer token provided by client."), + USE_API_KEY("Using api key."), REUSE_API_KEY("Reusing api key."), GENERATE_BEARER_TOKEN_FROM_CREDENTIALS_TRIGGERED("generateBearerTokenFromCredentials method triggered."), GENERATE_BEARER_TOKEN_FROM_CREDENTIALS_STRING_TRIGGERED("generateBearerTokenFromCredentialString method triggered."), @@ -57,6 +59,12 @@ public enum InfoLogs { DELETE_REQUEST_RESOLVED("Delete request resolved."), DELETE_SUCCESS("Data deleted."), + // Delete Tokens interface + DELETE_TOKENS_TRIGGERED("DeleteTokens method triggered."), + VALIDATE_DELETE_TOKENS_REQUEST("Validating delete tokens request."), + DELETE_TOKENS_REQUEST_RESOLVED("DeleteTokens request resolved."), + DELETE_TOKENS_SUCCESS("Tokens deleted."), + // Query interface QUERY_TRIGGERED("Query method triggered."), VALIDATING_QUERY_REQUEST("Validating query request."), @@ -69,11 +77,6 @@ public enum InfoLogs { TOKENIZE_REQUEST_RESOLVED("Tokenize request resolved."), TOKENIZE_SUCCESS("Data tokenized."), - // File upload interface - FILE_UPLOAD_TRIGGERED("File upload method triggered."), - VALIDATING_FILE_UPLOAD_REQUEST("Validating file upload request."), - FILE_UPLOAD_REQUEST_RESOLVED("File upload request resolved."), - FILE_UPLOAD_SUCCESS("File uploaded successfully."), // Invoke connection interface INVOKE_CONNECTION_TRIGGERED("Invoke connection method triggered."), @@ -96,6 +99,14 @@ public enum InfoLogs { VALIDATE_GET_DETECT_RUN_REQUEST("Validating get detect run request."), REIDENTIFY_TEXT_SUCCESS("Text data re-identified."), + // File upload interface + FILE_UPLOAD_TRIGGERED("File upload method triggered."), + VALIDATING_FILE_UPLOAD_REQUEST("Validating file upload request."), + FILE_UPLOAD_REQUEST_RESOLVED("File upload request resolved."), + FILE_UPLOAD_SUCCESS("File uploaded successfully."), + + PROCESSING_BATCHES("Processing batch"), + // Deprecation warnings — v2 backward compat DEPRECATED_SKYFLOW_ID_KEY("[DEPRECATED] Response key 'skyflow_id' is deprecated and will be removed in an upcoming release. Use 'skyflowId' instead."), DEPRECATED_SKYFLOW_ID_REQUEST_KEY("[DEPRECATED] Request data key 'skyflow_id' is deprecated and will be removed in an upcoming release. Use 'skyflowId' instead."), @@ -104,8 +115,8 @@ public enum InfoLogs { DEPRECATED_UPDATE_LOG_LEVEL("[DEPRECATED] Method 'updateLogLevel()' is deprecated and will be removed in an upcoming release. Use 'setLogLevel()' instead."), DEPRECATED_CREDENTIAL_CLIENT_ID("[DEPRECATED] Credential field 'clientID' is deprecated and will be removed in an upcoming release. Use 'clientId' instead."), DEPRECATED_CREDENTIAL_KEY_ID("[DEPRECATED] Credential field 'keyID' is deprecated and will be removed in an upcoming release. Use 'keyId' instead."), - DEPRECATED_CREDENTIAL_TOKEN_URI("[DEPRECATED] Credential field 'tokenURI' is deprecated and will be removed in an upcoming release. Use 'tokenUri' instead."); - + DEPRECATED_CREDENTIAL_TOKEN_URI("[DEPRECATED] Credential field 'tokenURI' is deprecated and will be removed in an upcoming release. Use 'tokenUri' instead.") + ; private final String log; diff --git a/common/src/main/java/com/skyflow/logs/WarningLogs.java b/common/src/main/java/com/skyflow/logs/WarningLogs.java new file mode 100644 index 00000000..1eb1bcd8 --- /dev/null +++ b/common/src/main/java/com/skyflow/logs/WarningLogs.java @@ -0,0 +1,23 @@ +package com.skyflow.logs; + +public enum WarningLogs { + INVALID_BATCH_SIZE_PROVIDED("Invalid value for batch size provided, switching to default value."), + INVALID_CONCURRENCY_LIMIT_PROVIDED("Invalid value for concurrency limit provided, switching to default value."), + BATCH_SIZE_EXCEEDS_MAX_LIMIT("Provided batch size exceeds the maximum limit, switching to max limit."), + CONCURRENCY_EXCEEDS_MAX_LIMIT("Provided concurrency limit exceeds the maximum limit, switching to max limit."), + EMPTY_DELETE_TOKENS_RESPONSE("DeleteTokens response did not include any token results."), + INCOMPLETE_DELETE_TOKENS_RESPONSE("DeleteTokens response did not account for all requested tokens."), + EMPTY_TOKENIZE_RESPONSE("Tokenize response did not include any record results."), + INCOMPLETE_TOKENIZE_RESPONSE("Tokenize response did not account for all requested records.") + ; + + private final String log; + + WarningLogs(String log) { + this.log = log; + } + + public final String getLog() { + return log; + } +} diff --git a/src/main/java/com/skyflow/serviceaccount/util/BearerToken.java b/common/src/main/java/com/skyflow/serviceaccount/util/BearerToken.java similarity index 91% rename from src/main/java/com/skyflow/serviceaccount/util/BearerToken.java rename to common/src/main/java/com/skyflow/serviceaccount/util/BearerToken.java index ad7cae30..60c07f6d 100644 --- a/src/main/java/com/skyflow/serviceaccount/util/BearerToken.java +++ b/common/src/main/java/com/skyflow/serviceaccount/util/BearerToken.java @@ -4,16 +4,16 @@ import com.skyflow.errors.ErrorCode; import com.skyflow.errors.ErrorMessage; import com.skyflow.errors.SkyflowException; -import com.skyflow.generated.rest.ApiClient; -import com.skyflow.generated.rest.ApiClientBuilder; -import com.skyflow.generated.rest.core.ApiClientApiException; -import com.skyflow.generated.rest.resources.authentication.AuthenticationClient; -import com.skyflow.generated.rest.resources.authentication.requests.V1GetAuthTokenRequest; -import com.skyflow.generated.rest.types.V1GetAuthTokenResponse; +import com.skyflow.generated.auth.rest.ApiClient; +import com.skyflow.generated.auth.rest.ApiClientBuilder; +import com.skyflow.generated.auth.rest.core.ApiClientApiException; +import com.skyflow.generated.auth.rest.resources.authentication.AuthenticationClient; +import com.skyflow.generated.auth.rest.resources.authentication.requests.V1GetAuthTokenRequest; +import com.skyflow.generated.auth.rest.types.V1GetAuthTokenResponse; import com.skyflow.logs.ErrorLogs; import com.skyflow.logs.InfoLogs; -import com.skyflow.utils.Constants; -import com.skyflow.utils.Utils; +import com.skyflow.utils.BaseConstants; +import com.skyflow.utils.BaseUtils; import com.skyflow.utils.logger.LogUtil; import io.jsonwebtoken.Jwts; @@ -65,13 +65,13 @@ private static V1GetAuthTokenResponse generateBearerTokenFromCredentials( } finally { try { reader.close(); } catch (IOException ignored) {} } - } catch (JsonSyntaxException e) { + } catch (JsonSyntaxException | IllegalStateException e) { LogUtil.printErrorLog(ErrorLogs.INVALID_CREDENTIALS_FILE_FORMAT.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), Utils.parameterizedString( + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseUtils.parameterizedString( ErrorMessage.FileInvalidJson.getMessage(), credentialsFile.getPath())); } catch (FileNotFoundException e) { LogUtil.printErrorLog(ErrorLogs.CREDENTIALS_FILE_NOT_FOUND.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), Utils.parameterizedString( + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseUtils.parameterizedString( ErrorMessage.FileNotFound.getMessage(), credentialsFile.getPath())); } } @@ -87,7 +87,7 @@ private static V1GetAuthTokenResponse generateBearerTokenFromCredentialString( } JsonObject serviceAccountCredentials = JsonParser.parseString(credentials).getAsJsonObject(); return getBearerTokenFromCredentials(serviceAccountCredentials, context, roles); - } catch (JsonSyntaxException e) { + } catch (JsonSyntaxException | IllegalStateException e) { LogUtil.printErrorLog(ErrorLogs.INVALID_CREDENTIALS_STRING_FORMAT.getLog()); throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.CredentialsStringInvalidJson.getMessage()); @@ -141,17 +141,17 @@ private static V1GetAuthTokenResponse getBearerTokenFromCredentials( throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.MissingTokenUri.getMessage()); } - PrivateKey pvtKey = Utils.getPrivateKeyFromPem(privateKey.getAsString()); + PrivateKey pvtKey = BaseUtils.getPrivateKeyFromPem(privateKey.getAsString()); String signedUserJWT = getSignedToken( clientId.getAsString(), keyId.getAsString(), tokenUri.getAsString(), pvtKey, context ); - String basePath = Utils.getBaseURL(tokenUri.getAsString()); + String basePath = BaseUtils.getBaseURL(tokenUri.getAsString()); API_CLIENT_BUILDER.url(basePath); ApiClient apiClient = API_CLIENT_BUILDER.token("token").build(); AuthenticationClient authenticationApi = apiClient.authentication(); - V1GetAuthTokenRequest._FinalStage authTokenBuilder = V1GetAuthTokenRequest.builder().grantType(Constants.GRANT_TYPE).assertion(signedUserJWT); + V1GetAuthTokenRequest._FinalStage authTokenBuilder = V1GetAuthTokenRequest.builder().grantType(BaseConstants.GRANT_TYPE).assertion(signedUserJWT); if (roles != null) { String scopedRoles = getScopeUsingRoles(roles); diff --git a/src/main/java/com/skyflow/serviceaccount/util/SignedDataTokenResponse.java b/common/src/main/java/com/skyflow/serviceaccount/util/SignedDataTokenResponse.java similarity index 83% rename from src/main/java/com/skyflow/serviceaccount/util/SignedDataTokenResponse.java rename to common/src/main/java/com/skyflow/serviceaccount/util/SignedDataTokenResponse.java index 3d35b541..c4618ae6 100644 --- a/src/main/java/com/skyflow/serviceaccount/util/SignedDataTokenResponse.java +++ b/common/src/main/java/com/skyflow/serviceaccount/util/SignedDataTokenResponse.java @@ -1,10 +1,10 @@ package com.skyflow.serviceaccount.util; import com.google.gson.Gson; -import com.skyflow.utils.Constants; +import com.skyflow.utils.BaseConstants; public class SignedDataTokenResponse { - private static final String PREFIX = Constants.SIGNED_DATA_TOKEN_PREFIX; + private static final String PREFIX = BaseConstants.SIGNED_DATA_TOKEN_PREFIX; private final String token; private final String signedToken; diff --git a/src/main/java/com/skyflow/serviceaccount/util/SignedDataTokens.java b/common/src/main/java/com/skyflow/serviceaccount/util/SignedDataTokens.java similarity index 97% rename from src/main/java/com/skyflow/serviceaccount/util/SignedDataTokens.java rename to common/src/main/java/com/skyflow/serviceaccount/util/SignedDataTokens.java index b909e45b..630a0aee 100644 --- a/src/main/java/com/skyflow/serviceaccount/util/SignedDataTokens.java +++ b/common/src/main/java/com/skyflow/serviceaccount/util/SignedDataTokens.java @@ -9,7 +9,7 @@ import com.skyflow.errors.SkyflowException; import com.skyflow.logs.ErrorLogs; import com.skyflow.logs.InfoLogs; -import com.skyflow.utils.Utils; +import com.skyflow.utils.BaseUtils; import com.skyflow.utils.logger.LogUtil; import io.jsonwebtoken.Jwts; @@ -18,11 +18,7 @@ import java.io.FileReader; import java.io.IOException; import java.security.PrivateKey; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.Objects; +import java.util.*; public class SignedDataTokens { private final File credentialsFile; @@ -64,11 +60,11 @@ private static List generateSignedTokenFromCredentialsF } } catch (JsonSyntaxException e) { LogUtil.printErrorLog(ErrorLogs.INVALID_CREDENTIALS_FILE_FORMAT.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), Utils.parameterizedString( + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseUtils.parameterizedString( ErrorMessage.FileInvalidJson.getMessage(), credentialsFile.getPath())); } catch (FileNotFoundException e) { LogUtil.printErrorLog(ErrorLogs.CREDENTIALS_FILE_NOT_FOUND.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), Utils.parameterizedString( + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), BaseUtils.parameterizedString( ErrorMessage.FileNotFound.getMessage(), credentialsFile.getPath())); } return responseToken; @@ -129,7 +125,7 @@ private static List generateSignedTokensFromCredentials LogUtil.printErrorLog(ErrorLogs.KEY_ID_IS_REQUIRED.getLog()); throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.MissingKeyId.getMessage()); } - PrivateKey pvtKey = Utils.getPrivateKeyFromPem(privateKey.getAsString()); + PrivateKey pvtKey = BaseUtils.getPrivateKeyFromPem(privateKey.getAsString()); signedDataTokens = getSignedToken( clientId.getAsString(), keyId.getAsString(), pvtKey, dataTokens, timeToLive, context); } catch (RuntimeException e) { diff --git a/src/main/java/com/skyflow/serviceaccount/util/Token.java b/common/src/main/java/com/skyflow/serviceaccount/util/Token.java similarity index 100% rename from src/main/java/com/skyflow/serviceaccount/util/Token.java rename to common/src/main/java/com/skyflow/serviceaccount/util/Token.java diff --git a/common/src/main/java/com/skyflow/utils/BaseConstants.java b/common/src/main/java/com/skyflow/utils/BaseConstants.java new file mode 100644 index 00000000..1f05fe13 --- /dev/null +++ b/common/src/main/java/com/skyflow/utils/BaseConstants.java @@ -0,0 +1,33 @@ +package com.skyflow.utils; + +public class BaseConstants { + public static final String SDK_NAME= "Skyflow Java SDK "; + public static final String SDK_VERSION = "1.0.0"; + public static final String SDK_PREFIX = SDK_NAME + SDK_VERSION; + public static final String ORDER_ASCENDING = "ASCENDING"; + public static final String ENV_CREDENTIALS_KEY_NAME = "SKYFLOW_CREDENTIALS"; + public static final String SECURE_PROTOCOL = "https://"; + + public static final String V2_VAULT_DOMAIN = ".vault."; + public static final String V3_VAULT_DOMAIN = ".skyvault."; + public static final String DEV_DOMAIN = "skyflowapis.dev"; + public static final String STAGE_DOMAIN = "skyflowapis.tech"; + public static final String SANDBOX_DOMAIN = "skyflowapis-preview.com"; + public static final String PROD_DOMAIN = "skyflowapis.com"; + public static final String PKCS8_PRIVATE_HEADER = "-----BEGIN PRIVATE KEY-----"; + public static final String PKCS8_PRIVATE_FOOTER = "-----END PRIVATE KEY-----"; + public static final String GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer"; + public static final String SIGNED_DATA_TOKEN_PREFIX = "signed_token_"; + public static final String API_KEY_REGEX = "^sky-[a-zA-Z0-9]{5}-[a-fA-F0-9]{32}$"; + public static final String CONTEXT_KEY_REGEX = "^[a-zA-Z0-9_]+$"; + public static final String SDK_METRIC_NAME_VERSION = "sdk_name_version"; + public static final String SDK_METRIC_CLIENT_DEVICE_MODEL = "sdk_client_device_model"; + public static final String SDK_METRIC_CLIENT_OS_DETAILS = "sdk_client_os_details"; + public static final String SDK_METRIC_RUNTIME_DETAILS = "sdk_runtime_details"; + public static final String SDK_METRIC_RUNTIME_DETAILS_PREFIX = "Java@"; + public static final String SDK_AUTH_HEADER_KEY = "x-skyflow-authorization"; + public static final String SDK_METRICS_HEADER_KEY = "sky-metadata"; + public static final String REQUEST_ID_HEADER_KEY = "x-request-id"; + public static final String ERROR_FROM_CLIENT_HEADER_KEY = "error-from-client"; + +} diff --git a/src/main/java/com/skyflow/utils/Utils.java b/common/src/main/java/com/skyflow/utils/BaseUtils.java similarity index 63% rename from src/main/java/com/skyflow/utils/Utils.java rename to common/src/main/java/com/skyflow/utils/BaseUtils.java index d116871f..e536c111 100644 --- a/src/main/java/com/skyflow/utils/Utils.java +++ b/common/src/main/java/com/skyflow/utils/BaseUtils.java @@ -1,19 +1,5 @@ package com.skyflow.utils; -import com.google.gson.JsonObject; -import com.skyflow.config.ConnectionConfig; -import com.skyflow.config.Credentials; -import com.skyflow.enums.Env; -import com.skyflow.errors.ErrorCode; -import com.skyflow.errors.ErrorMessage; -import com.skyflow.errors.SkyflowException; -import com.skyflow.logs.ErrorLogs; -import com.skyflow.logs.InfoLogs; -import com.skyflow.serviceaccount.util.BearerToken; -import com.skyflow.utils.logger.LogUtil; -import com.skyflow.vault.connection.InvokeConnectionRequest; -import java.util.Base64; - import java.io.File; import java.net.MalformedURLException; import java.net.URL; @@ -22,33 +8,22 @@ import java.security.PrivateKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; -import java.util.HashMap; +import java.util.Base64; import java.util.Map; -public final class Utils { - public static String getVaultURL(String clusterId, Env env) { - StringBuilder sb = new StringBuilder(Constants.SECURE_PROTOCOL); - sb.append(clusterId); - switch (env) { - case DEV: - sb.append(Constants.DEV_DOMAIN); - break; - case STAGE: - sb.append(Constants.STAGE_DOMAIN); - break; - case SANDBOX: - sb.append(Constants.SANDBOX_DOMAIN); - break; - case PROD: - default: - sb.append(Constants.PROD_DOMAIN); - break; - } - return sb.toString(); - } +import com.google.gson.JsonObject; +import com.skyflow.config.BaseCredentials; +import com.skyflow.enums.Env; +import com.skyflow.errors.ErrorCode; +import com.skyflow.errors.ErrorMessage; +import com.skyflow.errors.SkyflowException; +import com.skyflow.logs.ErrorLogs; +import com.skyflow.logs.InfoLogs; +import com.skyflow.serviceaccount.util.BearerToken; +import com.skyflow.utils.logger.LogUtil; - @SuppressWarnings("unchecked") - public static String generateBearerToken(Credentials credentials) throws SkyflowException { +public class BaseUtils { + public static String generateBearerToken(BaseCredentials credentials) throws SkyflowException { if (credentials.getPath() != null) { BearerToken.BearerTokenBuilder builder = BearerToken.builder() .setCredentials(new File(credentials.getPath())) @@ -76,11 +51,29 @@ public static String generateBearerToken(Credentials credentials) throws Skyflow } } + public static String getVaultURL(String clusterId, Env env, String vaultDomain) { + StringBuilder sb = buildBaseUrl(clusterId, vaultDomain); + switch (env) { + case DEV: + sb.append(BaseConstants.DEV_DOMAIN); + break; + case STAGE: + sb.append(BaseConstants.STAGE_DOMAIN); + break; + case SANDBOX: + sb.append(BaseConstants.SANDBOX_DOMAIN); + break; + case PROD: + default: + sb.append(BaseConstants.PROD_DOMAIN); + break; + } + return sb.toString(); + } + public static PrivateKey getPrivateKeyFromPem(String pemKey) throws SkyflowException { - @SuppressWarnings("checkstyle:LocalVariableName") - String PKCS8PrivateHeader = Constants.PKCS8_PRIVATE_HEADER; - @SuppressWarnings("checkstyle:LocalVariableName") - String PKCS8PrivateFooter = Constants.PKCS8_PRIVATE_FOOTER; + String PKCS8PrivateHeader = BaseConstants.PKCS8_PRIVATE_HEADER; + String PKCS8PrivateFooter = BaseConstants.PKCS8_PRIVATE_FOOTER; String privateKeyContent = pemKey; PrivateKey privateKey = null; @@ -88,9 +81,14 @@ public static PrivateKey getPrivateKeyFromPem(String pemKey) throws SkyflowExcep if (pemKey.contains(PKCS8PrivateHeader)) { privateKeyContent = privateKeyContent.replace(PKCS8PrivateHeader, ""); privateKeyContent = privateKeyContent.replace(PKCS8PrivateFooter, ""); - privateKeyContent = privateKeyContent.replace("\n", ""); privateKeyContent = privateKeyContent.replace("\r\n", ""); - privateKey = parsePkcs8PrivateKey(Base64.getDecoder().decode(privateKeyContent)); + privateKeyContent = privateKeyContent.replace("\n", ""); + try { + privateKey = parsePkcs8PrivateKey(Base64.getDecoder().decode(privateKeyContent)); + } catch (IllegalArgumentException e) { + LogUtil.printErrorLog(ErrorLogs.INVALID_KEY_SPEC.getLog()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.InvalidKeySpec.getMessage()); + } } else { LogUtil.printErrorLog(ErrorLogs.JWT_INVALID_FORMAT.getLog()); throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.JwtInvalidFormat.getMessage()); @@ -112,43 +110,8 @@ public static String parameterizedString(String base, String... args) { return base; } - public static String constructConnectionURL(ConnectionConfig config, InvokeConnectionRequest invokeConnectionRequest) { - StringBuilder filledURL = new StringBuilder(config.getConnectionUrl()); - - if (invokeConnectionRequest.getPathParams() != null && !invokeConnectionRequest.getPathParams().isEmpty()) { - for (Map.Entry entry : invokeConnectionRequest.getPathParams().entrySet()) { - String key = entry.getKey(); - String value = entry.getValue(); - filledURL = new StringBuilder(filledURL.toString().replace(String.format(Constants.CURLY_PLACEHOLDER, key), value)); - } - } - - if (invokeConnectionRequest.getQueryParams() != null && !invokeConnectionRequest.getQueryParams().isEmpty()) { - filledURL.append("?"); - for (Map.Entry entry : invokeConnectionRequest.getQueryParams().entrySet()) { - String key = entry.getKey(); - String value = entry.getValue(); - filledURL.append(key).append("=").append(value).append("&"); - } - filledURL = new StringBuilder(filledURL.substring(0, filledURL.length() - 1)); - } - - return filledURL.toString(); - } - - public static Map constructConnectionHeadersMap(Map requestHeaders) { - Map headersMap = new HashMap<>(); - for (Map.Entry entry : requestHeaders.entrySet()) { - String key = entry.getKey(); - String value = entry.getValue(); - headersMap.put(key.toLowerCase(), value); - } - return headersMap; - } - - public static JsonObject getMetrics() { + protected static JsonObject getCommonMetrics() { JsonObject details = new JsonObject(); - String sdkVersion = Constants.SDK_VERSION; String deviceModel; String osDetails; String javaVersion; @@ -159,7 +122,7 @@ public static JsonObject getMetrics() { } catch (Exception e) { LogUtil.printInfoLog(parameterizedString( InfoLogs.UNABLE_TO_GENERATE_SDK_METRIC.getLog(), - Constants.SDK_METRIC_CLIENT_DEVICE_MODEL + BaseConstants.SDK_METRIC_CLIENT_DEVICE_MODEL )); deviceModel = ""; } @@ -171,7 +134,7 @@ public static JsonObject getMetrics() { } catch (Exception e) { LogUtil.printInfoLog(parameterizedString( InfoLogs.UNABLE_TO_GENERATE_SDK_METRIC.getLog(), - Constants.SDK_METRIC_CLIENT_OS_DETAILS + BaseConstants.SDK_METRIC_CLIENT_OS_DETAILS )); osDetails = ""; } @@ -183,14 +146,13 @@ public static JsonObject getMetrics() { } catch (Exception e) { LogUtil.printInfoLog(parameterizedString( InfoLogs.UNABLE_TO_GENERATE_SDK_METRIC.getLog(), - Constants.SDK_METRIC_RUNTIME_DETAILS + BaseConstants.SDK_METRIC_RUNTIME_DETAILS )); javaVersion = ""; } - details.addProperty(Constants.SDK_METRIC_NAME_VERSION, Constants.SDK_METRIC_NAME_VERSION_PREFIX + sdkVersion); - details.addProperty(Constants.SDK_METRIC_CLIENT_DEVICE_MODEL, deviceModel); - details.addProperty(Constants.SDK_METRIC_RUNTIME_DETAILS, Constants.SDK_METRIC_RUNTIME_DETAILS_PREFIX + javaVersion); - details.addProperty(Constants.SDK_METRIC_CLIENT_OS_DETAILS, osDetails); + details.addProperty(BaseConstants.SDK_METRIC_CLIENT_DEVICE_MODEL, deviceModel); + details.addProperty(BaseConstants.SDK_METRIC_RUNTIME_DETAILS, BaseConstants.SDK_METRIC_RUNTIME_DETAILS_PREFIX + javaVersion); + details.addProperty(BaseConstants.SDK_METRIC_CLIENT_OS_DETAILS, osDetails); return details; } @@ -210,4 +172,11 @@ private static PrivateKey parsePkcs8PrivateKey(byte[] pkcs8Bytes) throws Skyflow } return privateKey; } + + private static StringBuilder buildBaseUrl(String clusterId, String vaultDomain) { + StringBuilder sb = new StringBuilder(BaseConstants.SECURE_PROTOCOL); + sb.append(clusterId); + sb.append(vaultDomain); + return sb; + } } diff --git a/common/src/main/java/com/skyflow/utils/SdkVersion.java b/common/src/main/java/com/skyflow/utils/SdkVersion.java new file mode 100644 index 00000000..8136d8c9 --- /dev/null +++ b/common/src/main/java/com/skyflow/utils/SdkVersion.java @@ -0,0 +1,13 @@ +package com.skyflow.utils; + +public class SdkVersion { + private static String sdkPrefix = BaseConstants.SDK_PREFIX; + + public static String getSdkPrefix() { + return sdkPrefix; + } + + public static void setSdkPrefix(String sdkPrefix) { + SdkVersion.sdkPrefix = sdkPrefix; + } +} diff --git a/common/src/main/java/com/skyflow/utils/logger/LogUtil.java b/common/src/main/java/com/skyflow/utils/logger/LogUtil.java new file mode 100644 index 00000000..ed2b7671 --- /dev/null +++ b/common/src/main/java/com/skyflow/utils/logger/LogUtil.java @@ -0,0 +1,103 @@ +package com.skyflow.utils.logger; + +import com.skyflow.enums.LogLevel; +import com.skyflow.logs.InfoLogs; +import com.skyflow.utils.SdkVersion; + +import java.util.logging.*; + +public final class LogUtil { + private static final Logger LOGGER = Logger.getLogger(LogUtil.class.getName()); + private static boolean IS_LOGGER_SETUP_DONE = false; + + private static String logPrefix() { + return "[" + SdkVersion.getSdkPrefix() + "] "; + } + + synchronized public static void setupLogger(LogLevel logLevel) { + IS_LOGGER_SETUP_DONE = true; + LogManager.getLogManager().reset(); + LOGGER.setUseParentHandlers(false); + Formatter formatter = new SimpleFormatter() { + private static final String format = "%s: %s %n"; + + // Override format method + @Override + public synchronized String format(LogRecord logRecord) { + return String.format( + format, + loggerLevelToLogLevelMap(logRecord.getLevel()), + logRecord.getMessage() + ); + } + }; + ConsoleHandler consoleHandler = new ConsoleHandler(); + consoleHandler.setFormatter(formatter); + consoleHandler.setLevel(Level.CONFIG); + + LOGGER.addHandler(consoleHandler); + LOGGER.setLevel(logLevelToLoggerLevelMap(logLevel)); + printInfoLog(InfoLogs.LOGGER_SETUP_DONE.getLog()); + } + + public static void printErrorLog(String message) { + if (IS_LOGGER_SETUP_DONE) + LOGGER.severe(logPrefix() + message); + else { + setupLogger(LogLevel.ERROR); + LOGGER.severe(logPrefix() + message); + } + } + + public static void printDebugLog(String message) { + if (IS_LOGGER_SETUP_DONE) + LOGGER.config(logPrefix() + message); + } + + public static void printWarningLog(String message) { + if (IS_LOGGER_SETUP_DONE) + LOGGER.warning(logPrefix() + message); + } + + public static void printInfoLog(String message) { + if (IS_LOGGER_SETUP_DONE) + LOGGER.info(logPrefix() + message); + } + + + private static Level logLevelToLoggerLevelMap(LogLevel logLevel) { + Level loggerLevel; + switch (logLevel) { + case ERROR: + loggerLevel = Level.SEVERE; + break; + case WARN: + loggerLevel = Level.WARNING; + break; + case INFO: + loggerLevel = Level.INFO; + break; + case DEBUG: + loggerLevel = Level.CONFIG; + break; + default: + loggerLevel = Level.OFF; + } + return loggerLevel; + } + + private static LogLevel loggerLevelToLogLevelMap(Level loggerLevel) { + LogLevel logLevel; + if (Level.SEVERE.equals(loggerLevel)) { + logLevel = LogLevel.ERROR; + } else if (Level.WARNING.equals(loggerLevel)) { + logLevel = LogLevel.WARN; + } else if (Level.INFO.equals(loggerLevel)) { + logLevel = LogLevel.INFO; + } else if (Level.CONFIG.equals(loggerLevel)) { + logLevel = LogLevel.DEBUG; + } else + logLevel = LogLevel.OFF; + return logLevel; + } +} diff --git a/common/src/main/java/com/skyflow/utils/validations/BaseValidations.java b/common/src/main/java/com/skyflow/utils/validations/BaseValidations.java new file mode 100644 index 00000000..c2ee2df9 --- /dev/null +++ b/common/src/main/java/com/skyflow/utils/validations/BaseValidations.java @@ -0,0 +1,111 @@ +package com.skyflow.utils.validations; + +import com.skyflow.config.BaseCredentials; +import com.skyflow.errors.ErrorCode; +import com.skyflow.errors.ErrorMessage; +import com.skyflow.errors.SkyflowException; +import com.skyflow.logs.ErrorLogs; +import com.skyflow.utils.BaseConstants; +import com.skyflow.utils.BaseUtils; +import com.skyflow.utils.logger.LogUtil; + +import java.util.ArrayList; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class BaseValidations { + BaseValidations() { + } + + public static void validateCredentials(BaseCredentials credentials) throws SkyflowException { + int nonNullMembers = 0; + String path = credentials.getPath(); + String credentialsString = credentials.getCredentialsString(); + String token = credentials.getToken(); + String apiKey = credentials.getApiKey(); + Object context = credentials.getContext(); + ArrayList roles = credentials.getRoles(); + + if (path != null) nonNullMembers++; + if (credentialsString != null) nonNullMembers++; + if (token != null) nonNullMembers++; + if (apiKey != null) nonNullMembers++; + + if (nonNullMembers > 1) { + LogUtil.printErrorLog(ErrorLogs.MULTIPLE_TOKEN_GENERATION_MEANS_PASSED.getLog()); + throw new SkyflowException( + ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.MultipleTokenGenerationMeansPassed.getMessage() + ); + } else if (nonNullMembers < 1) { + LogUtil.printErrorLog(ErrorLogs.NO_TOKEN_GENERATION_MEANS_PASSED.getLog()); + throw new SkyflowException( + ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.NoTokenGenerationMeansPassed.getMessage() + ); + } else if (path != null && path.trim().isEmpty()) { + LogUtil.printErrorLog(ErrorLogs.EMPTY_CREDENTIALS_PATH.getLog()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyCredentialFilePath.getMessage()); + } else if (credentialsString != null && credentialsString.trim().isEmpty()) { + LogUtil.printErrorLog(ErrorLogs.EMPTY_CREDENTIALS_STRING.getLog()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyCredentialsString.getMessage()); + } else if (token != null && token.trim().isEmpty()) { + LogUtil.printErrorLog(ErrorLogs.EMPTY_TOKEN_VALUE.getLog()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyToken.getMessage()); + } else if (apiKey != null) { + if (apiKey.trim().isEmpty()) { + LogUtil.printErrorLog(ErrorLogs.EMPTY_API_KEY_VALUE.getLog()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyApikey.getMessage()); + } else { + Pattern pattern = Pattern.compile(BaseConstants.API_KEY_REGEX); + Matcher matcher = pattern.matcher(apiKey); + if (!matcher.matches()) { + LogUtil.printErrorLog(ErrorLogs.INVALID_API_KEY.getLog()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.InvalidApikey.getMessage()); + } + } + } else if (roles != null) { + if (roles.isEmpty()) { + LogUtil.printErrorLog(ErrorLogs.EMPTY_ROLES.getLog()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyRoles.getMessage()); + } else { + for (int index = 0; index < roles.size(); index++) { + String role = roles.get(index); + if (role == null || role.trim().isEmpty()) { + LogUtil.printErrorLog(BaseUtils.parameterizedString( + ErrorLogs.EMPTY_OR_NULL_ROLE_IN_ROLES.getLog(), Integer.toString(index) + )); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyRoleInRoles.getMessage()); + } + } + } + } + if (context != null) { + if (context instanceof String) { + String ctxStr = (String) context; + if (ctxStr.trim().isEmpty()) { + LogUtil.printErrorLog(ErrorLogs.EMPTY_OR_NULL_CONTEXT.getLog()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyContext.getMessage()); + } + } else if (context instanceof Map) { + Map ctxMap = (Map) context; + if (ctxMap.isEmpty()) { + LogUtil.printErrorLog(ErrorLogs.EMPTY_OR_NULL_CONTEXT.getLog()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyContext.getMessage()); + } + Pattern ctxKeyPattern = Pattern.compile(BaseConstants.CONTEXT_KEY_REGEX); + for (Object key : ctxMap.keySet()) { + if (key == null || !ctxKeyPattern.matcher(key.toString()).matches()) { + String keyStr = key == null ? "null" : key.toString(); + LogUtil.printErrorLog(BaseUtils.parameterizedString( + ErrorLogs.INVALID_CONTEXT_MAP_KEY.getLog(), keyStr)); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), + BaseUtils.parameterizedString(ErrorMessage.InvalidContextMapKey.getMessage(), keyStr)); + } + } + } else { + LogUtil.printErrorLog(ErrorLogs.INVALID_CONTEXT_TYPE.getLog()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.InvalidContextType.getMessage()); + } + } + } +} diff --git a/common/src/main/java/com/skyflow/vault/controller/IVaultController.java b/common/src/main/java/com/skyflow/vault/controller/IVaultController.java new file mode 100644 index 00000000..1c4f907f --- /dev/null +++ b/common/src/main/java/com/skyflow/vault/controller/IVaultController.java @@ -0,0 +1,9 @@ +package com.skyflow.vault.controller; + +import com.skyflow.errors.SkyflowException; + +// Common interface — ONLY operations supported on both vault types. +public interface IVaultController { + InsertResp insert(InsertReq request) throws SkyflowException; + DetokenizeResp detokenize(DetokenizeReq request) throws SkyflowException; +} \ No newline at end of file diff --git a/common/src/main/java/com/skyflow/vault/data/BaseDetokenizeData.java b/common/src/main/java/com/skyflow/vault/data/BaseDetokenizeData.java new file mode 100644 index 00000000..6ff0ee87 --- /dev/null +++ b/common/src/main/java/com/skyflow/vault/data/BaseDetokenizeData.java @@ -0,0 +1,7 @@ +package com.skyflow.vault.data; + +// Shared extension point for module-specific detokenize request data. Intentionally empty: +// v2 and flowvault no longer have any field in common here, so each owns its own state. +// Retained so the modules keep a shared supertype for future use. +public class BaseDetokenizeData { +} diff --git a/common/src/main/java/com/skyflow/vault/data/BaseDetokenizeRecordResponse.java b/common/src/main/java/com/skyflow/vault/data/BaseDetokenizeRecordResponse.java new file mode 100644 index 00000000..7a1ec217 --- /dev/null +++ b/common/src/main/java/com/skyflow/vault/data/BaseDetokenizeRecordResponse.java @@ -0,0 +1,19 @@ +package com.skyflow.vault.data; + +public class BaseDetokenizeRecordResponse { + private final String token; + private final String error; + + public BaseDetokenizeRecordResponse(String token, String error){ + this.token = token; + this.error = error; + } + public String getToken() { + return token; + } + + public String getError() { + return error; + } + +} diff --git a/common/src/main/java/com/skyflow/vault/data/BaseDetokenizeRequest.java b/common/src/main/java/com/skyflow/vault/data/BaseDetokenizeRequest.java new file mode 100644 index 00000000..d19d0432 --- /dev/null +++ b/common/src/main/java/com/skyflow/vault/data/BaseDetokenizeRequest.java @@ -0,0 +1,4 @@ +package com.skyflow.vault.data; + +public class BaseDetokenizeRequest { +} diff --git a/common/src/main/java/com/skyflow/vault/data/BaseDetokenizeResponse.java b/common/src/main/java/com/skyflow/vault/data/BaseDetokenizeResponse.java new file mode 100644 index 00000000..a5061b25 --- /dev/null +++ b/common/src/main/java/com/skyflow/vault/data/BaseDetokenizeResponse.java @@ -0,0 +1,5 @@ +package com.skyflow.vault.data; + +public class BaseDetokenizeResponse { + +} diff --git a/common/src/main/java/com/skyflow/vault/data/BaseGetRequest.java b/common/src/main/java/com/skyflow/vault/data/BaseGetRequest.java new file mode 100644 index 00000000..de2ecd12 --- /dev/null +++ b/common/src/main/java/com/skyflow/vault/data/BaseGetRequest.java @@ -0,0 +1,47 @@ +package com.skyflow.vault.data; + +import java.util.ArrayList; + +public class BaseGetRequest { + private final BaseGetRequestBuilder builder; + + protected BaseGetRequest(BaseGetRequestBuilder builder) { + this.builder = builder; + } + + public String getTable() { + return this.builder.table; + } + + public ArrayList getIds() { + return this.builder.ids; + } + + public ArrayList getFields() { + return this.builder.fields; + } + + static class BaseGetRequestBuilder { + protected String table; + protected ArrayList ids; + protected ArrayList fields; + + protected BaseGetRequestBuilder() { + } + + public BaseGetRequestBuilder table(String table) { + this.table = table; + return this; + } + + public BaseGetRequestBuilder ids(ArrayList ids) { + this.ids = ids; + return this; + } + + public BaseGetRequestBuilder fields(ArrayList fields) { + this.fields = fields; + return this; + } + } +} diff --git a/src/main/java/com/skyflow/vault/data/GetResponse.java b/common/src/main/java/com/skyflow/vault/data/BaseGetResponse.java similarity index 65% rename from src/main/java/com/skyflow/vault/data/GetResponse.java rename to common/src/main/java/com/skyflow/vault/data/BaseGetResponse.java index 365fe38e..34545b7f 100644 --- a/src/main/java/com/skyflow/vault/data/GetResponse.java +++ b/common/src/main/java/com/skyflow/vault/data/BaseGetResponse.java @@ -5,11 +5,11 @@ import java.util.ArrayList; import java.util.HashMap; -public class GetResponse { +public class BaseGetResponse { private final ArrayList> data; private final ArrayList> errors; - public GetResponse(ArrayList> data, ArrayList> errors) { + public BaseGetResponse(ArrayList> data, ArrayList> errors) { this.data = data; this.errors = errors; } @@ -17,10 +17,6 @@ public GetResponse(ArrayList> data, ArrayListDeprecation notice: The {@code skyflow_id} key in each record map is - * deprecated and will be removed in an upcoming release. Use {@code skyflowId} instead. - * Both keys are present simultaneously in v2 for backward compatibility.

*/ public ArrayList> getData() { return data; diff --git a/common/src/main/java/com/skyflow/vault/data/BaseInsertRequest.java b/common/src/main/java/com/skyflow/vault/data/BaseInsertRequest.java new file mode 100644 index 00000000..04f82831 --- /dev/null +++ b/common/src/main/java/com/skyflow/vault/data/BaseInsertRequest.java @@ -0,0 +1,7 @@ +package com.skyflow.vault.data; + +// Shared extension point for module-specific insert requests. Intentionally empty: +// v2 and flowvault insert requests no longer have any field in common, so each owns +// its own state. Retained so the modules keep a shared supertype for future use. +public class BaseInsertRequest { +} diff --git a/common/src/main/java/com/skyflow/vault/data/BaseInsertResponse.java b/common/src/main/java/com/skyflow/vault/data/BaseInsertResponse.java new file mode 100644 index 00000000..bb10a688 --- /dev/null +++ b/common/src/main/java/com/skyflow/vault/data/BaseInsertResponse.java @@ -0,0 +1,7 @@ +package com.skyflow.vault.data; + +// Shared extension point for module-specific insert responses. Intentionally empty: +// v2 and flowvault insert responses no longer have any field in common, so each owns +// its own state. Retained so the modules keep a shared supertype for future use. +public class BaseInsertResponse { +} diff --git a/common/src/main/java/com/skyflow/vault/data/BaseQueryRequest.java b/common/src/main/java/com/skyflow/vault/data/BaseQueryRequest.java new file mode 100644 index 00000000..01ca3d46 --- /dev/null +++ b/common/src/main/java/com/skyflow/vault/data/BaseQueryRequest.java @@ -0,0 +1,25 @@ +package com.skyflow.vault.data; + +public class BaseQueryRequest { + private final BaseQueryRequestBuilder builder; + + protected BaseQueryRequest(BaseQueryRequestBuilder builder) { + this.builder = builder; + } + + public String getQuery() { + return this.builder.query; + } + + static class BaseQueryRequestBuilder { + protected String query; + + protected BaseQueryRequestBuilder() { + } + + public BaseQueryRequestBuilder query(String query) { + this.query = query; + return this; + } + } +} diff --git a/common/src/main/java/com/skyflow/vault/data/BaseQueryResponse.java b/common/src/main/java/com/skyflow/vault/data/BaseQueryResponse.java new file mode 100644 index 00000000..1930d66d --- /dev/null +++ b/common/src/main/java/com/skyflow/vault/data/BaseQueryResponse.java @@ -0,0 +1,37 @@ +package com.skyflow.vault.data; + +import com.google.gson.Gson; + +import java.util.ArrayList; +import java.util.HashMap; + +public class BaseQueryResponse { + private final ArrayList> fields; + private final ArrayList> errors; + + public BaseQueryResponse(ArrayList> fields) { + this.fields = fields; + this.errors = null; + } + + /** + * Returns the list of record maps from the Query response. Each map contains all + * field name/value pairs for the record. + */ + public ArrayList> getFields() { + return fields; + } + + /** + * Always returns null. The Query API does not support partial-error responses. + */ + public ArrayList> getErrors() { + return errors; + } + + @Override + public String toString() { + Gson gson = new Gson().newBuilder().serializeNulls().create(); + return gson.toJson(this); + } +} diff --git a/common/src/main/java/com/skyflow/vault/data/RequestContext.java b/common/src/main/java/com/skyflow/vault/data/RequestContext.java new file mode 100644 index 00000000..0cf6055b --- /dev/null +++ b/common/src/main/java/com/skyflow/vault/data/RequestContext.java @@ -0,0 +1,26 @@ +package com.skyflow.vault.data; + +import com.skyflow.enums.CustomHeaderKey; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public final class RequestContext { + private final String operation; + private final Map headers = new HashMap<>(); + + public RequestContext(String operation) { + this.operation = operation; + } + + public String getOperation() { return operation; } + + public void addHeader(CustomHeaderKey key, String value) { + headers.put(key, value); + } + + public Map getHeaders() { + return Collections.unmodifiableMap(headers); + } +} diff --git a/common/src/main/java/com/skyflow/vault/data/RequestInterceptor.java b/common/src/main/java/com/skyflow/vault/data/RequestInterceptor.java new file mode 100644 index 00000000..37f5261d --- /dev/null +++ b/common/src/main/java/com/skyflow/vault/data/RequestInterceptor.java @@ -0,0 +1,6 @@ +package com.skyflow.vault.data; + +@FunctionalInterface +public interface RequestInterceptor { + void intercept(RequestContext context); +} diff --git a/common/src/test/java/com/skyflow/BaseSkyflowTests.java b/common/src/test/java/com/skyflow/BaseSkyflowTests.java new file mode 100644 index 00000000..cfd65ad5 --- /dev/null +++ b/common/src/test/java/com/skyflow/BaseSkyflowTests.java @@ -0,0 +1,440 @@ +package com.skyflow; + +import com.skyflow.config.BaseVaultConfig; +import com.skyflow.config.Credentials; +import com.skyflow.enums.Env; +import com.skyflow.enums.LogLevel; +import com.skyflow.errors.ErrorCode; +import com.skyflow.errors.ErrorMessage; +import com.skyflow.errors.SkyflowException; +import com.skyflow.logs.ErrorLogs; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +public class BaseSkyflowTests { + private static final String INVALID_EXCEPTION_THROWN = "Should not have thrown any exception"; + private static final String EXCEPTION_NOT_THROWN = "Should have thrown an exception"; + private static String vaultID = null; + private static String clusterID = null; + private static String newClusterID = null; + private static String token = null; + + @BeforeClass + public static void setup() { + vaultID = "test_vault_id"; + clusterID = "test_cluster_id"; + newClusterID = "new_test_cluster_id"; + token = "test_token"; + } + + private static BaseVaultConfig newConfig(String vaultId, String clusterId, Env env) { + BaseVaultConfig config = new BaseVaultConfig(); + config.setVaultId(vaultId); + config.setClusterId(clusterId); + config.setEnv(env); + return config; + } + + @Test + public void testAddingExistingVaultConfigThrows() { + try { + BaseVaultConfig config = newConfig(vaultID, clusterID, Env.SANDBOX); + TestSkyflow client = TestSkyflow.builder().build(); + client.addVaultConfig(config).addVaultConfig(config); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); + Assert.assertEquals(ErrorMessage.VaultIdAlreadyInConfigList.getMessage(), e.getMessage()); + } + } + + @Test + public void testUpdatingNonExistentVaultConfigInBuilderThrows() { + try { + BaseVaultConfig config = newConfig(vaultID, clusterID, Env.SANDBOX); + TestSkyflow.builder().updateVaultConfig(config).build(); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); + Assert.assertEquals(ErrorMessage.VaultIdNotInConfigList.getMessage(), e.getMessage()); + } + } + + @Test + public void testUpdatingNonExistentVaultConfigInClientThrows() { + try { + BaseVaultConfig config = newConfig(vaultID, clusterID, Env.SANDBOX); + TestSkyflow client = TestSkyflow.builder().build(); + client.updateVaultConfig(config); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); + Assert.assertEquals(ErrorMessage.VaultIdNotInConfigList.getMessage(), e.getMessage()); + } + } + + @Test + public void testRemovingNonExistentVaultConfigInBuilderThrows() { + try { + TestSkyflow.builder().removeVaultConfig(vaultID).build(); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); + Assert.assertEquals(ErrorMessage.VaultIdNotInConfigList.getMessage(), e.getMessage()); + } + } + + @Test + public void testRemovingExistingVaultConfigSucceeds() { + try { + BaseVaultConfig config = newConfig(vaultID, clusterID, Env.SANDBOX); + TestSkyflow client = TestSkyflow.builder().addVaultConfig(config).build(); + client.removeVaultConfig(vaultID); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testRemovingVaultConfigWithoutAddingThrows() { + try { + TestSkyflow client = TestSkyflow.builder().build(); + client.removeVaultConfig(vaultID); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); + Assert.assertEquals(ErrorMessage.VaultIdNotInConfigList.getMessage(), e.getMessage()); + } + } + + @Test + public void testGettingNonExistentVaultConfigReturnsNull() { + TestSkyflow client = TestSkyflow.builder().build(); + Assert.assertNull(client.getVaultConfig(vaultID)); + } + + @Test + public void testVaultThrowsWhenNoConfigAdded() { + try { + TestSkyflow client = TestSkyflow.builder().build(); + client.vault(); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); + Assert.assertEquals(ErrorMessage.VaultIdNotInConfigList.getMessage(), e.getMessage()); + } + } + + @Test + public void testVaultByIdThrowsAfterRemoval() { + try { + BaseVaultConfig primary = newConfig(vaultID, clusterID, Env.SANDBOX); + BaseVaultConfig secondary = newConfig(vaultID + "123", clusterID, Env.SANDBOX); + TestSkyflow client = TestSkyflow.builder().addVaultConfig(primary).addVaultConfig(secondary).build(); + client.removeVaultConfig(vaultID); + client.vault(vaultID); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); + Assert.assertEquals(ErrorMessage.VaultIdNotInConfigList.getMessage(), e.getMessage()); + } + } + + @Test + public void testDefaultLogLevel() { + TestSkyflow client = TestSkyflow.builder().setLogLevel(null).build(); + Assert.assertEquals(LogLevel.ERROR, client.getLogLevel()); + } + + @Test + public void testSetLogLevel() { + TestSkyflow client = TestSkyflow.builder().setLogLevel(LogLevel.INFO).build(); + Assert.assertEquals(LogLevel.INFO, client.getLogLevel()); + client.setLogLevel(LogLevel.WARN); + Assert.assertEquals(LogLevel.WARN, client.getLogLevel()); + } + + @Test + public void testAddingInvalidSkyflowCredentialsThrows() { + try { + Credentials credentials = new Credentials(); + TestSkyflow.builder().addSkyflowCredentials(credentials).build(); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); + Assert.assertEquals(ErrorMessage.NoTokenGenerationMeansPassed.getMessage(), e.getMessage()); + } + } + + @Test + public void testUpdatingValidSkyflowCredentialsSucceeds() { + try { + BaseVaultConfig config = newConfig(vaultID, clusterID, Env.SANDBOX); + Credentials credentials = new Credentials(); + credentials.setToken(token); + TestSkyflow client = TestSkyflow.builder().addVaultConfig(config).build(); + client.updateSkyflowCredentials(credentials); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testUpdateVaultConfigNullCredentialsFallsBackToPrevious() { + try { + Credentials creds = new Credentials(); + creds.setToken(token); + BaseVaultConfig config = newConfig(vaultID, clusterID, Env.SANDBOX); + config.setCredentials(creds); + TestSkyflow client = TestSkyflow.builder().addVaultConfig(config).build(); + + BaseVaultConfig partialUpdate = newConfig(vaultID, clusterID, Env.SANDBOX); + client.updateVaultConfig(partialUpdate); + Assert.assertNotNull(client.getVaultConfig(vaultID).getCredentials()); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testUpdateVaultConfigWithNewClusterIdAndCredentialsUpdatesAllFields() { + try { + Credentials creds = new Credentials(); + creds.setToken(token); + BaseVaultConfig config = newConfig(vaultID, clusterID, Env.DEV); + config.setCredentials(creds); + TestSkyflow client = TestSkyflow.builder().addVaultConfig(config).build(); + + Credentials newCreds = new Credentials(); + newCreds.setToken("updated-token-value"); + BaseVaultConfig update = newConfig(vaultID, newClusterID, Env.PROD); + update.setCredentials(newCreds); + client.updateVaultConfig(update); + + Assert.assertEquals(newClusterID, client.getVaultConfig(vaultID).getClusterId()); + Assert.assertEquals(Env.PROD, client.getVaultConfig(vaultID).getEnv()); + Assert.assertEquals("updated-token-value", client.getVaultConfig(vaultID).getCredentials().getToken()); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testUpdateVaultConfigWithNullEnvFallsBackToPreviousEnv() { + // BaseVaultConfig's setEnv/constructor never store null (default to PROD), so getEnv() + // never returns null via the normal API. Override it to exercise the fallback branch + // in mergeVaultConfig. + try { + Credentials creds = new Credentials(); + creds.setToken(token); + BaseVaultConfig initial = newConfig(vaultID, clusterID, Env.SANDBOX); + initial.setCredentials(creds); + TestSkyflow client = TestSkyflow.builder().addVaultConfig(initial).build(); + + BaseVaultConfig updateWithNullEnv = new BaseVaultConfig() { + @Override + public Env getEnv() { + return null; + } + }; + updateWithNullEnv.setVaultId(vaultID); + updateWithNullEnv.setClusterId(clusterID); + updateWithNullEnv.setCredentials(creds); + + client.updateVaultConfig(updateWithNullEnv); + Assert.assertEquals(Env.SANDBOX, client.getVaultConfig(vaultID).getEnv()); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testMergeVaultConfigWithNullClusterIdFallsBackToPreviousClusterId() throws SkyflowException { + BaseVaultConfig existing = newConfig(vaultID, clusterID, Env.DEV); + BaseVaultConfig incoming = new BaseVaultConfig(); + incoming.setVaultId(vaultID); + // clusterId intentionally left null + + BaseVaultConfig result = TestSkyflow.builder().mergeVaultConfig(incoming, existing); + Assert.assertEquals(clusterID, result.getClusterId()); + } + + @Test + public void testVaultReturnsFirstEntryWhenNoVaultIdSpecified() throws SkyflowException { + BaseVaultConfig config = newConfig(vaultID, clusterID, Env.SANDBOX); + TestSkyflow client = TestSkyflow.builder().addVaultConfig(config).build(); + + Object vault = client.vault(); + + Assert.assertNotNull(vault); + Assert.assertSame(vault, client.vault(vaultID)); + } + + @Test + public void testVaultByIdReturnsConfigSpecificEntry() throws SkyflowException { + String secondaryId = vaultID + "123"; + BaseVaultConfig primary = newConfig(vaultID, clusterID, Env.SANDBOX); + BaseVaultConfig secondary = newConfig(secondaryId, clusterID, Env.SANDBOX); + TestSkyflow client = TestSkyflow.builder().addVaultConfig(primary).addVaultConfig(secondary).build(); + + Object primaryVault = client.vault(vaultID); + Object secondaryVault = client.vault(secondaryId); + + Assert.assertNotNull(primaryVault); + Assert.assertNotNull(secondaryVault); + Assert.assertNotSame(primaryVault, secondaryVault); + Assert.assertSame(primaryVault, client.vault(vaultID)); + } + + @Test + public void testGettingExistingVaultConfigReturnsStoredConfig() throws SkyflowException { + BaseVaultConfig config = newConfig(vaultID, clusterID, Env.SANDBOX); + TestSkyflow client = TestSkyflow.builder().addVaultConfig(config).build(); + + BaseVaultConfig stored = client.getVaultConfig(vaultID); + + Assert.assertNotNull(stored); + Assert.assertEquals(vaultID, stored.getVaultId()); + Assert.assertEquals(clusterID, stored.getClusterId()); + Assert.assertEquals(Env.SANDBOX, stored.getEnv()); + } + + @Test + public void testInstanceSetLogLevelNullResetsToDefault() throws SkyflowException { + TestSkyflow client = TestSkyflow.builder().setLogLevel(LogLevel.INFO).build(); + Assert.assertEquals(LogLevel.INFO, client.getLogLevel()); + + client.setLogLevel(null); + + Assert.assertEquals(LogLevel.ERROR, client.getLogLevel()); + } + + @Test + public void testUpdateVaultConfigLeavesOldConfigWhenHookThrows() throws SkyflowException { + BaseVaultConfig config = newConfig(vaultID, clusterID, Env.SANDBOX); + TestSkyflow client = TestSkyflow.builder() + .addVaultConfig(config) + .failVaultConfigUpdateFor(vaultID) + .build(); + + BaseVaultConfig update = newConfig(vaultID, newClusterID, Env.PROD); + try { + client.updateVaultConfig(update); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + // expected: onVaultConfigUpdated hook forced a failure + } + + BaseVaultConfig stillOld = client.getVaultConfig(vaultID); + Assert.assertEquals(clusterID, stillOld.getClusterId()); + Assert.assertEquals(Env.SANDBOX, stillOld.getEnv()); + } + + private static class TestSkyflow extends BaseSkyflow { + private final TestSkyflowClientBuilder builder; + + private TestSkyflow(TestSkyflowClientBuilder builder) { + super(builder); + this.builder = builder; + } + + static TestSkyflowClientBuilder builder() { + return new TestSkyflowClientBuilder(); + } + + @Override + protected TestSkyflow self() { + return this; + } + + Object vault() throws SkyflowException { + return resolveOrThrow(this.builder.vaultClientsMap, null, + ErrorLogs.VAULT_CONFIG_DOES_NOT_EXIST, ErrorMessage.VaultIdNotInConfigList); + } + + Object vault(String vaultId) throws SkyflowException { + return resolveOrThrow(this.builder.vaultClientsMap, vaultId, + ErrorLogs.VAULT_CONFIG_DOES_NOT_EXIST, ErrorMessage.VaultIdNotInConfigList); + } + + private static class TestSkyflowClientBuilder extends BaseSkyflowClientBuilder { + private final java.util.LinkedHashMap vaultClientsMap = new java.util.LinkedHashMap<>(); + private String vaultIdToFailUpdate; + + @Override + protected void validateVaultConfig(BaseVaultConfig vaultConfig) throws SkyflowException { + if (vaultConfig.getVaultId() == null || vaultConfig.getVaultId().trim().isEmpty()) { + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyVaultId.getMessage()); + } + } + + @Override + protected void onVaultConfigAdded(BaseVaultConfig vaultConfig) { + this.vaultClientsMap.put(vaultConfig.getVaultId(), new Object()); + } + + @Override + protected void onVaultConfigUpdated(BaseVaultConfig updatedConfig) throws SkyflowException { + if (updatedConfig.getVaultId().equals(this.vaultIdToFailUpdate)) { + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), "forced update hook failure"); + } + this.vaultClientsMap.put(updatedConfig.getVaultId(), new Object()); + } + + TestSkyflowClientBuilder failVaultConfigUpdateFor(String vaultId) { + this.vaultIdToFailUpdate = vaultId; + return this; + } + + @Override + protected void onVaultConfigRemoved(String vaultId) { + this.vaultClientsMap.remove(vaultId); + } + + @Override + protected boolean hasVaultClient(String vaultId) { + return this.vaultClientsMap.containsKey(vaultId); + } + + @Override + protected void onCredentialsUpdated(Credentials credentials) { + // no-op: this test double only exercises template orchestration, not propagation + } + + @Override + public TestSkyflowClientBuilder addVaultConfig(BaseVaultConfig vaultConfig) throws SkyflowException { + super.addVaultConfig(vaultConfig); + return this; + } + + @Override + public TestSkyflowClientBuilder updateVaultConfig(BaseVaultConfig vaultConfig) throws SkyflowException { + super.updateVaultConfig(vaultConfig); + return this; + } + + @Override + public TestSkyflowClientBuilder removeVaultConfig(String vaultId) throws SkyflowException { + super.removeVaultConfig(vaultId); + return this; + } + + @Override + public TestSkyflowClientBuilder addSkyflowCredentials(Credentials credentials) throws SkyflowException { + super.addSkyflowCredentials(credentials); + return this; + } + + @Override + public TestSkyflowClientBuilder setLogLevel(LogLevel logLevel) { + super.setLogLevel(logLevel); + return this; + } + + TestSkyflow build() { + return new TestSkyflow(this); + } + } + } +} diff --git a/common/src/test/java/com/skyflow/BaseVaultClientTests.java b/common/src/test/java/com/skyflow/BaseVaultClientTests.java new file mode 100644 index 00000000..ec78c397 --- /dev/null +++ b/common/src/test/java/com/skyflow/BaseVaultClientTests.java @@ -0,0 +1,304 @@ +package com.skyflow; + +import com.skyflow.config.BaseCredentials; +import com.skyflow.config.BaseVaultConfig; +import com.skyflow.errors.ErrorMessage; +import com.skyflow.errors.SkyflowException; +import com.skyflow.logs.ErrorLogs; +import com.skyflow.utils.BaseConstants; +import okhttp3.Call; +import okhttp3.Connection; +import okhttp3.Interceptor; +import okhttp3.OkHttpClient; +import okhttp3.Protocol; +import okhttp3.Request; +import okhttp3.Response; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +public class BaseVaultClientTests { + + private static final String ENV_FILE = ".env"; + private byte[] originalEnvContent; + + @Before + public void saveEnvFileState() throws IOException { + File f = new File(ENV_FILE); + originalEnvContent = f.exists() ? Files.readAllBytes(Paths.get(ENV_FILE)) : null; + } + + @After + public void restoreEnvFile() throws IOException { + if (originalEnvContent != null) { + Files.write(Paths.get(ENV_FILE), originalEnvContent); + } else { + Files.deleteIfExists(Paths.get(ENV_FILE)); + } + } + + private BaseVaultClient newClient(BaseCredentials commonCredentials) { + return new BaseVaultClient<>(new BaseVaultConfig(), commonCredentials); + } + + @Test + public void testPrioritiseCredentials_prefersVaultSpecificCredentials() throws SkyflowException { + BaseCredentials vaultSpecific = new BaseCredentials(); + vaultSpecific.setApiKey("test_api_key"); + BaseVaultClient client = newClient(null); + + client.prioritiseCredentials(vaultSpecific); + + Assert.assertEquals(vaultSpecific, client.finalCredentials); + } + + @Test + public void testPrioritiseCredentials_fallsBackToCommonCredentials() throws SkyflowException { + BaseCredentials common = new BaseCredentials(); + common.setApiKey("common_api_key"); + BaseVaultClient client = newClient(common); + + client.prioritiseCredentials(null); + + Assert.assertEquals(common, client.finalCredentials); + } + + @Test + public void testPrioritiseCredentials_credentialChange_resetsTokenAndApiKey() throws SkyflowException { + BaseCredentials credentialsA = new BaseCredentials(); + credentialsA.setToken("x.eyJleHAiOjk5OTk5OTk5OTl9.y"); + BaseVaultClient client = newClient(null); + + client.prioritiseCredentials(credentialsA); + client.token = "cached-token"; + client.apiKey = "cached-api-key"; + + BaseCredentials credentialsB = new BaseCredentials(); + credentialsB.setToken("other-token"); + client.prioritiseCredentials(credentialsB); + + Assert.assertNull(client.token); + Assert.assertNull(client.apiKey); + } + + @Test + public void testSetBearerToken_withApiKey() throws SkyflowException { + BaseCredentials creds = new BaseCredentials(); + creds.setApiKey("sky-ab123-abcd1234cdef1234abcd4321cdef4321"); + BaseVaultClient client = newClient(null); + + client.setBearerToken(creds); + + Assert.assertEquals("sky-ab123-abcd1234cdef1234abcd4321cdef4321", client.token); + } + + @Test + public void testSetBearerToken_generatesTokenWhenNull() throws SkyflowException { + BaseCredentials creds = new BaseCredentials(); + creds.setToken("x.eyJleHAiOjk5OTk5OTk5OTl9.y"); + BaseVaultClient client = newClient(null); + + client.setBearerToken(creds); + + Assert.assertEquals("x.eyJleHAiOjk5OTk5OTk5OTl9.y", client.token); + } + + @Test + public void testSetBearerToken_reusesValidNonExpiredToken() throws SkyflowException { + BaseCredentials creds = new BaseCredentials(); + creds.setToken("x.eyJleHAiOjk5OTk5OTk5OTl9.y"); + BaseVaultClient client = newClient(null); + + // First call: token=null → generates from creds.getToken() + client.setBearerToken(creds); + Assert.assertEquals("x.eyJleHAiOjk5OTk5OTk5OTl9.y", client.token); + + // Second call: token valid, not expired → reuse branch + client.setBearerToken(creds); + Assert.assertEquals("x.eyJleHAiOjk5OTk5OTk5OTl9.y", client.token); + } + + @Test + public void testSetBearerToken_noCredentials_throwsEmptyCredentials() { + BaseVaultClient client = newClient(null); + try { + client.setBearerToken(null); + Assert.fail("Should have thrown SkyflowException"); + } catch (SkyflowException e) { + // message varies by environment (EmptyCredentials when no .env, or credential error when .env provides creds) + } + } + + /** + * Covers the dotenv success path: Dotenv.load() succeeds and returns a + * non-null credentials string, so finalCredentials is set via credentialsString. + */ + @Test + public void testPrioritiseCredentials_dotenvReturnsCredentials_setsCredentials() throws Exception { + try (FileWriter fw = new FileWriter(ENV_FILE)) { + fw.write(BaseConstants.ENV_CREDENTIALS_KEY_NAME + "={\"token\":\"env-token-value\"}\n"); + } + + BaseVaultClient client = newClient(null); + client.prioritiseCredentials(null); + + Assert.assertNotNull(client.finalCredentials); + Assert.assertEquals("{\"token\":\"env-token-value\"}", client.finalCredentials.getCredentialsString()); + } + + /** + * Covers the path where dotenv loads but the key is absent (returns null), + * causing SkyflowException(EmptyCredentials) to be thrown directly. + */ + @Test + public void testPrioritiseCredentials_dotenvKeyMissing_throwsSkyflowException() throws Exception { + try (FileWriter fw = new FileWriter(ENV_FILE)) { + fw.write("SOME_OTHER_KEY=some_value\n"); + } + + BaseVaultClient client = newClient(null); + try { + client.prioritiseCredentials(null); + Assert.fail("Should have thrown SkyflowException"); + } catch (SkyflowException e) { + Assert.assertTrue(e.getMessage().contains(ErrorMessage.EmptyCredentials.getMessage())); + } + } + + /** + * Covers buildSharedHttpClient: the interceptor it installs must inject + * "Authorization: Bearer " using the supplied tokenSupplier. No real network call is + * made; a hand-rolled Interceptor.Chain captures the request that would be sent. + */ + @Test + public void testBuildSharedHttpClient_injectsAuthorizationHeaderFromTokenSupplier() throws IOException { + BaseVaultClient client = newClient(null); + Supplier tokenSupplier = () -> "test-shared-token"; + + OkHttpClient httpClient = client.buildSharedHttpClient(tokenSupplier); + + Assert.assertEquals(1, httpClient.interceptors().size()); + Interceptor interceptor = httpClient.interceptors().get(0); + + Request originalRequest = new Request.Builder().url("https://example.com/").build(); + final Request[] capturedRequest = new Request[1]; + Interceptor.Chain fakeChain = new Interceptor.Chain() { + @Override + public Request request() { + return originalRequest; + } + + @Override + public Response proceed(Request request) { + capturedRequest[0] = request; + return new Response.Builder() + .request(request) + .protocol(Protocol.HTTP_1_1) + .code(200) + .message("OK") + .build(); + } + + @Override + public Connection connection() { + return null; + } + + @Override + public Call call() { + throw new UnsupportedOperationException(); + } + + @Override + public int connectTimeoutMillis() { + return 0; + } + + @Override + public Interceptor.Chain withConnectTimeout(int timeout, TimeUnit unit) { + throw new UnsupportedOperationException(); + } + + @Override + public int readTimeoutMillis() { + return 0; + } + + @Override + public Interceptor.Chain withReadTimeout(int timeout, TimeUnit unit) { + throw new UnsupportedOperationException(); + } + + @Override + public int writeTimeoutMillis() { + return 0; + } + + @Override + public Interceptor.Chain withWriteTimeout(int timeout, TimeUnit unit) { + throw new UnsupportedOperationException(); + } + }; + + interceptor.intercept(fakeChain); + + Assert.assertNotNull(capturedRequest[0]); + Assert.assertEquals("Bearer test-shared-token", capturedRequest[0].header("Authorization")); + } + + /** + * Covers wrapApiException: the returned SkyflowException should carry the given status code, + * and its message should reflect the JSON-serialized responseBody (round-tripped through Gson + * exactly as wrapApiException does internally). + */ + @Test + public void testWrapApiException_carriesStatusCodeAndJsonResponseBody() { + Map errorBody = new HashMap<>(); + errorBody.put("message", "something went wrong"); + Map responseBody = new HashMap<>(); + responseBody.put("error", errorBody); + Map> headers = new HashMap<>(); + + SkyflowException exception = BaseVaultClient.wrapApiException( + 400, new RuntimeException("network error"), headers, responseBody, ErrorLogs.INSERT_RECORDS_REJECTED); + + Assert.assertEquals(400, exception.getHttpCode()); + Assert.assertEquals("something went wrong", exception.getMessage()); + } + + /** + * Covers setBearerToken's "token present but expired -> regenerate" branch. A hand-crafted + * always-expired JWT ("x.eyJleHAiOjF9.y", exp=1 => 1970) is seeded directly into the token + * field, bypassing setBearerToken. The very same credentials instance used to seed + * finalCredentials is then reused when calling setBearerToken, so prioritiseCredentials does + * NOT treat this as a credential change (that branch is covered by + * testPrioritiseCredentials_credentialChange_resetsTokenAndApiKey) - isolating the isExpired + * branch specifically. + */ + @Test + public void testSetBearerToken_expiredToken_regeneratesToken() throws SkyflowException { + BaseCredentials creds = new BaseCredentials(); + creds.setToken("x.eyJleHAiOjk5OTk5OTk5OTl9.y"); // far-future exp -> not expired once (re)generated + BaseVaultClient client = newClient(null); + + client.prioritiseCredentials(creds); + client.token = "x.eyJleHAiOjF9.y"; // exp=1 (1970) -> always expired, seeded directly + + client.setBearerToken(creds); + + Assert.assertEquals("x.eyJleHAiOjk5OTk5OTk5OTl9.y", client.token); + Assert.assertNotEquals("x.eyJleHAiOjF9.y", client.token); + } +} diff --git a/common/src/test/java/com/skyflow/config/BaseVaultConfigTests.java b/common/src/test/java/com/skyflow/config/BaseVaultConfigTests.java new file mode 100644 index 00000000..bea158b5 --- /dev/null +++ b/common/src/test/java/com/skyflow/config/BaseVaultConfigTests.java @@ -0,0 +1,75 @@ +package com.skyflow.config; + +import com.skyflow.enums.Env; +import org.junit.Assert; +import org.junit.Test; + +public class BaseVaultConfigTests { + + @Test + public void testDefaultConstructorDefaults() { + BaseVaultConfig config = new BaseVaultConfig(); + Assert.assertNull(config.getVaultId()); + Assert.assertNull(config.getClusterId()); + Assert.assertEquals(Env.PROD, config.getEnv()); + Assert.assertNull(config.getCredentials()); + } + + @Test + public void testGettersAndSetters() { + BaseVaultConfig config = new BaseVaultConfig(); + Credentials credentials = new Credentials(); + credentials.setToken("test_token"); + + config.setVaultId("vault_id"); + config.setClusterId("cluster_id"); + config.setEnv(Env.SANDBOX); + config.setCredentials(credentials); + + Assert.assertEquals("vault_id", config.getVaultId()); + Assert.assertEquals("cluster_id", config.getClusterId()); + Assert.assertEquals(Env.SANDBOX, config.getEnv()); + Assert.assertEquals(credentials, config.getCredentials()); + } + + @Test + public void testSetEnvNullFallsBackToProd() { + BaseVaultConfig config = new BaseVaultConfig(); + config.setEnv(Env.DEV); + config.setEnv(null); + Assert.assertEquals(Env.PROD, config.getEnv()); + } + + @Test + public void testCloneProducesIndependentCopy() throws CloneNotSupportedException { + BaseVaultConfig original = new BaseVaultConfig(); + original.setVaultId("vault_id"); + original.setClusterId("cluster_id"); + original.setEnv(Env.STAGE); + Credentials credentials = new Credentials(); + credentials.setToken("original_token"); + original.setCredentials(credentials); + + BaseVaultConfig cloned = (BaseVaultConfig) original.clone(); + + Assert.assertNotSame(original, cloned); + Assert.assertEquals(original.getVaultId(), cloned.getVaultId()); + Assert.assertEquals(original.getClusterId(), cloned.getClusterId()); + Assert.assertEquals(original.getEnv(), cloned.getEnv()); + Assert.assertNotSame(original.getCredentials(), cloned.getCredentials()); + Assert.assertEquals("original_token", cloned.getCredentials().getToken()); + + // Mutating the clone's credentials must not affect the original + cloned.getCredentials().setToken("mutated_token"); + Assert.assertEquals("original_token", original.getCredentials().getToken()); + } + + @Test + public void testCloneWithNullCredentials() throws CloneNotSupportedException { + BaseVaultConfig original = new BaseVaultConfig(); + original.setVaultId("vault_id"); + + BaseVaultConfig cloned = (BaseVaultConfig) original.clone(); + Assert.assertNull(cloned.getCredentials()); + } +} diff --git a/src/test/java/com/skyflow/config/CredentialsTests.java b/common/src/test/java/com/skyflow/config/CredentialsTests.java similarity index 89% rename from src/test/java/com/skyflow/config/CredentialsTests.java rename to common/src/test/java/com/skyflow/config/CredentialsTests.java index a9a9153f..cdf7fc3f 100644 --- a/src/test/java/com/skyflow/config/CredentialsTests.java +++ b/common/src/test/java/com/skyflow/config/CredentialsTests.java @@ -3,7 +3,7 @@ import com.skyflow.errors.ErrorCode; import com.skyflow.errors.ErrorMessage; import com.skyflow.errors.SkyflowException; -import com.skyflow.utils.validations.Validations; +import com.skyflow.utils.validations.BaseValidations; import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; @@ -13,8 +13,6 @@ import java.util.HashMap; import java.util.Map; -import com.skyflow.utils.Utils; - public class CredentialsTests { private static final String INVALID_EXCEPTION_THROWN = "Should not have thrown any exception"; private static final String EXCEPTION_NOT_THROWN = "Should have thrown an exception"; @@ -49,7 +47,7 @@ public void testValidCredentialsWithPath() { try { Credentials credentials = new Credentials(); credentials.setPath(path); - Validations.validateCredentials(credentials); + BaseValidations.validateCredentials(credentials); Assert.assertNull(credentials.getCredentialsString()); Assert.assertNull(credentials.getToken()); Assert.assertNull(credentials.getApiKey()); @@ -63,7 +61,7 @@ public void testValidCredentialsWithCredentialsString() { try { Credentials credentials = new Credentials(); credentials.setCredentialsString(credentialsString); - Validations.validateCredentials(credentials); + BaseValidations.validateCredentials(credentials); Assert.assertNull(credentials.getPath()); Assert.assertNull(credentials.getToken()); Assert.assertNull(credentials.getApiKey()); @@ -77,7 +75,7 @@ public void testValidCredentialsWithToken() { try { Credentials credentials = new Credentials(); credentials.setToken(token); - Validations.validateCredentials(credentials); + BaseValidations.validateCredentials(credentials); Assert.assertNull(credentials.getPath()); Assert.assertNull(credentials.getCredentialsString()); Assert.assertNull(credentials.getApiKey()); @@ -91,7 +89,7 @@ public void testValidCredentialsWithApikey() { try { Credentials credentials = new Credentials(); credentials.setApiKey(validApiKey); - Validations.validateCredentials(credentials); + BaseValidations.validateCredentials(credentials); Assert.assertNull(credentials.getPath()); Assert.assertNull(credentials.getCredentialsString()); Assert.assertNull(credentials.getToken()); @@ -108,7 +106,7 @@ public void testValidCredentialsWithRolesAndContext() { credentials.setApiKey(validApiKey); credentials.setRoles(roles); credentials.setContext(context); - Validations.validateCredentials(credentials); + BaseValidations.validateCredentials(credentials); Assert.assertNull(credentials.getPath()); Assert.assertNull(credentials.getCredentialsString()); Assert.assertNull(credentials.getToken()); @@ -124,7 +122,7 @@ public void testEmptyPathInCredentials() { try { Credentials credentials = new Credentials(); credentials.setPath(""); - Validations.validateCredentials(credentials); + BaseValidations.validateCredentials(credentials); Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); @@ -137,7 +135,7 @@ public void testEmptyCredentialsStringInCredentials() { try { Credentials credentials = new Credentials(); credentials.setCredentialsString(""); - Validations.validateCredentials(credentials); + BaseValidations.validateCredentials(credentials); Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); @@ -150,7 +148,7 @@ public void testEmptyTokenInCredentials() { try { Credentials credentials = new Credentials(); credentials.setToken(""); - Validations.validateCredentials(credentials); + BaseValidations.validateCredentials(credentials); Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); @@ -163,7 +161,7 @@ public void testEmptyApikeyInCredentials() { try { Credentials credentials = new Credentials(); credentials.setApiKey(""); - Validations.validateCredentials(credentials); + BaseValidations.validateCredentials(credentials); Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); @@ -176,7 +174,7 @@ public void testInvalidApikeyInCredentials() { try { Credentials credentials = new Credentials(); credentials.setApiKey(invalidApiKey); - Validations.validateCredentials(credentials); + BaseValidations.validateCredentials(credentials); Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); @@ -190,7 +188,7 @@ public void testBothTokenAndPathInCredentials() { Credentials credentials = new Credentials(); credentials.setPath(path); credentials.setToken(token); - Validations.validateCredentials(credentials); + BaseValidations.validateCredentials(credentials); Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); @@ -202,7 +200,7 @@ public void testBothTokenAndPathInCredentials() { public void testNothingPassedInCredentials() { try { Credentials credentials = new Credentials(); - Validations.validateCredentials(credentials); + BaseValidations.validateCredentials(credentials); Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); @@ -216,7 +214,7 @@ public void testEmptyRolesInCredentials() { Credentials credentials = new Credentials(); credentials.setPath(path); credentials.setRoles(roles); - Validations.validateCredentials(credentials); + BaseValidations.validateCredentials(credentials); Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); @@ -232,7 +230,7 @@ public void testNullRoleInRolesInCredentials() { roles.add(role); roles.add(null); credentials.setRoles(roles); - Validations.validateCredentials(credentials); + BaseValidations.validateCredentials(credentials); Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); @@ -248,7 +246,7 @@ public void testEmptyRoleInRolesInCredentials() { roles.add(role); roles.add(""); credentials.setRoles(roles); - Validations.validateCredentials(credentials); + BaseValidations.validateCredentials(credentials); Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); @@ -262,7 +260,7 @@ public void testEmptyContextInCredentials() { Credentials credentials = new Credentials(); credentials.setPath(path); credentials.setContext(""); - Validations.validateCredentials(credentials); + BaseValidations.validateCredentials(credentials); Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); @@ -280,7 +278,7 @@ public void testValidMapContextInCredentials() { ctxMap.put("department", "finance"); ctxMap.put("user_id", "user_12345"); credentials.setContext(ctxMap); - Validations.validateCredentials(credentials); + BaseValidations.validateCredentials(credentials); } catch (SkyflowException e) { Assert.fail(INVALID_EXCEPTION_THROWN); } @@ -293,7 +291,7 @@ public void testEmptyMapContextInCredentials() { credentials.setPath(path); Map ctxMap = new HashMap<>(); credentials.setContext(ctxMap); - Validations.validateCredentials(credentials); + BaseValidations.validateCredentials(credentials); Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); @@ -310,7 +308,7 @@ public void testInvalidMapKeyInContextCredentials() { ctxMap.put("valid_key", "value"); ctxMap.put("invalid-key", "value"); credentials.setContext(ctxMap); - Validations.validateCredentials(credentials); + BaseValidations.validateCredentials(credentials); Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); @@ -329,7 +327,7 @@ public void testMapContextWithNestedObjects() { ctxMap.put("role", "admin"); ctxMap.put("metadata", nested); credentials.setContext(ctxMap); - Validations.validateCredentials(credentials); + BaseValidations.validateCredentials(credentials); } catch (SkyflowException e) { Assert.fail(INVALID_EXCEPTION_THROWN); } @@ -346,7 +344,7 @@ public void testMapContextWithMixedValueTypes() { ctxMap.put("active", true); ctxMap.put("timestamp", "2025-12-25T10:30:00Z"); credentials.setContext(ctxMap); - Validations.validateCredentials(credentials); + BaseValidations.validateCredentials(credentials); } catch (SkyflowException e) { Assert.fail(INVALID_EXCEPTION_THROWN); } diff --git a/src/test/java/com/skyflow/errors/SkyflowExceptionTest.java b/common/src/test/java/com/skyflow/errors/SkyflowExceptionTest.java similarity index 100% rename from src/test/java/com/skyflow/errors/SkyflowExceptionTest.java rename to common/src/test/java/com/skyflow/errors/SkyflowExceptionTest.java diff --git a/src/test/java/com/skyflow/serviceaccount/util/BearerTokenTests.java b/common/src/test/java/com/skyflow/serviceaccount/util/BearerTokenTests.java similarity index 76% rename from src/test/java/com/skyflow/serviceaccount/util/BearerTokenTests.java rename to common/src/test/java/com/skyflow/serviceaccount/util/BearerTokenTests.java index 3aeca811..fb5a2c32 100644 --- a/src/test/java/com/skyflow/serviceaccount/util/BearerTokenTests.java +++ b/common/src/test/java/com/skyflow/serviceaccount/util/BearerTokenTests.java @@ -1,16 +1,23 @@ package com.skyflow.serviceaccount.util; +import com.google.gson.Gson; +import com.google.gson.JsonObject; import com.skyflow.errors.ErrorCode; import com.skyflow.errors.ErrorMessage; import com.skyflow.errors.SkyflowException; -import com.skyflow.utils.Constants; -import com.skyflow.utils.Utils; +import com.skyflow.generated.auth.rest.core.ApiClientException; +import com.skyflow.utils.BaseConstants; +import com.skyflow.utils.BaseUtils; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import java.io.File; +import java.io.IOException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; import java.util.ArrayList; +import java.util.Base64; import java.util.HashMap; import java.util.Map; @@ -92,7 +99,7 @@ public void testEmptyCredentialsFilePath() { } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); Assert.assertEquals( - Utils.parameterizedString(ErrorMessage.FileNotFound.getMessage(), ""), e.getMessage() + BaseUtils.parameterizedString(ErrorMessage.FileNotFound.getMessage(), ""), e.getMessage() ); } } @@ -107,7 +114,7 @@ public void testInvalidFilePath() { } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); Assert.assertEquals( - Utils.parameterizedString(ErrorMessage.FileNotFound.getMessage(), invalidFilePath), + BaseUtils.parameterizedString(ErrorMessage.FileNotFound.getMessage(), invalidFilePath), e.getMessage() ); } @@ -123,7 +130,7 @@ public void testInvalidCredentialsFile() { } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); Assert.assertEquals( - Utils.parameterizedString(ErrorMessage.FileInvalidJson.getMessage(), invalidJsonFilePath), + BaseUtils.parameterizedString(ErrorMessage.FileInvalidJson.getMessage(), invalidJsonFilePath), e.getMessage() ); } @@ -264,8 +271,58 @@ public void testBearerTokenWithNewFormCredentialKeys() { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); // InvalidKeySpec confirms all credential fields were resolved — failure is at RSA parsing, not field lookup Assert.assertEquals( - Utils.parameterizedString(ErrorMessage.InvalidKeySpec.getMessage(), Constants.SDK_PREFIX), + BaseUtils.parameterizedString(ErrorMessage.InvalidKeySpec.getMessage(), BaseConstants.SDK_PREFIX), e.getMessage()); } } + + /** + * Generates a real, valid PKCS#8-encoded RSA private key PEM string, using the same + * header/footer BaseUtils.getPrivateKeyFromPem expects, so tests can exercise JWT + * signing (getSignedToken()) instead of always failing at key parsing. + */ + private static String generateValidPkcs8PrivateKeyPem() throws Exception { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(2048); + KeyPair keyPair = keyPairGenerator.generateKeyPair(); + String base64EncodedKey = Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded()); + return BaseConstants.PKCS8_PRIVATE_HEADER + "\n" + base64EncodedKey + "\n" + BaseConstants.PKCS8_PRIVATE_FOOTER; + } + + @Test + public void testGetSignedTokenAndScopeUsingRolesExecuteWithValidKeyRolesAndContext() { + try { + String privateKeyPem = generateValidPkcs8PrivateKeyPem(); + ArrayList testRoles = new ArrayList<>(); + testRoles.add("test_role_one"); + testRoles.add("test_role_two"); + Map ctxMap = new HashMap<>(); + ctxMap.put("role", "admin"); + ctxMap.put("department", "finance"); + + JsonObject credentials = new JsonObject(); + credentials.addProperty("privateKey", privateKeyPem); + credentials.addProperty("clientId", "client_id_value"); + credentials.addProperty("keyId", "key_id_value"); + // Syntactically valid but unreachable, so failure surfaces at the network call, + // proving getSignedToken() (with the ctx claim) and getScopeUsingRoles() (roles != null) + // both executed successfully first. + credentials.addProperty("tokenUri", "https://localhost:1"); + String credentialsString = new Gson().toJson(credentials); + + BearerToken bearerToken = BearerToken.builder() + .setCredentials(credentialsString) + .setCtx(ctxMap) + .setRoles(testRoles) + .build(); + bearerToken.getBearerToken(); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (ApiClientException e) { + // Reaching this network-layer exception (rather than a SkyflowException from key + // parsing) confirms getSignedToken() and getScopeUsingRoles() both ran successfully. + Assert.assertTrue(e.getCause() instanceof IOException); + } catch (Exception e) { + Assert.fail(INVALID_EXCEPTION_THROWN + ": " + e); + } + } } diff --git a/src/test/java/com/skyflow/serviceaccount/util/SignedDataTokensTests.java b/common/src/test/java/com/skyflow/serviceaccount/util/SignedDataTokensTests.java similarity index 58% rename from src/test/java/com/skyflow/serviceaccount/util/SignedDataTokensTests.java rename to common/src/test/java/com/skyflow/serviceaccount/util/SignedDataTokensTests.java index 93d69b0e..79542476 100644 --- a/src/test/java/com/skyflow/serviceaccount/util/SignedDataTokensTests.java +++ b/common/src/test/java/com/skyflow/serviceaccount/util/SignedDataTokensTests.java @@ -1,16 +1,25 @@ package com.skyflow.serviceaccount.util; +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import com.skyflow.errors.ErrorCode; import com.skyflow.errors.ErrorMessage; import com.skyflow.errors.SkyflowException; -import com.skyflow.utils.Utils; +import com.skyflow.utils.BaseConstants; +import com.skyflow.utils.BaseUtils; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import java.io.File; +import java.nio.charset.StandardCharsets; +import java.security.KeyPair; +import java.security.KeyPairGenerator; import java.util.ArrayList; +import java.util.Base64; import java.util.HashMap; +import java.util.List; import java.util.Map; public class SignedDataTokensTests { @@ -85,7 +94,7 @@ public void testEmptyCredentialsFilePath() { Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); - Assert.assertEquals(Utils.parameterizedString(ErrorMessage.FileNotFound.getMessage(), ""), e.getMessage()); + Assert.assertEquals(BaseUtils.parameterizedString(ErrorMessage.FileNotFound.getMessage(), ""), e.getMessage()); } } @@ -99,7 +108,7 @@ public void testInvalidFilePath() { } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); Assert.assertEquals( - Utils.parameterizedString(ErrorMessage.FileNotFound.getMessage(), invalidFilePath), + BaseUtils.parameterizedString(ErrorMessage.FileNotFound.getMessage(), invalidFilePath), e.getMessage()); } } @@ -114,7 +123,7 @@ public void testInvalidCredentialsFile() { } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); Assert.assertEquals( - Utils.parameterizedString(ErrorMessage.FileInvalidJson.getMessage(), invalidJsonFilePath), + BaseUtils.parameterizedString(ErrorMessage.FileInvalidJson.getMessage(), invalidJsonFilePath), e.getMessage() ); } @@ -128,12 +137,10 @@ public void testEmptyCredentialsString() { Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); - Assert.assertEquals( - Utils.parameterizedString(ErrorMessage.InvalidCredentials.getMessage(), invalidJsonFilePath), - e.getMessage() - ); + // InvalidCredentials has no %s1 placeholder, so no extra arg is passed here. + Assert.assertEquals(ErrorMessage.InvalidCredentials.getMessage(), e.getMessage()); } catch (Exception e) { - System.out.println(e); + Assert.fail(INVALID_EXCEPTION_THROWN + ": " + e); } } @@ -145,10 +152,8 @@ public void testInvalidCredentialsString() { Assert.fail(EXCEPTION_NOT_THROWN); } catch (SkyflowException e) { Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); - Assert.assertEquals( - Utils.parameterizedString(ErrorMessage.CredentialsStringInvalidJson.getMessage(), invalidJsonFilePath), - e.getMessage() - ); + // CredentialsStringInvalidJson has no %s1 placeholder, so no extra arg is passed here. + Assert.assertEquals(ErrorMessage.CredentialsStringInvalidJson.getMessage(), e.getMessage()); } } @@ -258,4 +263,118 @@ public void testSignedDataTokenResponse() { Assert.fail(INVALID_EXCEPTION_THROWN); } } + + /** + * Generates a real, valid PKCS#8-encoded RSA private key PEM string, using the same + * header/footer BaseUtils.getPrivateKeyFromPem expects, so tests can exercise the actual + * JWT signing path (getSignedToken()) instead of always failing at key parsing. + */ + private static String generateValidPkcs8PrivateKeyPem() throws Exception { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(2048); + KeyPair keyPair = keyPairGenerator.generateKeyPair(); + String base64EncodedKey = Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded()); + return BaseConstants.PKCS8_PRIVATE_HEADER + "\n" + base64EncodedKey + "\n" + BaseConstants.PKCS8_PRIVATE_FOOTER; + } + + /** + * Decodes (without verifying) the payload segment of a compact JWT so tests can assert + * on its claims. These tokens are self-signed with a key generated in-test, so decoding + * the payload directly is sufficient to verify claim content. + */ + private static JsonObject decodeJwtPayload(String jwt) { + String[] parts = jwt.split("\\."); + byte[] payloadBytes = Base64.getUrlDecoder().decode(parts[1]); + return JsonParser.parseString(new String(payloadBytes, StandardCharsets.UTF_8)).getAsJsonObject(); + } + + @Test + public void testGetSignedDataTokensWithValidKeyMultipleTokensTimeToLiveAndContext() { + try { + String privateKeyPem = generateValidPkcs8PrivateKeyPem(); + ArrayList tokens = new ArrayList<>(); + tokens.add("data_token_one"); + tokens.add("data_token_two"); + Map ctxMap = new HashMap<>(); + ctxMap.put("role", "admin"); + ctxMap.put("department", "finance"); + + JsonObject credentials = new JsonObject(); + credentials.addProperty("privateKey", privateKeyPem); + credentials.addProperty("clientId", "client_id_value"); + credentials.addProperty("keyId", "key_id_value"); + String credentialsJsonString = new Gson().toJson(credentials); + + int timeToLiveSeconds = 120; + long beforeCreationEpochSeconds = System.currentTimeMillis() / 1000; + List responses = SignedDataTokens.builder() + .setCredentials(credentialsJsonString) + .setDataTokens(tokens) + .setTimeToLive(timeToLiveSeconds) + .setCtx(ctxMap) + .build() + .getSignedDataTokens(); + + Assert.assertEquals(tokens.size(), responses.size()); + for (int i = 0; i < tokens.size(); i++) { + SignedDataTokenResponse response = responses.get(i); + Assert.assertEquals(tokens.get(i), response.getToken()); + Assert.assertTrue(response.getSignedToken().startsWith(BaseConstants.SIGNED_DATA_TOKEN_PREFIX)); + + String signedJwt = response.getSignedToken().substring(BaseConstants.SIGNED_DATA_TOKEN_PREFIX.length()); + JsonObject payload = decodeJwtPayload(signedJwt); + Assert.assertEquals(tokens.get(i), payload.get("tok").getAsString()); + Assert.assertEquals("key_id_value", payload.get("key").getAsString()); + Assert.assertEquals("client_id_value", payload.get("sub").getAsString()); + Assert.assertEquals("sdk", payload.get("iss").getAsString()); + + Assert.assertTrue(payload.has("ctx")); + Assert.assertEquals("admin", payload.getAsJsonObject("ctx").get("role").getAsString()); + Assert.assertEquals("finance", payload.getAsJsonObject("ctx").get("department").getAsString()); + + long expirationEpochSeconds = payload.get("exp").getAsLong(); + long expectedExpirationEpochSeconds = beforeCreationEpochSeconds + timeToLiveSeconds; + // Small allowance for time elapsed during test execution + Assert.assertTrue(Math.abs(expirationEpochSeconds - expectedExpirationEpochSeconds) <= 5); + } + } catch (Exception e) { + Assert.fail(INVALID_EXCEPTION_THROWN + ": " + e); + } + } + + @Test + public void testGetSignedDataTokensWithValidKeyAndDefaultTimeToLive() { + try { + String privateKeyPem = generateValidPkcs8PrivateKeyPem(); + ArrayList tokens = new ArrayList<>(); + tokens.add("default_ttl_token"); + + JsonObject credentials = new JsonObject(); + credentials.addProperty("privateKey", privateKeyPem); + credentials.addProperty("clientId", "client_id_value"); + credentials.addProperty("keyId", "key_id_value"); + String credentialsJsonString = new Gson().toJson(credentials); + + long beforeCreationEpochSeconds = System.currentTimeMillis() / 1000; + List responses = SignedDataTokens.builder() + .setCredentials(credentialsJsonString) + .setDataTokens(tokens) + // timeToLive intentionally not set, exercising the default-60-second branch + .build() + .getSignedDataTokens(); + + Assert.assertEquals(1, responses.size()); + String signedJwt = responses.get(0).getSignedToken().substring(BaseConstants.SIGNED_DATA_TOKEN_PREFIX.length()); + JsonObject payload = decodeJwtPayload(signedJwt); + Assert.assertEquals(tokens.get(0), payload.get("tok").getAsString()); + // No context was set, so the "ctx" claim branch should be skipped entirely + Assert.assertFalse(payload.has("ctx")); + + long expirationEpochSeconds = payload.get("exp").getAsLong(); + long expectedExpirationEpochSeconds = beforeCreationEpochSeconds + 60; + Assert.assertTrue(Math.abs(expirationEpochSeconds - expectedExpirationEpochSeconds) <= 5); + } catch (Exception e) { + Assert.fail(INVALID_EXCEPTION_THROWN + ": " + e); + } + } } diff --git a/src/test/java/com/skyflow/serviceaccount/util/TokenTests.java b/common/src/test/java/com/skyflow/serviceaccount/util/TokenTests.java similarity index 94% rename from src/test/java/com/skyflow/serviceaccount/util/TokenTests.java rename to common/src/test/java/com/skyflow/serviceaccount/util/TokenTests.java index 88887681..cb631071 100644 --- a/src/test/java/com/skyflow/serviceaccount/util/TokenTests.java +++ b/common/src/test/java/com/skyflow/serviceaccount/util/TokenTests.java @@ -1,7 +1,7 @@ package com.skyflow.serviceaccount.util; -import com.skyflow.Skyflow; import com.skyflow.enums.LogLevel; +import com.skyflow.utils.logger.LogUtil; import io.github.cdimascio.dotenv.Dotenv; import org.junit.Assert; import org.junit.BeforeClass; @@ -12,7 +12,7 @@ public class TokenTests { @BeforeClass public static void setup() { - Skyflow skyflowClient = Skyflow.builder().setLogLevel(LogLevel.DEBUG).build(); + LogUtil.setupLogger(LogLevel.DEBUG); } @Test diff --git a/common/src/test/java/com/skyflow/utils/BaseUtilsTests.java b/common/src/test/java/com/skyflow/utils/BaseUtilsTests.java new file mode 100644 index 00000000..bc1bd216 --- /dev/null +++ b/common/src/test/java/com/skyflow/utils/BaseUtilsTests.java @@ -0,0 +1,244 @@ +package com.skyflow.utils; + +import com.google.gson.JsonObject; +import com.skyflow.config.Credentials; +import com.skyflow.enums.Env; +import com.skyflow.errors.ErrorCode; +import com.skyflow.errors.ErrorMessage; +import com.skyflow.errors.SkyflowException; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.net.MalformedURLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +public class BaseUtilsTests { + private static final String INVALID_EXCEPTION_THROWN = "Should not have thrown any exception"; + private static final String EXCEPTION_NOT_THROWN = "Should have thrown an exception"; + private static String clusterId = null; + private static String url = null; + private static String filePath = null; + private static String credentialsString = null; + private static String token = null; + private static String context = null; + private static ArrayList roles = null; + + @BeforeClass + public static void setup() { + clusterId = "test_cluster_id"; + url = "https://test-url.com/java/unit/tests"; + filePath = "invalid/file/path/credentials.json"; + credentialsString = "invalid credentials string"; + token = "invalid-token"; + context = "test_context"; + roles = new ArrayList<>(); + roles.add("test_role"); + } + + @Test + public void testGetVaultURLForDev() { + try { + String vaultURL = BaseUtils.getVaultURL(clusterId, Env.DEV, BaseConstants.V2_VAULT_DOMAIN); + String devUrl = "https://test_cluster_id.vault.skyflowapis.dev"; + Assert.assertEquals(devUrl, vaultURL); + } catch (Exception e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testGetVaultURLForStage() { + try { + String vaultURL = BaseUtils.getVaultURL(clusterId, Env.STAGE, BaseConstants.V2_VAULT_DOMAIN); + String stageUrl = "https://test_cluster_id.vault.skyflowapis.tech"; + Assert.assertEquals(stageUrl, vaultURL); + } catch (Exception e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testGetVaultURLForSandbox() { + try { + String vaultURL = BaseUtils.getVaultURL(clusterId, Env.SANDBOX, BaseConstants.V2_VAULT_DOMAIN); + String sandboxUrl = "https://test_cluster_id.vault.skyflowapis-preview.com"; + Assert.assertEquals(sandboxUrl, vaultURL); + } catch (Exception e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testGetVaultURLForProd() { + try { + String vaultURL = BaseUtils.getVaultURL(clusterId, Env.PROD, BaseConstants.V2_VAULT_DOMAIN); + String prodUrl = "https://test_cluster_id.vault.skyflowapis.com"; + Assert.assertEquals(prodUrl, vaultURL); + } catch (Exception e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testGetBaseURL() { + try { + String baseURL = BaseUtils.getBaseURL(url); + String expected = "https://test-url.com"; + Assert.assertEquals(expected, baseURL); + } catch (Exception e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testGenerateBearerTokenWithCredentialsFile() { + try { + Credentials credentials = new Credentials(); + credentials.setPath(filePath); + credentials.setContext(context); + credentials.setRoles(roles); + BaseUtils.generateBearerToken(credentials); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); + Assert.assertEquals( + BaseUtils.parameterizedString(ErrorMessage.FileNotFound.getMessage(), filePath), + e.getMessage() + ); + } + } + + @Test + public void testGenerateBearerTokenWithCredentialsString() { + try { + Credentials credentials = new Credentials(); + credentials.setCredentialsString(credentialsString); + credentials.setContext(context); + credentials.setRoles(roles); + BaseUtils.generateBearerToken(credentials); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); + Assert.assertEquals(ErrorMessage.CredentialsStringInvalidJson.getMessage(), e.getMessage()); + } + } + + @Test + public void testGenerateBearerTokenWithToken() { + try { + Credentials credentials = new Credentials(); + credentials.setToken(token); + credentials.setContext(context); + credentials.setRoles(roles); + String bearerToken = BaseUtils.generateBearerToken(credentials); + Assert.assertEquals(token, bearerToken); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testGetBaseURLWithMalformedURL() { + try { + BaseUtils.getBaseURL("not a url"); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (MalformedURLException e) { + // expected + } + } + + @Test + public void testGenerateBearerTokenWithCredentialsFileAndMapContext() { + try { + Map mapContext = new HashMap<>(); + mapContext.put("test_key", "test_value"); + Credentials credentials = new Credentials(); + credentials.setPath(filePath); + credentials.setContext(mapContext); + credentials.setRoles(roles); + BaseUtils.generateBearerToken(credentials); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (ClassCastException e) { + Assert.fail("Map context should not cause a ClassCastException"); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); + Assert.assertEquals( + BaseUtils.parameterizedString(ErrorMessage.FileNotFound.getMessage(), filePath), + e.getMessage() + ); + } + } + + @Test + public void testGenerateBearerTokenWithCredentialsStringAndMapContext() { + try { + Map mapContext = new HashMap<>(); + mapContext.put("test_key", "test_value"); + Credentials credentials = new Credentials(); + credentials.setCredentialsString(credentialsString); + credentials.setContext(mapContext); + credentials.setRoles(roles); + BaseUtils.generateBearerToken(credentials); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (ClassCastException e) { + Assert.fail("Map context should not cause a ClassCastException"); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); + Assert.assertEquals(ErrorMessage.CredentialsStringInvalidJson.getMessage(), e.getMessage()); + } + } + + @Test + public void testGenerateBearerTokenWithContextNeitherStringNorMap() { + try { + // context is left unset (null), which is neither a String nor a Map -- this should + // simply be skipped rather than throwing any type-related exception. + Credentials credentials = new Credentials(); + credentials.setCredentialsString(credentialsString); + credentials.setRoles(roles); + BaseUtils.generateBearerToken(credentials); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (ClassCastException e) { + Assert.fail("Non String/Map context should not cause a ClassCastException"); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); + Assert.assertEquals(ErrorMessage.CredentialsStringInvalidJson.getMessage(), e.getMessage()); + } + } + + @Test + public void testGetPrivateKeyFromPemWithMissingHeader() { + try { + BaseUtils.getPrivateKeyFromPem("this-is-not-a-pem-key-at-all"); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); + Assert.assertEquals(ErrorMessage.JwtInvalidFormat.getMessage(), e.getMessage()); + } + } + + @Test + public void testGetPrivateKeyFromPemWithInvalidBase64Body() { + String malformedPem = "-----BEGIN PRIVATE KEY-----\nnot-valid-base64!!!\n-----END PRIVATE KEY-----"; + try { + BaseUtils.getPrivateKeyFromPem(malformedPem); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (IllegalArgumentException e) { + Assert.fail("Invalid base64 content should be wrapped into a SkyflowException, not thrown raw"); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); + Assert.assertEquals(ErrorMessage.InvalidKeySpec.getMessage(), e.getMessage()); + } + } + + @Test + public void testGetCommonMetrics() { + JsonObject metrics = BaseUtils.getCommonMetrics(); + Assert.assertNotNull(metrics.get(BaseConstants.SDK_METRIC_CLIENT_DEVICE_MODEL)); + Assert.assertNotNull(metrics.get(BaseConstants.SDK_METRIC_RUNTIME_DETAILS)); + Assert.assertNotNull(metrics.get(BaseConstants.SDK_METRIC_CLIENT_OS_DETAILS)); + } +} diff --git a/src/test/java/com/skyflow/utils/logger/LogUtilLevelTests.java b/common/src/test/java/com/skyflow/utils/logger/LogUtilLevelTests.java similarity index 58% rename from src/test/java/com/skyflow/utils/logger/LogUtilLevelTests.java rename to common/src/test/java/com/skyflow/utils/logger/LogUtilLevelTests.java index 9754687a..11108e4b 100644 --- a/src/test/java/com/skyflow/utils/logger/LogUtilLevelTests.java +++ b/common/src/test/java/com/skyflow/utils/logger/LogUtilLevelTests.java @@ -96,4 +96,68 @@ public void testInfoLogSuppressedWhenLogLevelIsWarn() { .anyMatch(r -> r.getLevel().equals(Level.INFO)); Assert.assertFalse("INFO log should NOT appear when LogLevel is WARN", infoCaptured); } + + @Test + public void testDebugLogAppearsWhenLogLevelIsDebug() { + LogUtil.setupLogger(LogLevel.DEBUG); + CapturingHandler handler = attachCapture(); + + LogUtil.printDebugLog("debug message"); + + boolean debugCaptured = handler.records.stream() + .anyMatch(r -> r.getLevel().equals(Level.CONFIG) + && r.getMessage().contains("debug message")); + Assert.assertTrue("DEBUG log should appear when LogLevel is DEBUG", debugCaptured); + } + + @Test + public void testDebugLogSuppressedWhenLogLevelIsInfo() { + LogUtil.setupLogger(LogLevel.INFO); + CapturingHandler handler = attachCapture(); + + LogUtil.printDebugLog("suppressed debug message"); + + boolean debugCaptured = handler.records.stream() + .anyMatch(r -> r.getLevel().equals(Level.CONFIG)); + Assert.assertFalse("DEBUG log should NOT appear when LogLevel is INFO", debugCaptured); + } + + @Test + public void testErrorLogAppearsWhenLogLevelIsError() { + LogUtil.setupLogger(LogLevel.ERROR); + CapturingHandler handler = attachCapture(); + + LogUtil.printErrorLog("error message"); + + boolean errorCaptured = handler.records.stream() + .anyMatch(r -> r.getLevel().equals(Level.SEVERE) + && r.getMessage().contains("error message")); + Assert.assertTrue("ERROR log should appear when LogLevel is ERROR", errorCaptured); + } + + @Test + public void testErrorLogAppearsWhenLogLevelIsDebug() { + LogUtil.setupLogger(LogLevel.DEBUG); + CapturingHandler handler = attachCapture(); + + LogUtil.printErrorLog("debug level error message"); + + boolean errorCaptured = handler.records.stream() + .anyMatch(r -> r.getLevel().equals(Level.SEVERE) + && r.getMessage().contains("debug level error message")); + Assert.assertTrue("ERROR log should appear when LogLevel is DEBUG", errorCaptured); + } + + @Test + public void testNoLogsAppearWhenLogLevelIsOff() { + LogUtil.setupLogger(LogLevel.OFF); + CapturingHandler handler = attachCapture(); + + LogUtil.printErrorLog("off error message"); + LogUtil.printWarningLog("off warning message"); + LogUtil.printInfoLog("off info message"); + LogUtil.printDebugLog("off debug message"); + + Assert.assertTrue("No logs should appear when LogLevel is OFF", handler.records.isEmpty()); + } } diff --git a/common/src/test/java/com/skyflow/vault/data/BaseDetokenizeDataTests.java b/common/src/test/java/com/skyflow/vault/data/BaseDetokenizeDataTests.java new file mode 100644 index 00000000..ba75144c --- /dev/null +++ b/common/src/test/java/com/skyflow/vault/data/BaseDetokenizeDataTests.java @@ -0,0 +1,24 @@ +package com.skyflow.vault.data; + +import org.junit.Assert; +import org.junit.Test; + +public class BaseDetokenizeDataTests { + + @Test + public void testInstantiationDoesNotThrow() { + BaseDetokenizeData data = new BaseDetokenizeData(); + + Assert.assertNotNull(data); + } + + @Test + public void testUsableAsExtensionPointForSubclasses() { + // BaseDetokenizeData carries no state of its own; it exists purely so module-specific + // classes (v2's DetokenizeData, flowvault's TokenGroupRedactions) share a supertype. + BaseDetokenizeData data = new BaseDetokenizeData() { + }; + + Assert.assertTrue(data instanceof BaseDetokenizeData); + } +} diff --git a/common/src/test/java/com/skyflow/vault/data/BaseDetokenizeRecordResponseTests.java b/common/src/test/java/com/skyflow/vault/data/BaseDetokenizeRecordResponseTests.java new file mode 100644 index 00000000..52992e46 --- /dev/null +++ b/common/src/test/java/com/skyflow/vault/data/BaseDetokenizeRecordResponseTests.java @@ -0,0 +1,39 @@ +package com.skyflow.vault.data; + +import org.junit.Assert; +import org.junit.Test; + +public class BaseDetokenizeRecordResponseTests { + + @Test + public void testGettersReturnConstructorValuesOnSuccess() { + BaseDetokenizeRecordResponse response = new BaseDetokenizeRecordResponse("token-value", null); + + Assert.assertEquals("token-value", response.getToken()); + Assert.assertNull(response.getError()); + } + + @Test + public void testGettersReturnConstructorValuesOnError() { + BaseDetokenizeRecordResponse response = new BaseDetokenizeRecordResponse(null, "some error"); + + Assert.assertNull(response.getToken()); + Assert.assertEquals("some error", response.getError()); + } + + @Test + public void testBothTokenAndErrorNull() { + BaseDetokenizeRecordResponse response = new BaseDetokenizeRecordResponse(null, null); + + Assert.assertNull(response.getToken()); + Assert.assertNull(response.getError()); + } + + @Test + public void testBothTokenAndErrorPopulated() { + BaseDetokenizeRecordResponse response = new BaseDetokenizeRecordResponse("token-value", "some error"); + + Assert.assertEquals("token-value", response.getToken()); + Assert.assertEquals("some error", response.getError()); + } +} diff --git a/common/src/test/java/com/skyflow/vault/data/BaseDetokenizeRequestTests.java b/common/src/test/java/com/skyflow/vault/data/BaseDetokenizeRequestTests.java new file mode 100644 index 00000000..0b414353 --- /dev/null +++ b/common/src/test/java/com/skyflow/vault/data/BaseDetokenizeRequestTests.java @@ -0,0 +1,24 @@ +package com.skyflow.vault.data; + +import org.junit.Assert; +import org.junit.Test; + +public class BaseDetokenizeRequestTests { + + @Test + public void testInstantiationDoesNotThrow() { + BaseDetokenizeRequest request = new BaseDetokenizeRequest(); + + Assert.assertNotNull(request); + } + + @Test + public void testUsableAsExtensionPointForSubclasses() { + // BaseDetokenizeRequest carries no state of its own; it exists purely so module-specific + // DetokenizeRequest classes (e.g. flowvault's) can extend it. Verify the subtype relationship holds. + BaseDetokenizeRequest request = new BaseDetokenizeRequest() { + }; + + Assert.assertTrue(request instanceof BaseDetokenizeRequest); + } +} diff --git a/common/src/test/java/com/skyflow/vault/data/BaseInsertRequestTests.java b/common/src/test/java/com/skyflow/vault/data/BaseInsertRequestTests.java new file mode 100644 index 00000000..5b59e261 --- /dev/null +++ b/common/src/test/java/com/skyflow/vault/data/BaseInsertRequestTests.java @@ -0,0 +1,24 @@ +package com.skyflow.vault.data; + +import org.junit.Assert; +import org.junit.Test; + +public class BaseInsertRequestTests { + + @Test + public void testInstantiationDoesNotThrow() { + BaseInsertRequest request = new BaseInsertRequest(); + + Assert.assertNotNull(request); + } + + @Test + public void testUsableAsExtensionPointForSubclasses() { + // BaseInsertRequest carries no state of its own; it exists purely so module-specific + // InsertRequest classes (v2's and flowvault's) share a supertype. Verify the subtype relationship holds. + BaseInsertRequest request = new BaseInsertRequest() { + }; + + Assert.assertTrue(request instanceof BaseInsertRequest); + } +} diff --git a/common/src/test/java/com/skyflow/vault/data/BaseInsertResponseTests.java b/common/src/test/java/com/skyflow/vault/data/BaseInsertResponseTests.java new file mode 100644 index 00000000..182d0d05 --- /dev/null +++ b/common/src/test/java/com/skyflow/vault/data/BaseInsertResponseTests.java @@ -0,0 +1,24 @@ +package com.skyflow.vault.data; + +import org.junit.Assert; +import org.junit.Test; + +public class BaseInsertResponseTests { + + @Test + public void testInstantiationDoesNotThrow() { + BaseInsertResponse response = new BaseInsertResponse(); + + Assert.assertNotNull(response); + } + + @Test + public void testUsableAsExtensionPointForSubclasses() { + // BaseInsertResponse carries no state of its own; it exists purely so module-specific + // InsertResponse classes (v2's and flowvault's) share a supertype. Verify the subtype relationship holds. + BaseInsertResponse response = new BaseInsertResponse() { + }; + + Assert.assertTrue(response instanceof BaseInsertResponse); + } +} diff --git a/common/src/test/java/com/skyflow/vault/data/RequestContextTests.java b/common/src/test/java/com/skyflow/vault/data/RequestContextTests.java new file mode 100644 index 00000000..0c6700d8 --- /dev/null +++ b/common/src/test/java/com/skyflow/vault/data/RequestContextTests.java @@ -0,0 +1,68 @@ +package com.skyflow.vault.data; + +import com.skyflow.enums.CustomHeaderKey; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Map; + +public class RequestContextTests { + + @Test + public void testGetOperationReturnsConstructorValue() { + RequestContext context = new RequestContext("INSERT"); + + Assert.assertEquals("INSERT", context.getOperation()); + } + + @Test + public void testNullOperation() { + RequestContext context = new RequestContext(null); + + Assert.assertNull(context.getOperation()); + } + + @Test + public void testGetHeadersReturnsEmptyMapByDefault() { + RequestContext context = new RequestContext("INSERT"); + + Assert.assertTrue(context.getHeaders().isEmpty()); + } + + @Test + public void testAddHeaderIsReflectedInGetHeaders() { + RequestContext context = new RequestContext("INSERT"); + context.addHeader(CustomHeaderKey.SkyflowAccountId, "account-id-value"); + + Map headers = context.getHeaders(); + + Assert.assertEquals(1, headers.size()); + Assert.assertEquals("account-id-value", headers.get(CustomHeaderKey.SkyflowAccountId)); + } + + @Test + public void testAddHeaderOverwritesExistingValueForSameKey() { + RequestContext context = new RequestContext("INSERT"); + context.addHeader(CustomHeaderKey.SkyflowAccountId, "first-value"); + context.addHeader(CustomHeaderKey.SkyflowAccountId, "second-value"); + + Assert.assertEquals(1, context.getHeaders().size()); + Assert.assertEquals("second-value", context.getHeaders().get(CustomHeaderKey.SkyflowAccountId)); + } + + @Test + public void testAddMultipleDistinctHeaders() { + RequestContext context = new RequestContext("DETOKENIZE"); + context.addHeader(CustomHeaderKey.SkyflowAccountId, "account-id-value"); + context.addHeader(CustomHeaderKey.SkyflowAccountName, "account-name-value"); + + Assert.assertEquals(2, context.getHeaders().size()); + } + + @Test(expected = UnsupportedOperationException.class) + public void testGetHeadersReturnsUnmodifiableMap() { + RequestContext context = new RequestContext("INSERT"); + + context.getHeaders().put(CustomHeaderKey.RequestIdHeader, "request-id-value"); + } +} diff --git a/common/src/test/java/com/skyflow/vault/data/RequestInterceptorTests.java b/common/src/test/java/com/skyflow/vault/data/RequestInterceptorTests.java new file mode 100644 index 00000000..d7bd3193 --- /dev/null +++ b/common/src/test/java/com/skyflow/vault/data/RequestInterceptorTests.java @@ -0,0 +1,28 @@ +package com.skyflow.vault.data; + +import com.skyflow.enums.CustomHeaderKey; +import org.junit.Assert; +import org.junit.Test; + +public class RequestInterceptorTests { + + @Test + public void testInterceptMutatesRequestContext() { + RequestInterceptor interceptor = context -> context.addHeader(CustomHeaderKey.SkyflowAccountId, "account-id-value"); + RequestContext context = new RequestContext("INSERT"); + + interceptor.intercept(context); + + Assert.assertEquals("account-id-value", context.getHeaders().get(CustomHeaderKey.SkyflowAccountId)); + } + + @Test + public void testInterceptorIsFunctionalInterfaceUsableAsLambda() { + final boolean[] invoked = {false}; + RequestInterceptor interceptor = context -> invoked[0] = true; + + interceptor.intercept(new RequestContext("DETOKENIZE")); + + Assert.assertTrue(invoked[0]); + } +} diff --git a/src/test/resources/invalidPrivateKeyCredentials.json b/common/src/test/resources/invalidPrivateKeyCredentials.json similarity index 100% rename from src/test/resources/invalidPrivateKeyCredentials.json rename to common/src/test/resources/invalidPrivateKeyCredentials.json diff --git a/src/test/resources/invalidTokenURICredentials.json b/common/src/test/resources/invalidTokenURICredentials.json similarity index 100% rename from src/test/resources/invalidTokenURICredentials.json rename to common/src/test/resources/invalidTokenURICredentials.json diff --git a/src/test/resources/noClientIDCredentials.json b/common/src/test/resources/noClientIDCredentials.json similarity index 100% rename from src/test/resources/noClientIDCredentials.json rename to common/src/test/resources/noClientIDCredentials.json diff --git a/src/test/resources/noKeyIDCredentials.json b/common/src/test/resources/noKeyIDCredentials.json similarity index 100% rename from src/test/resources/noKeyIDCredentials.json rename to common/src/test/resources/noKeyIDCredentials.json diff --git a/src/test/resources/noPrivateKeyCredentials.json b/common/src/test/resources/noPrivateKeyCredentials.json similarity index 100% rename from src/test/resources/noPrivateKeyCredentials.json rename to common/src/test/resources/noPrivateKeyCredentials.json diff --git a/src/test/resources/noTokenURICredentials.json b/common/src/test/resources/noTokenURICredentials.json similarity index 100% rename from src/test/resources/noTokenURICredentials.json rename to common/src/test/resources/noTokenURICredentials.json diff --git a/src/test/resources/notJson.txt b/common/src/test/resources/notJson.txt similarity index 100% rename from src/test/resources/notJson.txt rename to common/src/test/resources/notJson.txt diff --git a/flowvault/dependency-reduced-pom.xml b/flowvault/dependency-reduced-pom.xml new file mode 100644 index 00000000..92fc11f2 --- /dev/null +++ b/flowvault/dependency-reduced-pom.xml @@ -0,0 +1,195 @@ + + + + skyflow + com.skyflow + 1.0.0 + + 4.0.0 + skyflow-flowvault-java + ${project.groupId}:${project.artifactId} + 1.0.0 + Skyflow V3 SDK for the Java programming language + https://github.com/skyflowapi/skyflow-java/tree/main + + + skyflow + skyflow + + + + + The MIT License (MIT) + https://github.com/skyflowapi/skyflow-java/blob/main/LICENSE + + + + + + true + src/main/resources + + + + + maven-shade-plugin + 3.6.0 + + + package + + shade + + + + + com.skyflow:common + + + + + + + + + + + jfrog + + + central + prekarilabs.jfrog.io-releases + https://prekarilabs.jfrog.io/artifactory/skyflow-java + + + snapshots + prekarilabs.jfrog.io-snapshots + https://prekarilabs.jfrog.io/artifactory/skyflow-java + + + + + maven-central + + + + org.sonatype.central + central-publishing-maven-plugin + 0.4.0 + true + + central + true + true + + + + + + + central + https://central.sonatype.com/api/v1/publisher/upload + + + central-snapshots + https://central.sonatype.com/api/v1/publisher/upload + + + + + + + com.fasterxml.jackson.core + jackson-databind + 2.17.2 + compile + + + com.fasterxml.jackson.datatype + jackson-datatype-jdk8 + 2.18.6 + compile + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + 2.18.6 + compile + + + io.github.cdimascio + dotenv-java + 2.2.0 + compile + + + com.google.code.gson + gson + 2.10.1 + compile + + + com.squareup.okhttp3 + okhttp + 4.12.0 + compile + + + io.jsonwebtoken + jjwt + 0.12.6 + compile + + + junit + junit + 4.13.2 + test + + + hamcrest-core + org.hamcrest + + + + + org.powermock + powermock-module-junit4 + 2.0.9 + test + + + powermock-module-junit4-common + org.powermock + + + hamcrest-core + org.hamcrest + + + + + org.powermock + powermock-api-mockito2 + 2.0.9 + test + + + powermock-api-support + org.powermock + + + mockito-core + org.mockito + + + + + + UTF-8 + 8 + false + 8 + ${project.version} + + diff --git a/flowvault/pom.xml b/flowvault/pom.xml new file mode 100644 index 00000000..9b34fa4d --- /dev/null +++ b/flowvault/pom.xml @@ -0,0 +1,129 @@ + + + 4.0.0 + + com.skyflow + skyflow + 1.0.0 + ../pom.xml + + + skyflow-flowvault-java + 3.0.0-beta.13-dev.f72e2218 + jar + ${project.groupId}:${project.artifactId} + Skyflow V3 SDK for the Java programming language + https://github.com/skyflowapi/skyflow-java/tree/main + + + + The MIT License (MIT) + https://github.com/skyflowapi/skyflow-java/blob/main/LICENSE + + + + + skyflow + skyflow + + + + + 8 + 8 + UTF-8 + false + ${project.version} + + + + + + + com.skyflow + common + 1.0.0 + compile + + + + + + + src/main/resources + true + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.6.0 + + + package + + shade + + + + + com.skyflow:common + + + + + + + + + + + + jfrog + + + central + prekarilabs.jfrog.io-releases + https://prekarilabs.jfrog.io/artifactory/skyflow-java + + + snapshots + prekarilabs.jfrog.io-snapshots + https://prekarilabs.jfrog.io/artifactory/skyflow-java + + + + + maven-central + + + central + https://central.sonatype.com/api/v1/publisher/upload + + + central-snapshots + https://central.sonatype.com/api/v1/publisher/upload + + + + + + org.sonatype.central + central-publishing-maven-plugin + 0.4.0 + true + + central + true + true + + + + + + + + diff --git a/samples/README.md b/flowvault/samples/README.md similarity index 100% rename from samples/README.md rename to flowvault/samples/README.md diff --git a/flowvault/samples/pom.xml b/flowvault/samples/pom.xml new file mode 100644 index 00000000..390c0251 --- /dev/null +++ b/flowvault/samples/pom.xml @@ -0,0 +1,41 @@ + + + 4.0.0 + + org.example + skyflow-javasdk-sample + 1.0.0 + + + 8 + 8 + + + + + + com.skyflow + skyflow-flowvault-java + 3.0.0-beta.13-dev.0d4db0a + + + + + + central + https://prekarilabs.jfrog.io/artifactory/skyflow-java + + false + + + + maven_central + Maven Central + https://repo.maven.apache.org/maven2/ + + + + + \ No newline at end of file diff --git a/samples/src/main/java/com/example/serviceaccount/BearerTokenExpiryExample.java b/flowvault/samples/src/main/java/com/example/serviceaccount/BearerTokenExpiryExample.java similarity index 100% rename from samples/src/main/java/com/example/serviceaccount/BearerTokenExpiryExample.java rename to flowvault/samples/src/main/java/com/example/serviceaccount/BearerTokenExpiryExample.java diff --git a/samples/src/main/java/com/example/serviceaccount/BearerTokenGenerationExample.java b/flowvault/samples/src/main/java/com/example/serviceaccount/BearerTokenGenerationExample.java similarity index 100% rename from samples/src/main/java/com/example/serviceaccount/BearerTokenGenerationExample.java rename to flowvault/samples/src/main/java/com/example/serviceaccount/BearerTokenGenerationExample.java diff --git a/samples/src/main/java/com/example/serviceaccount/BearerTokenGenerationUsingThreadsExample.java b/flowvault/samples/src/main/java/com/example/serviceaccount/BearerTokenGenerationUsingThreadsExample.java similarity index 100% rename from samples/src/main/java/com/example/serviceaccount/BearerTokenGenerationUsingThreadsExample.java rename to flowvault/samples/src/main/java/com/example/serviceaccount/BearerTokenGenerationUsingThreadsExample.java diff --git a/samples/src/main/java/com/example/serviceaccount/BearerTokenGenerationWithContextExample.java b/flowvault/samples/src/main/java/com/example/serviceaccount/BearerTokenGenerationWithContextExample.java similarity index 100% rename from samples/src/main/java/com/example/serviceaccount/BearerTokenGenerationWithContextExample.java rename to flowvault/samples/src/main/java/com/example/serviceaccount/BearerTokenGenerationWithContextExample.java diff --git a/samples/src/main/java/com/example/serviceaccount/ScopedTokenGenerationExample.java b/flowvault/samples/src/main/java/com/example/serviceaccount/ScopedTokenGenerationExample.java similarity index 100% rename from samples/src/main/java/com/example/serviceaccount/ScopedTokenGenerationExample.java rename to flowvault/samples/src/main/java/com/example/serviceaccount/ScopedTokenGenerationExample.java diff --git a/samples/src/main/java/com/example/serviceaccount/SignedTokenGenerationExample.java b/flowvault/samples/src/main/java/com/example/serviceaccount/SignedTokenGenerationExample.java similarity index 100% rename from samples/src/main/java/com/example/serviceaccount/SignedTokenGenerationExample.java rename to flowvault/samples/src/main/java/com/example/serviceaccount/SignedTokenGenerationExample.java diff --git a/flowvault/samples/src/main/java/com/example/vault/BulkDetokenizeAsync.java b/flowvault/samples/src/main/java/com/example/vault/BulkDetokenizeAsync.java new file mode 100644 index 00000000..c6354fe8 --- /dev/null +++ b/flowvault/samples/src/main/java/com/example/vault/BulkDetokenizeAsync.java @@ -0,0 +1,82 @@ +package com.example.vault; + +import com.skyflow.Skyflow; +import com.skyflow.config.Credentials; +import com.skyflow.config.VaultConfig; +import com.skyflow.enums.Env; +import com.skyflow.enums.LogLevel; +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.data.BulkDetokenizeRequest; +import com.skyflow.vault.data.BulkDetokenizeResponse; +import com.skyflow.vault.data.TokenGroupRedactions; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +/** + * This sample demonstrates how to perform an asynchronous bulk detokenize operation using the Skyflow Java SDK. + * The process involves: + * 1. Setting up credentials and vault configuration + * 2. Creating a list of tokens to detokenize + * 3. Configuring token group redactions + * 4. Building and executing an async bulk detokenize request + * 5. Handling the detokenize response or errors using CompletableFuture + */ +public class BulkDetokenizeAsync { + + public static void main(String[] args) { + try { + // Step 1: Initialize credentials using credentials string + String credentialsString = ""; + Credentials credentials = new Credentials(); + credentials.setCredentialsString(credentialsString); + + // Step 2: Configure the vault with required parameters + VaultConfig vaultConfig = new VaultConfig(); + vaultConfig.setVaultId(""); + vaultConfig.setClusterId(""); + vaultConfig.setEnv(Env.PROD); + vaultConfig.setCredentials(credentials); + + // Step 3: Create Skyflow client instance with error logging + Skyflow skyflowClient = Skyflow.builder() + .setLogLevel(LogLevel.ERROR) + .addVaultConfig(vaultConfig) + .build(); + + // Step 4: Prepare list of tokens to detokenize + List tokens = new ArrayList<>(); + tokens.add(""); + tokens.add(""); + + // Step 5: Configure token group redactions + TokenGroupRedactions tokenGroupRedaction = TokenGroupRedactions.builder() + .tokenGroupName("") + .redaction("") + .build(); + List tokenGroupRedactions = new ArrayList<>(); + tokenGroupRedactions.add(tokenGroupRedaction); + + // Step 6: Build the detokenize request + BulkDetokenizeRequest detokenizeRequest = BulkDetokenizeRequest.builder() + .tokens(tokens) + .tokenGroupRedactions(tokenGroupRedactions) + .build(); + + // Step 7: Execute the async bulk detokenize operation and handle response using callbacks + CompletableFuture future = + skyflowClient.vault().bulkDetokenizeAsync(detokenizeRequest); + future.thenAccept(response -> { + System.out.println("Async bulk detokenize resolved with response:\t" + response); + }).exceptionally(throwable -> { + System.err.println("Async bulk detokenize rejected with error:\t" + throwable.getMessage()); + throw new CompletionException(throwable); + }); + } catch (SkyflowException e) { + // Step 8: Handle any synchronous errors that occur during setup + System.err.println("Error in Skyflow operations: " + e.getMessage()); + } + } +} diff --git a/flowvault/samples/src/main/java/com/example/vault/BulkDetokenizeSync.java b/flowvault/samples/src/main/java/com/example/vault/BulkDetokenizeSync.java new file mode 100644 index 00000000..957c23ff --- /dev/null +++ b/flowvault/samples/src/main/java/com/example/vault/BulkDetokenizeSync.java @@ -0,0 +1,74 @@ +package com.example.vault; + +import com.skyflow.Skyflow; +import com.skyflow.config.Credentials; +import com.skyflow.config.VaultConfig; +import com.skyflow.enums.Env; +import com.skyflow.enums.LogLevel; +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.data.BulkDetokenizeRequest; +import com.skyflow.vault.data.BulkDetokenizeResponse; +import com.skyflow.vault.data.TokenGroupRedactions; + +import java.util.ArrayList; +import java.util.List; + +/** + * This sample demonstrates how to perform a synchronous bulk detokenize operation using the Skyflow Java SDK. + * The process involves: + * 1. Setting up credentials and vault configuration + * 2. Creating a list of tokens to detokenize + * 3. Configuring token group redactions + * 4. Building and executing a bulk detokenize request + * 5. Handling the detokenize response or any potential errors + */ +public class BulkDetokenizeSync { + + public static void main(String[] args) { + try { + // Step 1: Initialize credentials using credentials string + String credentialsString = ""; + Credentials credentials = new Credentials(); + credentials.setCredentialsString(credentialsString); + + // Step 2: Configure the vault with required parameters + VaultConfig vaultConfig = new VaultConfig(); + vaultConfig.setVaultId(""); + vaultConfig.setClusterId(""); + vaultConfig.setEnv(Env.PROD); + vaultConfig.setCredentials(credentials); + + // Step 3: Create Skyflow client instance with error logging + Skyflow skyflowClient = Skyflow.builder() + .setLogLevel(LogLevel.ERROR) + .addVaultConfig(vaultConfig) + .build(); + + // Step 4: Prepare list of tokens to detokenize + List tokens = new ArrayList<>(); + tokens.add(""); + tokens.add(""); + + // Step 5: Configure token group redactions + TokenGroupRedactions tokenGroupRedaction = TokenGroupRedactions.builder() + .tokenGroupName("") + .redaction("") + .build(); + List tokenGroupRedactions = new ArrayList<>(); + tokenGroupRedactions.add(tokenGroupRedaction); + + // Step 6: Build the detokenize request + BulkDetokenizeRequest detokenizeRequest = BulkDetokenizeRequest.builder() + .tokens(tokens) + .tokenGroupRedactions(tokenGroupRedactions) + .build(); + + // Step 7: Execute the bulk detokenize operation and print the response + BulkDetokenizeResponse detokenizeResponse = skyflowClient.vault().bulkDetokenize(detokenizeRequest); + System.out.println(detokenizeResponse); + } catch (SkyflowException e) { + // Step 8: Handle any errors that occur during the process + System.err.println("Error in Skyflow operations: " + e.getMessage()); + } + } +} diff --git a/flowvault/samples/src/main/java/com/example/vault/BulkInsertAsync.java b/flowvault/samples/src/main/java/com/example/vault/BulkInsertAsync.java new file mode 100644 index 00000000..dff0deca --- /dev/null +++ b/flowvault/samples/src/main/java/com/example/vault/BulkInsertAsync.java @@ -0,0 +1,107 @@ +package com.example.vault; + +import com.skyflow.Skyflow; +import com.skyflow.config.Credentials; +import com.skyflow.config.VaultConfig; +import com.skyflow.enums.Env; +import com.skyflow.enums.LogLevel; +import com.skyflow.vault.data.BulkInsertRequest; +import com.skyflow.vault.data.BulkInsertRequestRecord; +import com.skyflow.vault.data.BulkInsertResponse; +import com.skyflow.vault.data.InsertRequestRecord; +import com.skyflow.vault.data.UpsertOptions; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +/** + * This sample demonstrates how to perform an asynchronous bulk insert operation using the Skyflow Java SDK. + * The process involves: + * 1. Setting up credentials and vault configuration + * 2. Creating multiple records to be inserted + * 3. Building and executing an async bulk insert request + * 4. Handling the insert response or errors using CompletableFuture + */ +public class BulkInsertAsync { + + public static void main(String[] args) { + try { + // Step 1: Initialize credentials with the path to your service account key file + String filePath = ""; + Credentials credentials = new Credentials(); + credentials.setPath(filePath); + + // Step 2: Configure the vault with required parameters + VaultConfig vaultConfig = new VaultConfig(); + vaultConfig.setVaultId(""); + vaultConfig.setClusterId(""); + vaultConfig.setEnv(Env.PROD); + vaultConfig.setCredentials(credentials); + + // Step 3: Create Skyflow client instance with error logging + Skyflow skyflowClient = Skyflow.builder() + .setLogLevel(LogLevel.ERROR) + .addVaultConfig(vaultConfig) + .build(); + + // Step 4: Prepare first record for insertion + HashMap recordData1 = new HashMap<>(); + recordData1.put("", ""); + recordData1.put("", ""); + + BulkInsertRequestRecord insertRecord1 = BulkInsertRequestRecord + .builder() + .data(recordData1) + .build(); + + // Step 5: Prepare second record for insertion + HashMap recordData2 = new HashMap<>(); + recordData2.put("", ""); + recordData2.put("", ""); + + BulkInsertRequestRecord insertRecord2 = BulkInsertRequestRecord + .builder() + .data(recordData2) + .build(); + + // Step 6: Combine records into a Insert record list + List insertRecords = new ArrayList<>(); + insertRecords.add(insertRecord1); + insertRecords.add(insertRecord2); + + // Step 7: Configure upsert. uniqueColumns is required; updateType accepts "UPDATE" + // (default) or "REPLACE". + List upsertColumns = new ArrayList<>(); + upsertColumns.add(""); + + UpsertOptions upsert = UpsertOptions.builder() + .uniqueColumns(upsertColumns) + .updateType("REPLACE") + .build(); + + // Step 8: Build the insert request with table name and insertRecords. + // tableName and upsert must sit at the same level — here, the request level. + BulkInsertRequest request = BulkInsertRequest.builder() + .tableName("") + .upsert(upsert) + .records(insertRecords) + .build(); + + // Step 9: Execute the async bulk insert operation and handle response using callbacks + CompletableFuture future = skyflowClient.vault().bulkInsertAsync(request); + // Add success and error callbacks + future.thenAccept(response -> { + System.out.println("Async bulk insert resolved with response:\t" + response); + }).exceptionally(throwable -> { + System.err.println("Async bulk insert rejected with error:\t" + throwable.getMessage()); + throw new CompletionException(throwable); + }); + } catch (Exception e) { + // Step 10: Handle any synchronous errors that occur during setup + System.err.println("Error in Skyflow operations:\t" + e.getMessage()); + } + } +} diff --git a/flowvault/samples/src/main/java/com/example/vault/BulkInsertSync.java b/flowvault/samples/src/main/java/com/example/vault/BulkInsertSync.java new file mode 100644 index 00000000..c3ff5d60 --- /dev/null +++ b/flowvault/samples/src/main/java/com/example/vault/BulkInsertSync.java @@ -0,0 +1,100 @@ +package com.example.vault; + +import com.skyflow.Skyflow; +import com.skyflow.config.Credentials; +import com.skyflow.config.VaultConfig; +import com.skyflow.enums.Env; +import com.skyflow.enums.LogLevel; +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.data.BulkInsertRequest; +import com.skyflow.vault.data.BulkInsertRequestRecord; +import com.skyflow.vault.data.BulkInsertResponse; +import com.skyflow.vault.data.InsertRequestRecord; +import com.skyflow.vault.data.UpsertOptions; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +/** + * This sample demonstrates how to perform a synchronous bulk insert operation using the Skyflow Java SDK. + * The process involves: + * 1. Setting up credentials and vault configuration + * 2. Creating multiple records to be inserted + * 3. Building and executing a bulk insert request + * 4. Handling the insert response or any potential errors + */ +public class BulkInsertSync { + + public static void main(String[] args) { + try { + // Step 1: Initialize credentials with the path to your service account key file + String filePath = ""; + Credentials credentials = new Credentials(); + credentials.setPath(filePath); + + // Step 2: Configure the vault with required parameters + VaultConfig vaultConfig = new VaultConfig(); + vaultConfig.setVaultId(""); + vaultConfig.setClusterId(""); + vaultConfig.setEnv(Env.PROD); + vaultConfig.setCredentials(credentials); + + // Step 3: Create Skyflow client instance with error logging + Skyflow skyflowClient = Skyflow.builder() + .setLogLevel(LogLevel.ERROR) + .addVaultConfig(vaultConfig) + .build(); + + // Step 4: Prepare first record for insertion + HashMap recordData1 = new HashMap<>(); + recordData1.put("", ""); + recordData1.put("", ""); + + BulkInsertRequestRecord insertRecord1 = BulkInsertRequestRecord + .builder() + .data(recordData1) + .build(); + + // Step 5: Prepare second record for insertion + HashMap recordData2 = new HashMap<>(); + recordData2.put("", ""); + recordData2.put("", ""); + + BulkInsertRequestRecord insertRecord2 = BulkInsertRequestRecord + .builder() + .data(recordData2) + .build(); + + // Step 6: Combine records into a Insert record list + List insertRecords = new ArrayList<>(); + insertRecords.add(insertRecord1); + insertRecords.add(insertRecord2); + + // Step 7: Configure upsert. uniqueColumns is required; updateType accepts "UPDATE" + // (default) or "REPLACE". + List upsertColumns = new ArrayList<>(); + upsertColumns.add(""); + + UpsertOptions upsert = UpsertOptions.builder() + .uniqueColumns(upsertColumns) + .updateType("REPLACE") + .build(); + + // Step 8: Build the insert request with table name and insertRecords. + // tableName and upsert must sit at the same level — here, the request level. + BulkInsertRequest request = BulkInsertRequest.builder() + .tableName("") + .upsert(upsert) + .records(insertRecords) + .build(); + + // Step 9: Execute the bulk insert operation and print the response + BulkInsertResponse response = skyflowClient.vault().bulkInsert(request); + System.out.println(response); + } catch (SkyflowException e) { + // Step 10: Handle any errors that occur during the process + System.err.println("Error in Skyflow operations: " + e.getMessage()); + } + } +} diff --git a/flowvault/samples/src/main/java/com/example/vault/BulkMultiTableInsertAsync.java b/flowvault/samples/src/main/java/com/example/vault/BulkMultiTableInsertAsync.java new file mode 100644 index 00000000..c0f15834 --- /dev/null +++ b/flowvault/samples/src/main/java/com/example/vault/BulkMultiTableInsertAsync.java @@ -0,0 +1,109 @@ +package com.example.vault; + +import com.skyflow.Skyflow; +import com.skyflow.config.Credentials; +import com.skyflow.config.VaultConfig; +import com.skyflow.enums.Env; +import com.skyflow.enums.LogLevel; +import com.skyflow.vault.data.BulkInsertRequest; +import com.skyflow.vault.data.BulkInsertRequestRecord; +import com.skyflow.vault.data.BulkInsertResponse; +import com.skyflow.vault.data.InsertRequestRecord; +import com.skyflow.vault.data.UpsertOptions; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +/** + * This sample demonstrates how to perform an asynchronous bulk insert operation using the Skyflow Java SDK. + * The process involves: + * 1. Setting up credentials and vault configuration + * 2. Creating multiple records to be inserted + * 3. Building and executing an async bulk insert request + * 4. Handling the insert response or errors using CompletableFuture + * + *

Multi-table mode: the table name is set on every record instead of on the request. + * The SDK rejects a request that sets it at both levels, or on only some of the records. + */ +public class BulkMultiTableInsertAsync { + + public static void main(String[] args) { + try { + // Step 1: Initialize credentials with the path to your service account key file + String filePath = ""; + Credentials credentials = new Credentials(); + credentials.setPath(filePath); + + // Step 2: Configure the vault with required parameters + VaultConfig vaultConfig = new VaultConfig(); + vaultConfig.setVaultId(""); + vaultConfig.setClusterId(""); + vaultConfig.setEnv(Env.PROD); + vaultConfig.setCredentials(credentials); + + // Step 3: Create Skyflow client instance with error logging + Skyflow skyflowClient = Skyflow.builder() + .setLogLevel(LogLevel.ERROR) + .addVaultConfig(vaultConfig) + .build(); + + // Step 4: Prepare first record for insertion + HashMap recordData1 = new HashMap<>(); + recordData1.put("", ""); + recordData1.put("", ""); + + List upsertColumns = new ArrayList<>(); + upsertColumns.add(""); + + // upsert is optional; when updateType is omitted the vault defaults to "UPDATE". + // Set .updateType("REPLACE") to replace the matched row instead. + UpsertOptions upsert = UpsertOptions.builder() + .uniqueColumns(upsertColumns) + .build(); + + BulkInsertRequestRecord insertRecord1 = BulkInsertRequestRecord + .builder() + .data(recordData1) + .tableName("") + .upsert(upsert) + .build(); + + // Step 5: Prepare second record for insertion + HashMap recordData2 = new HashMap<>(); + recordData2.put("", ""); + recordData2.put("", ""); + + BulkInsertRequestRecord insertRecord2 = BulkInsertRequestRecord + .builder() + .data(recordData2) + .tableName("") + .build(); + + // Step 6: Combine records into a Insert record list + List insertRecords = new ArrayList<>(); + insertRecords.add(insertRecord1); + insertRecords.add(insertRecord2); + + // Step 7: Build the insert request. No tableName here — each record carries its own. + BulkInsertRequest request = BulkInsertRequest.builder() + .records(insertRecords) + .build(); + + // Step 8: Execute the async bulk insert operation and handle response using callbacks + CompletableFuture future = skyflowClient.vault().bulkInsertAsync(request); + // Add success and error callbacks + future.thenAccept(response -> { + System.out.println("Async bulk insert resolved with response:\t" + response); + }).exceptionally(throwable -> { + System.err.println("Async bulk insert rejected with error:\t" + throwable.getMessage()); + throw new CompletionException(throwable); + }); + } catch (Exception e) { + // Step 9: Handle any synchronous errors that occur during setup + System.err.println("Error in Skyflow operations:\t" + e.getMessage()); + } + } +} diff --git a/flowvault/samples/src/main/java/com/example/vault/BulkMultiTableInsertSync.java b/flowvault/samples/src/main/java/com/example/vault/BulkMultiTableInsertSync.java new file mode 100644 index 00000000..a3c1211b --- /dev/null +++ b/flowvault/samples/src/main/java/com/example/vault/BulkMultiTableInsertSync.java @@ -0,0 +1,102 @@ +package com.example.vault; + +import com.skyflow.Skyflow; +import com.skyflow.config.Credentials; +import com.skyflow.config.VaultConfig; +import com.skyflow.enums.Env; +import com.skyflow.enums.LogLevel; +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.data.BulkInsertRequest; +import com.skyflow.vault.data.BulkInsertRequestRecord; +import com.skyflow.vault.data.BulkInsertResponse; +import com.skyflow.vault.data.InsertRequestRecord; +import com.skyflow.vault.data.UpsertOptions; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +/** + * This sample demonstrates how to perform a synchronous bulk insert operation using the Skyflow Java SDK. + * The process involves: + * 1. Setting up credentials and vault configuration + * 2. Creating multiple records to be inserted + * 3. Building and executing a bulk insert request + * 4. Handling the insert response or any potential errors + * + *

Multi-table mode: the table name is set on every record instead of on the request. + * The SDK rejects a request that sets it at both levels, or on only some of the records. + */ +public class BulkMultiTableInsertSync { + + public static void main(String[] args) { + try { + // Step 1: Initialize credentials with the path to your service account key file + String filePath = ""; + Credentials credentials = new Credentials(); + credentials.setPath(filePath); + + // Step 2: Configure the vault with required parameters + VaultConfig vaultConfig = new VaultConfig(); + vaultConfig.setVaultId(""); + vaultConfig.setClusterId(""); + vaultConfig.setEnv(Env.PROD); + vaultConfig.setCredentials(credentials); + + // Step 3: Create Skyflow client instance with error logging + Skyflow skyflowClient = Skyflow.builder() + .setLogLevel(LogLevel.ERROR) + .addVaultConfig(vaultConfig) + .build(); + + // Step 4: Prepare first record for insertion + HashMap recordData1 = new HashMap<>(); + recordData1.put("", ""); + recordData1.put("", ""); + + List upsertColumns = new ArrayList<>(); + upsertColumns.add(""); + + // upsert is optional; when updateType is omitted the vault defaults to "UPDATE". + // Set .updateType("REPLACE") to replace the matched row instead. + UpsertOptions upsert = UpsertOptions.builder() + .uniqueColumns(upsertColumns) + .build(); + + BulkInsertRequestRecord insertRecord1 = BulkInsertRequestRecord + .builder() + .data(recordData1) + .tableName("") + .upsert(upsert) + .build(); + + // Step 5: Prepare second record for insertion + HashMap recordData2 = new HashMap<>(); + recordData2.put("", ""); + recordData2.put("", ""); + + BulkInsertRequestRecord insertRecord2 = BulkInsertRequestRecord + .builder() + .data(recordData2) + .tableName("") + .build(); + + // Step 6: Combine records into a Insert record list + List insertRecords = new ArrayList<>(); + insertRecords.add(insertRecord1); + insertRecords.add(insertRecord2); + + // Step 7: Build the insert request. No tableName here — each record carries its own. + BulkInsertRequest request = BulkInsertRequest.builder() + .records(insertRecords) + .build(); + + // Step 8: Execute the bulk insert operation and print the response + BulkInsertResponse response = skyflowClient.vault().bulkInsert(request); + System.out.println(response); + } catch (SkyflowException e) { + // Step 9: Handle any errors that occur during the process + System.err.println("Error in Skyflow operations: " + e.getMessage()); + } + } +} diff --git a/flowvault/samples/src/main/java/com/example/vault/CustomHeaderExample.java b/flowvault/samples/src/main/java/com/example/vault/CustomHeaderExample.java new file mode 100644 index 00000000..05941f8b --- /dev/null +++ b/flowvault/samples/src/main/java/com/example/vault/CustomHeaderExample.java @@ -0,0 +1,113 @@ +package com.example.vault; + +import com.skyflow.Skyflow; +import com.skyflow.config.Credentials; +import com.skyflow.config.VaultConfig; +import com.skyflow.enums.CustomHeaderKey; +import com.skyflow.enums.Env; +import com.skyflow.enums.LogLevel; +import com.skyflow.vault.data.BulkInsertRequest; +import com.skyflow.vault.data.BulkInsertRequestRecord; +import com.skyflow.vault.data.BulkInsertResponse; +import com.skyflow.vault.data.InsertOptions; +import com.skyflow.vault.data.InsertRequestRecord; +import com.skyflow.vault.data.UpsertOptions; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +/** + * This sample demonstrates how to attach custom headers to outgoing requests via a request + * interceptor on the options object. + * + *

Available keys on {@link CustomHeaderKey}: {@code SkyflowAccountId} ({@code x-skyflow-account-id}), + * {@code SkyflowAccountName} ({@code x-skyflow-account-name}) and {@code RequestIdHeader} + * ({@code x-request-id}). + * + *

The interceptor runs once per batch, so a per-request value such as a request id is generated + * fresh for each outgoing call rather than reused across the whole bulk operation. + */ +public class CustomHeaderExample { + public static void main(String[] args) { + try { + // Step 1: Initialize credentials with a bearer token + Credentials credentials = new Credentials(); + credentials.setToken(""); + + // Step 2: Configure the vault with required parameters + VaultConfig vaultConfig = new VaultConfig(); + vaultConfig.setVaultId(""); + vaultConfig.setClusterId(""); + vaultConfig.setEnv(Env.DEV); + vaultConfig.setCredentials(credentials); + + // Step 3: Create Skyflow client instance with debug logging + Skyflow skyflowClient = Skyflow.builder() + .setLogLevel(LogLevel.DEBUG) + .addVaultConfig(vaultConfig) + .build(); + + // Step 4: Prepare the records for insertion + List insertRecords = new ArrayList<>(); + for (int i = 0; i < 100; i++) { + HashMap recordData = new HashMap<>(); + recordData.put("", ""); + + BulkInsertRequestRecord insertRecord = BulkInsertRequestRecord + .builder() + .data(recordData) + .build(); + + insertRecords.add(insertRecord); + } + + // Step 5: Configure upsert. uniqueColumns is required; updateType accepts "UPDATE" + // (default) or "REPLACE". + List upsertColumns = new ArrayList<>(); + upsertColumns.add(""); + + UpsertOptions upsert = UpsertOptions.builder() + .uniqueColumns(upsertColumns) + .updateType("REPLACE") + .build(); + + // Step 6: Build the insert request. tableName and upsert both sit at the request level. + BulkInsertRequest request = BulkInsertRequest.builder() + .tableName("") + .upsert(upsert) + .records(insertRecords) + .build(); + + // Step 7: Attach a custom header through the interceptor + InsertOptions options = InsertOptions.builder() + .interceptor(ctx -> { + ctx.addHeader(CustomHeaderKey.RequestIdHeader, getRequestId()); // pass the request id here + }) + .build(); + + // Step 8: Execute the async bulk insert operation and handle response using callbacks + CompletableFuture future = + skyflowClient.vault().bulkInsertAsync(request, options); + // Add success and error callbacks + future.thenAccept(response -> { + System.out.println("Async bulk insert resolved with response:\t" + response); + }).exceptionally(throwable -> { + System.err.println("Async bulk insert rejected with error:\t" + throwable.getMessage()); + throw new CompletionException(throwable); + }); + } catch (Exception e) { + // Step 9: Handle any synchronous errors that occur during setup + System.err.println("Error in Skyflow operations:\t" + e.getMessage()); + } + } + + public static String getRequestId() { + String id = UUID.randomUUID().toString(); + System.out.println("id=>" + id); + return id; + } +} diff --git a/flowvault/samples/src/main/java/com/example/vault/TimeoutAndRetryConfigExample.java b/flowvault/samples/src/main/java/com/example/vault/TimeoutAndRetryConfigExample.java new file mode 100644 index 00000000..63fcd01b --- /dev/null +++ b/flowvault/samples/src/main/java/com/example/vault/TimeoutAndRetryConfigExample.java @@ -0,0 +1,88 @@ +package com.example.vault; + +import com.skyflow.Skyflow; +import com.skyflow.config.Credentials; +import com.skyflow.config.VaultConfig; +import com.skyflow.enums.Env; +import com.skyflow.enums.LogLevel; +import com.skyflow.errors.SkyflowException; + +/** + * This sample demonstrates how to configure HTTP timeout and retry behavior in the Skyflow Java SDK. + * + *

Configurable settings (all optional): + *

    + *
  • {@code timeout} – overall call timeout in seconds (bounds the whole + * request including retries and backoff). Default: 60.
  • + *
  • {@code connectTimeout} – per-attempt connection-establishment timeout in seconds. + * Default: 10 (the underlying HTTP client default).
  • + *
  • {@code readTimeout} – per-attempt response-read timeout in seconds. + * Default: 10.
  • + *
  • {@code writeTimeout} – per-attempt request-write timeout in seconds. + * Default: 10.
  • + *
  • {@code maxRetries} – retry attempts after the first failure (retries on HTTP + * 408 / 429 / 5xx). Default: 0 — retries are OFF unless you set this (avoids auto-retrying + * non-idempotent writes).
  • + *
+ * + *

Backoff is not configurable. When {@code maxRetries} is greater than 0 the SDK backs off + * exponentially with jitter between attempts; there is no builder or {@code VaultConfig} setter to + * tune the delay. + * + *

How they relate: {@code timeout} is the total ceiling for the whole call (all + * attempts + backoff). {@code connectTimeout}/{@code readTimeout}/{@code writeTimeout} each bound a + * single phase of one attempt; because they are per attempt, their sum across retries can + * exceed {@code timeout}, but {@code timeout} always wins and cuts the call off. + * + *

Two levels + precedence: set client-wide defaults on {@code Skyflow.builder()}, and/or + * per-vault overrides on {@code VaultConfig}. The most specific value wins, resolved per field: + * per-vault → client-wide → SDK default. + */ +public class TimeoutAndRetryConfigExample { + + public static void main(String[] args) { + try { + // Step 1: Initialize credentials with the path to your service account key file + String filePath = ""; + Credentials credentials = new Credentials(); + credentials.setPath(filePath); + + // Step 2: Configure the vault. Optionally override timeout/retry settings for THIS vault only. + VaultConfig vaultConfig = new VaultConfig(); + vaultConfig.setVaultId(""); + vaultConfig.setClusterId(""); + vaultConfig.setEnv(Env.PROD); + vaultConfig.setCredentials(credentials); + // Per-vault overrides (optional). Any field left unset inherits the client-wide default below, + // and then the SDK default. + vaultConfig.setTimeout(30); // seconds – tighter overall ceiling for this vault + vaultConfig.setConnectTimeout(5); // seconds – fail fast if the connection stalls + vaultConfig.setReadTimeout(20); // seconds – allow a slower response read + vaultConfig.setWriteTimeout(5); // seconds – bound the request write + vaultConfig.setMaxRetries(2); // fewer retries for this vault + + // Step 3: Create the Skyflow client. Client-wide defaults apply to every vault + // unless that vault overrides them (as above). + Skyflow skyflowClient = Skyflow.builder() + .setLogLevel(LogLevel.ERROR) + .timeout(60) // seconds – client-wide overall call timeout + .connectTimeout(10) // seconds – client-wide per-attempt connect timeout + .readTimeout(15) // seconds – client-wide per-attempt read timeout + .writeTimeout(10) // seconds – client-wide per-attempt write timeout + .maxRetries(3) // client-wide retry attempts + .addVaultConfig(vaultConfig) + .build(); + + // Step 4: Use the client as usual. Requests now fail fast at the configured timeout and + // retry transient 408/429/5xx responses with exponential backoff + jitter. + System.out.println("Skyflow client configured with custom timeout & retry settings: " + skyflowClient); + + // Example (uncomment and fill in a real request to try it): + // BulkDetokenizeResponse response = skyflowClient.vault().bulkDetokenize(detokenizeRequest); + // System.out.println(response); + } catch (SkyflowException e) { + // Step 5: Handle any errors that occur during the process + System.err.println("Error in Skyflow operations: " + e.getMessage()); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/Skyflow.java b/flowvault/src/main/java/com/skyflow/Skyflow.java new file mode 100644 index 00000000..64551cb9 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/Skyflow.java @@ -0,0 +1,285 @@ +package com.skyflow; + +import com.skyflow.config.Credentials; +import com.skyflow.config.VaultConfig; +import com.skyflow.enums.LogLevel; +import com.skyflow.errors.ErrorMessage; +import com.skyflow.errors.SkyflowException; +import com.skyflow.logs.ErrorLogs; +import com.skyflow.logs.InfoLogs; +import com.skyflow.utils.Constants; +import com.skyflow.utils.SdkVersion; +import com.skyflow.utils.Utils; +import com.skyflow.utils.logger.LogUtil; +import com.skyflow.utils.validations.Validations; +import com.skyflow.vault.controller.VaultController; + +import java.util.LinkedHashMap; + +public final class Skyflow extends BaseSkyflow { + + private final SkyflowClientBuilder builder; + + private Skyflow(SkyflowClientBuilder builder) { + super(builder); + this.builder = builder; + } + + @Override + protected Skyflow self() { + return this; + } + + public static SkyflowClientBuilder builder() { + SdkVersion.setSdkPrefix(Constants.SDK_PREFIX); + return new SkyflowClientBuilder(); + } + + public VaultConfig getVaultConfig() { + Object[] array = this.builder.vaultConfigMap.values().toArray(); + return (VaultConfig) array[0]; + } + + public VaultController vault() throws SkyflowException { + return resolveOrThrow(this.builder.vaultClientsMap, null, ErrorLogs.VAULT_CONFIG_DOES_NOT_EXIST, ErrorMessage.VaultIdNotInConfigList); + } + + public static final class SkyflowClientBuilder extends BaseSkyflowClientBuilder { + private final LinkedHashMap vaultClientsMap = new LinkedHashMap<>(); + // Client-wide HTTP config. Resolution per vault, most specific first: + // VaultConfig value -> the value set here -> SDK default (60s call timeout, 0 retries). + // null here means "not set", so the SDK default applies to vaults that don't override it. + // Only null means inherit: an explicit 0 is a real value and wins over the level below. + private Integer timeout; + private Integer connectTimeout; + private Integer readTimeout; + private Integer writeTimeout; + private Integer maxRetries; + private Long initialRetryDelayMillis; + private Long maxRetryDelayMillis; + + @Override + protected void validateVaultConfig(VaultConfig vaultConfig) throws SkyflowException { + Validations.validateVaultConfiguration(vaultConfig); + } + + @Override + protected void onVaultConfigAdded(VaultConfig vaultConfig) throws SkyflowException { + VaultController controller = new VaultController(vaultConfig, this.skyflowCredentials); + controller.setCommonHttpConfig(this.timeout, this.connectTimeout, this.readTimeout, + this.writeTimeout, this.maxRetries, this.initialRetryDelayMillis, + this.maxRetryDelayMillis); + this.vaultClientsMap.put(vaultConfig.getVaultId(), controller); + LogUtil.printInfoLog(Utils.parameterizedString(InfoLogs.VAULT_CONTROLLER_INITIALIZED.getLog(), vaultConfig.getVaultId())); + } + + @Override + protected void onVaultConfigUpdated(VaultConfig updatedConfig) throws SkyflowException { + // Update the existing controller in place — replacing it would leave any VaultController + // reference the caller already holds pointing at the previous config. + VaultController updated = this.vaultClientsMap.get(updatedConfig.getVaultId()); + if (updated == null) { + updated = new VaultController(updatedConfig, this.skyflowCredentials); + this.vaultClientsMap.put(updatedConfig.getVaultId(), updated); + } else { + updated.setVaultConfig(updatedConfig); + } + updated.setCommonHttpConfig(this.timeout, this.connectTimeout, this.readTimeout, + this.writeTimeout, this.maxRetries, this.initialRetryDelayMillis, + this.maxRetryDelayMillis); + } + + @Override + protected void onVaultConfigRemoved(String vaultId) throws SkyflowException { + this.vaultClientsMap.remove(vaultId); + } + + @Override + protected boolean hasVaultClient(String vaultId) { + return this.vaultClientsMap.containsKey(vaultId); + } + + @Override + protected void onCredentialsUpdated(Credentials credentials) throws SkyflowException { + for (VaultController vault : this.vaultClientsMap.values()) { + vault.setCommonCredentials(credentials); + } + } + + @Override + public SkyflowClientBuilder addVaultConfig(VaultConfig vaultConfig) throws SkyflowException { + super.addVaultConfig(vaultConfig); + return this; + } + + @Override + public SkyflowClientBuilder updateVaultConfig(VaultConfig vaultConfig) throws SkyflowException { + super.updateVaultConfig(vaultConfig); + carryVaultOverrides(vaultConfig); + return this; + } + + /** + * BaseSkyflow.mergeVaultConfig() only carries env, clusterId and credentials across, so the + * flowvault-specific fields on an incoming update — vaultUrl and the HTTP settings — would + * be dropped silently. Apply them to the merged config the new controller is holding. A null + * on the incoming config means "leave as is", matching how the base class merges every + * other field. + */ + private void carryVaultOverrides(VaultConfig incoming) throws SkyflowException { + VaultConfig merged = this.vaultConfigMap.get(incoming.getVaultId()); + if (merged == null || merged == incoming) { + return; + } + if (incoming.getTimeout() != null) { + merged.setTimeout(incoming.getTimeout()); + } + if (incoming.getConnectTimeout() != null) { + merged.setConnectTimeout(incoming.getConnectTimeout()); + } + if (incoming.getReadTimeout() != null) { + merged.setReadTimeout(incoming.getReadTimeout()); + } + if (incoming.getWriteTimeout() != null) { + merged.setWriteTimeout(incoming.getWriteTimeout()); + } + if (incoming.getMaxRetries() != null) { + merged.setMaxRetries(incoming.getMaxRetries()); + } + if (incoming.getInitialRetryDelayMillis() != null) { + merged.setInitialRetryDelayMillis(incoming.getInitialRetryDelayMillis()); + } + if (incoming.getMaxRetryDelayMillis() != null) { + merged.setMaxRetryDelayMillis(incoming.getMaxRetryDelayMillis()); + } + // The HTTP settings above are resolved lazily on the next request, but the URL is + // resolved once in the VaultClient constructor — which already ran with the old value. + if (incoming.getVaultUrl() != null) { + merged.setVaultUrl(incoming.getVaultUrl()); + VaultController controller = this.vaultClientsMap.get(incoming.getVaultId()); + if (controller != null) { + controller.refreshVaultUrl(); + } + } + } + + @Override + public SkyflowClientBuilder removeVaultConfig(String vaultId) throws SkyflowException { + super.removeVaultConfig(vaultId); + return this; + } + + @Override + public SkyflowClientBuilder addSkyflowCredentials(Credentials credentials) throws SkyflowException { + super.addSkyflowCredentials(credentials); + return this; + } + + @Override + public SkyflowClientBuilder setLogLevel(LogLevel logLevel) { + super.setLogLevel(logLevel); + return this; + } + + /** + * Overall call timeout in seconds, including retries. Default 60. + *

+ * Precedence: a vault that sets {@link VaultConfig#setTimeout(Integer)} wins; this + * value applies only to vaults that leave it unset. + */ + public SkyflowClientBuilder timeout(int timeout) { + this.timeout = timeout; + propagateHttpConfig(); + return this; + } + + /** + * Per-attempt connection-establishment timeout in seconds. Unset => HTTP client default (10s). + *

+ * Precedence: a vault that sets {@link VaultConfig#setConnectTimeout(Integer)} wins; + * this value applies only to vaults that leave it unset. + */ + public SkyflowClientBuilder connectTimeout(int connectTimeout) { + this.connectTimeout = connectTimeout; + propagateHttpConfig(); + return this; + } + + /** + * Per-attempt response-read timeout in seconds. Unset => HTTP client default (10s). + *

+ * Precedence: a vault that sets {@link VaultConfig#setReadTimeout(Integer)} wins; + * this value applies only to vaults that leave it unset. + */ + public SkyflowClientBuilder readTimeout(int readTimeout) { + this.readTimeout = readTimeout; + propagateHttpConfig(); + return this; + } + + /** + * Per-attempt request-write timeout in seconds. Unset => HTTP client default (10s). + *

+ * Precedence: a vault that sets {@link VaultConfig#setWriteTimeout(Integer)} wins; + * this value applies only to vaults that leave it unset. + */ + public SkyflowClientBuilder writeTimeout(int writeTimeout) { + this.writeTimeout = writeTimeout; + propagateHttpConfig(); + return this; + } + + /** + * Retry attempts after the first failure. Default 0 — retries are opt-in so non-idempotent + * bulk writes are not replayed automatically. + *

+ * Precedence: a vault that sets {@link VaultConfig#setMaxRetries(Integer)} wins; + * this value applies only to vaults that leave it unset. + */ + public SkyflowClientBuilder maxRetries(int maxRetries) { + this.maxRetries = maxRetries; + propagateHttpConfig(); + return this; + } + + /** + * Backoff before the first retry, in milliseconds. Default 500. Only applies when + * {@code maxRetries} is greater than zero. + *

+ * Precedence: a vault that sets {@link VaultConfig#setInitialRetryDelayMillis(Long)} + * wins; this value applies only to vaults that leave it unset. + */ + public SkyflowClientBuilder initialRetryDelayMillis(long initialRetryDelayMillis) { + this.initialRetryDelayMillis = initialRetryDelayMillis; + propagateHttpConfig(); + return this; + } + + /** + * Ceiling the exponential backoff grows to, in milliseconds. Default 2000. Only applies + * when {@code maxRetries} is greater than zero. + *

+ * Precedence: a vault that sets {@link VaultConfig#setMaxRetryDelayMillis(Long)} + * wins; this value applies only to vaults that leave it unset. + */ + public SkyflowClientBuilder maxRetryDelayMillis(long maxRetryDelayMillis) { + this.maxRetryDelayMillis = maxRetryDelayMillis; + propagateHttpConfig(); + return this; + } + + /** Push the current client-wide HTTP settings onto every vault controller built so far. */ + private void propagateHttpConfig() { + for (VaultController vault : this.vaultClientsMap.values()) { + vault.setCommonHttpConfig(this.timeout, this.connectTimeout, this.readTimeout, + this.writeTimeout, this.maxRetries, this.initialRetryDelayMillis, + this.maxRetryDelayMillis); + } + } + + public Skyflow build() { + return new Skyflow(this); + } + } + +} \ No newline at end of file diff --git a/flowvault/src/main/java/com/skyflow/VaultClient.java b/flowvault/src/main/java/com/skyflow/VaultClient.java new file mode 100644 index 00000000..d45d50fc --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/VaultClient.java @@ -0,0 +1,188 @@ +package com.skyflow; + +import com.skyflow.config.Credentials; +import com.skyflow.config.VaultConfig; +import com.skyflow.errors.SkyflowException; +import com.skyflow.generated.rest.ApiClient; +import com.skyflow.generated.rest.ApiClientBuilder; +import com.skyflow.generated.rest.resources.flowservice.FlowserviceClient; +import com.skyflow.generated.rest.resources.records.RecordsClient; +import com.skyflow.utils.SkyflowRetryInterceptor; +import com.skyflow.utils.Utils; + +import java.util.concurrent.TimeUnit; + +import okhttp3.ConnectionPool; +import okhttp3.OkHttpClient; +import okhttp3.Request; + +public class VaultClient extends BaseVaultClient { + private final ApiClientBuilder apiClientBuilder; + private ApiClient apiClient; + // Client-wide (Skyflow builder) HTTP config; null => fall back to the SDK defaults below. + private Integer commonTimeout; + private Integer commonConnectTimeout; + private Integer commonReadTimeout; + private Integer commonWriteTimeout; + private Integer commonMaxRetries; + private Long commonInitialRetryDelayMillis; + private Long commonMaxRetryDelayMillis; + // SDK defaults, used when neither the vault-level nor the client-wide value is set. + private static final int DEFAULT_TIMEOUT_SECONDS = 60; + // Retries OFF by default (opt-in) so non-idempotent bulk writes aren't replayed automatically. + private static final int DEFAULT_MAX_RETRIES = 0; + private static final long DEFAULT_INITIAL_RETRY_DELAY_MILLIS = 500L; + private static final long DEFAULT_MAX_RETRY_DELAY_MILLIS = 2000L; + + protected VaultClient(VaultConfig vaultConfig, Credentials credentials) throws SkyflowException { + super(vaultConfig, credentials); + this.apiClientBuilder = new ApiClientBuilder(); + this.apiClient = null; + updateVaultUrl(); + } + + /** + * Applies the client-wide HTTP settings from the Skyflow builder. Discards the cached HTTP + * client and ApiClient so the next call rebuilds them with the new values. + */ + protected void setCommonHttpConfig(Integer timeout, Integer connectTimeout, Integer readTimeout, + Integer writeTimeout, Integer maxRetries, + Long initialRetryDelayMillis, Long maxRetryDelayMillis) { + this.commonTimeout = timeout; + this.commonConnectTimeout = connectTimeout; + this.commonReadTimeout = readTimeout; + this.commonWriteTimeout = writeTimeout; + this.commonMaxRetries = maxRetries; + this.commonInitialRetryDelayMillis = initialRetryDelayMillis; + this.commonMaxRetryDelayMillis = maxRetryDelayMillis; + this.sharedHttpClient = null; + this.apiClient = null; + } + + /** Resolve a setting: vault-level override, else client-wide default, else the SDK default. */ + private static int resolveInt(Integer vaultLevel, Integer clientLevel, int defaultValue) { + if (vaultLevel != null) { + return vaultLevel; + } + return clientLevel != null ? clientLevel : defaultValue; + } + + /** Resolve a long setting: vault-level override, else client-wide default, else the SDK default. */ + private static long resolveLong(Long vaultLevel, Long clientLevel, long defaultValue) { + if (vaultLevel != null) { + return vaultLevel; + } + return clientLevel != null ? clientLevel : defaultValue; + } + + /** + * Resolve an optional setting: vault-level override, else client-wide default, else null. + * Null means "not configured" — the caller leaves the underlying HTTP client default in place. + */ + private static Integer resolveNullableInt(Integer vaultLevel, Integer clientLevel) { + return vaultLevel != null ? vaultLevel : clientLevel; + } + + protected FlowserviceClient getRecordsApi() { + return this.apiClient.flowservice(); + } + + protected RecordsClient getQueryApi() { + return this.apiClient.records(); + } + + protected void setCommonCredentials(Credentials commonCredentials) throws SkyflowException { + this.commonCredentials = commonCredentials; + super.prioritiseCredentials(this.vaultConfig.getCredentials()); + } + + protected synchronized void setBearerToken() throws SkyflowException { + super.setBearerToken(this.vaultConfig.getCredentials()); + if (apiClient == null) { + updateExecutorInHTTP(); + this.apiClient = this.apiClientBuilder.build(); + } + } + + /** + * Adopts an updated config in place, so a VaultController reference the caller already holds + * keeps working instead of silently serving the previous config. Discards the cached HTTP and + * API clients; the bearer token is re-resolved by setBearerToken, which drops it when the + * effective credentials changed. + */ + protected void setVaultConfig(VaultConfig vaultConfig) throws SkyflowException { + this.vaultConfig = vaultConfig; + this.sharedHttpClient = null; + this.apiClient = null; + updateVaultUrl(); + } + + /** + * Re-resolves the vault URL from the current config. The constructor resolves it once, so a + * vaultUrl supplied later through updateVaultConfig would otherwise never take effect. + */ + protected void refreshVaultUrl() throws SkyflowException { + updateVaultUrl(); + } + + private void updateVaultUrl() throws SkyflowException { + // Fetch vaultUrl from ENV + String vaultUrl = Utils.getEnvVaultUrl(); + + // If vaultUrl from ENV is null or empty, fetch vaultUrl from vault config + if (vaultUrl == null || vaultUrl.isEmpty()) { + vaultUrl = this.vaultConfig.getVaultUrl(); + } + + // If vaultUrl from vault config is also null or empty, construct vaultUrl from clusterId passed in vault config + if (vaultUrl == null || vaultUrl.isEmpty()) { + vaultUrl = Utils.getVaultUrl(this.vaultConfig.getClusterId(), this.vaultConfig.getEnv()); + } + this.apiClientBuilder.url(vaultUrl); + if (!vaultUrl.equals(this.currentVaultURL)) { + this.currentVaultURL = vaultUrl; + this.apiClient = null; + } + } + + protected void updateExecutorInHTTP() { + if (sharedHttpClient == null) { + int timeoutSeconds = resolveInt(vaultConfig.getTimeout(), commonTimeout, DEFAULT_TIMEOUT_SECONDS); + int maxRetries = resolveInt(vaultConfig.getMaxRetries(), commonMaxRetries, DEFAULT_MAX_RETRIES); + long initialRetryDelayMillis = resolveLong(vaultConfig.getInitialRetryDelayMillis(), + commonInitialRetryDelayMillis, DEFAULT_INITIAL_RETRY_DELAY_MILLIS); + long maxRetryDelayMillis = resolveLong(vaultConfig.getMaxRetryDelayMillis(), + commonMaxRetryDelayMillis, DEFAULT_MAX_RETRY_DELAY_MILLIS); + // Per-attempt timeouts: null => leave OkHttp's built-in default (backward compatible). + Integer connectTimeout = resolveNullableInt(vaultConfig.getConnectTimeout(), commonConnectTimeout); + Integer readTimeout = resolveNullableInt(vaultConfig.getReadTimeout(), commonReadTimeout); + Integer writeTimeout = resolveNullableInt(vaultConfig.getWriteTimeout(), commonWriteTimeout); + + OkHttpClient.Builder httpBuilder = new OkHttpClient.Builder() + .connectionPool(new ConnectionPool(10, 1, TimeUnit.MINUTES)) + // Overall ceiling; bounds the whole call including retries. + .callTimeout(timeoutSeconds, TimeUnit.SECONDS) + // OUTER: retries. Must wrap the auth interceptor so each attempt re-reads the + // (possibly refreshed) bearer token rather than replaying a stale one. + .addInterceptor(new SkyflowRetryInterceptor(maxRetries, initialRetryDelayMillis, maxRetryDelayMillis)) + .addInterceptor(chain -> { // INNER: auth + Request requestWithAuth = chain.request().newBuilder() + .header("Authorization", "Bearer " + this.token) + .build(); + return chain.proceed(requestWithAuth); + }); + if (connectTimeout != null) { + httpBuilder.connectTimeout(connectTimeout, TimeUnit.SECONDS); + } + if (readTimeout != null) { + httpBuilder.readTimeout(readTimeout, TimeUnit.SECONDS); + } + if (writeTimeout != null) { + httpBuilder.writeTimeout(writeTimeout, TimeUnit.SECONDS); + } + sharedHttpClient = httpBuilder.build(); + apiClientBuilder.httpClient(sharedHttpClient); + } + } + +} diff --git a/flowvault/src/main/java/com/skyflow/config/VaultConfig.java b/flowvault/src/main/java/com/skyflow/config/VaultConfig.java new file mode 100644 index 00000000..3369abc7 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/config/VaultConfig.java @@ -0,0 +1,152 @@ +package com.skyflow.config; + +/** + * Per-vault configuration. + *

+ * The HTTP timeout and retry settings below are vault-level overrides. Each one resolves + * most-specific-first: the value set here, else the client-wide value set on + * {@code Skyflow.builder()}, else the SDK default. So when the same setting is supplied at both + * levels, the value on this VaultConfig takes precedence and the client-wide value is + * ignored for this vault. + *

+ * Only {@code null} means "inherit" — an explicit {@code 0} is a real value and wins over the + * client-wide setting. + */ +public class VaultConfig extends BaseVaultConfig { + + private String vaultUrl; + // HTTP timeout & retry config (vault-level overrides). null => inherit client-wide default, then SDK default. + private Integer timeout; // overall call timeout, in seconds + private Integer connectTimeout; // per-attempt connection-establishment timeout, in seconds + private Integer readTimeout; // per-attempt response-read timeout, in seconds + private Integer writeTimeout; // per-attempt request-write timeout, in seconds + private Integer maxRetries; // retry attempts after the first failure + private Long initialRetryDelayMillis; // backoff before the first retry, in milliseconds + private Long maxRetryDelayMillis; // ceiling the exponential backoff grows to, in milliseconds + + public VaultConfig() { + super(); + this.vaultUrl = null; + this.timeout = null; + this.connectTimeout = null; + this.readTimeout = null; + this.writeTimeout = null; + this.maxRetries = null; + this.initialRetryDelayMillis = null; + this.maxRetryDelayMillis = null; + } + + public String getVaultUrl() { + return vaultUrl; + } + + public void setVaultUrl(String vaultUrl) { + this.vaultUrl = vaultUrl; + } + + public Integer getTimeout() { + return timeout; + } + + /** + * Overall call timeout in seconds for this vault, including retries. + *

+ * Takes precedence over the client-wide {@code Skyflow.builder().timeout(...)}. Leave unset + * (null) to inherit that value, or the SDK default of 60s if it is also unset. + */ + public void setTimeout(Integer timeout) { + this.timeout = timeout; + } + + public Integer getConnectTimeout() { + return connectTimeout; + } + + /** + * Per-attempt connection-establishment timeout in seconds for this vault. + *

+ * Takes precedence over the client-wide {@code Skyflow.builder().connectTimeout(...)}. Leave + * unset (null) to inherit that value; if neither is set, the underlying HTTP client default + * (10s) applies. Note the overall {@code timeout} still bounds the whole call, including retries. + */ + public void setConnectTimeout(Integer connectTimeout) { + this.connectTimeout = connectTimeout; + } + + public Integer getReadTimeout() { + return readTimeout; + } + + /** + * Per-attempt response-read timeout in seconds for this vault. + *

+ * Takes precedence over the client-wide {@code Skyflow.builder().readTimeout(...)}. Leave + * unset (null) to inherit that value; if neither is set, the underlying HTTP client default + * (10s) applies. Note the overall {@code timeout} still bounds the whole call, including retries. + */ + public void setReadTimeout(Integer readTimeout) { + this.readTimeout = readTimeout; + } + + public Integer getWriteTimeout() { + return writeTimeout; + } + + /** + * Per-attempt request-write timeout in seconds for this vault. + *

+ * Takes precedence over the client-wide {@code Skyflow.builder().writeTimeout(...)}. Leave + * unset (null) to inherit that value; if neither is set, the underlying HTTP client default + * (10s) applies. Note the overall {@code timeout} still bounds the whole call, including retries. + */ + public void setWriteTimeout(Integer writeTimeout) { + this.writeTimeout = writeTimeout; + } + + public Integer getMaxRetries() { + return maxRetries; + } + + /** + * Retry attempts after the first failure for this vault. + *

+ * Takes precedence over the client-wide {@code Skyflow.builder().maxRetries(...)}. Leave unset + * (null) to inherit that value, or the SDK default of 0 if it is also unset — retries are + * opt-in, so non-idempotent bulk writes are not replayed silently. + */ + public void setMaxRetries(Integer maxRetries) { + this.maxRetries = maxRetries; + } + + + public Long getInitialRetryDelayMillis() { + return initialRetryDelayMillis; + } + + /** + * Backoff before the first retry, in milliseconds, for this vault. + *

+ * Takes precedence over the client-wide {@code Skyflow.builder().initialRetryDelayMillis(...)}. + * Leave unset (null) to inherit that value, or the SDK default of 500 ms if it is also unset. + * Only applies when {@code maxRetries} is greater than zero. + */ + public void setInitialRetryDelayMillis(Long initialRetryDelayMillis) { + this.initialRetryDelayMillis = initialRetryDelayMillis; + } + + public Long getMaxRetryDelayMillis() { + return maxRetryDelayMillis; + } + + /** + * Ceiling the exponential backoff grows to, in milliseconds, for this vault. + *

+ * Takes precedence over the client-wide {@code Skyflow.builder().maxRetryDelayMillis(...)}. + * Leave unset (null) to inherit that value, or the SDK default of 2000 ms if it is also unset. + * Only applies when {@code maxRetries} is greater than zero. + */ + public void setMaxRetryDelayMillis(Long maxRetryDelayMillis) { + this.maxRetryDelayMillis = maxRetryDelayMillis; + } + +} diff --git a/flowvault/src/main/java/com/skyflow/enums/InterfaceName.java b/flowvault/src/main/java/com/skyflow/enums/InterfaceName.java new file mode 100644 index 00000000..475f8ad2 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/enums/InterfaceName.java @@ -0,0 +1,21 @@ +package com.skyflow.enums; + +public enum InterfaceName { + INSERT("insert"), + DETOKENIZE("detokenize"), + DELETE("delete tokens"), + TOKENIZE("tokenize"), + QUERY("query"), + GET("get"); + + + private final String interfaceName; + + InterfaceName(String interfaceName) { + this.interfaceName = interfaceName; + } + + public String getName() { + return interfaceName; + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/ApiClient.java b/flowvault/src/main/java/com/skyflow/generated/rest/ApiClient.java new file mode 100644 index 00000000..02b972dd --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/ApiClient.java @@ -0,0 +1,37 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest; + +import com.skyflow.generated.rest.core.ClientOptions; +import com.skyflow.generated.rest.core.Suppliers; +import com.skyflow.generated.rest.resources.flowservice.FlowserviceClient; +import com.skyflow.generated.rest.resources.records.RecordsClient; + +import java.util.function.Supplier; + +public class ApiClient { + protected final ClientOptions clientOptions; + + protected final Supplier recordsClient; + + protected final Supplier flowserviceClient; + + public ApiClient(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + this.recordsClient = Suppliers.memoize(() -> new RecordsClient(clientOptions)); + this.flowserviceClient = Suppliers.memoize(() -> new FlowserviceClient(clientOptions)); + } + + public RecordsClient records() { + return this.recordsClient.get(); + } + + public FlowserviceClient flowservice() { + return this.flowserviceClient.get(); + } + + public static ApiClientBuilder builder() { + return new ApiClientBuilder(); + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/ApiClientBuilder.java b/flowvault/src/main/java/com/skyflow/generated/rest/ApiClientBuilder.java new file mode 100644 index 00000000..fa3d6e9d --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/ApiClientBuilder.java @@ -0,0 +1,48 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest; + +import com.skyflow.generated.rest.core.ClientOptions; +import com.skyflow.generated.rest.core.Environment; +import okhttp3.OkHttpClient; + +public final class ApiClientBuilder { + private ClientOptions.Builder clientOptionsBuilder = ClientOptions.builder(); + + private Environment environment; + + public ApiClientBuilder url(String url) { + this.environment = Environment.custom(url); + return this; + } + + /** + * Sets the timeout (in seconds) for the client. Defaults to 60 seconds. + */ + public ApiClientBuilder timeout(int timeout) { + this.clientOptionsBuilder.timeout(timeout); + return this; + } + + /** + * Sets the maximum number of retries for the client. Defaults to 2 retries. + */ + public ApiClientBuilder maxRetries(int maxRetries) { + this.clientOptionsBuilder.maxRetries(maxRetries); + return this; + } + + /** + * Sets the underlying OkHttp client + */ + public ApiClientBuilder httpClient(OkHttpClient httpClient) { + this.clientOptionsBuilder.httpClient(httpClient); + return this; + } + + public ApiClient build() { + clientOptionsBuilder.environment(this.environment); + return new ApiClient(clientOptionsBuilder.build()); + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/AsyncApiClient.java b/flowvault/src/main/java/com/skyflow/generated/rest/AsyncApiClient.java new file mode 100644 index 00000000..840c0bad --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/AsyncApiClient.java @@ -0,0 +1,37 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest; + +import com.skyflow.generated.rest.core.ClientOptions; +import com.skyflow.generated.rest.core.Suppliers; +import com.skyflow.generated.rest.resources.flowservice.AsyncFlowserviceClient; +import com.skyflow.generated.rest.resources.records.AsyncRecordsClient; + +import java.util.function.Supplier; + +public class AsyncApiClient { + protected final ClientOptions clientOptions; + + protected final Supplier recordsClient; + + protected final Supplier flowserviceClient; + + public AsyncApiClient(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + this.recordsClient = Suppliers.memoize(() -> new AsyncRecordsClient(clientOptions)); + this.flowserviceClient = Suppliers.memoize(() -> new AsyncFlowserviceClient(clientOptions)); + } + + public AsyncRecordsClient records() { + return this.recordsClient.get(); + } + + public AsyncFlowserviceClient flowservice() { + return this.flowserviceClient.get(); + } + + public static AsyncApiClientBuilder builder() { + return new AsyncApiClientBuilder(); + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/AsyncApiClientBuilder.java b/flowvault/src/main/java/com/skyflow/generated/rest/AsyncApiClientBuilder.java new file mode 100644 index 00000000..10c08d51 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/AsyncApiClientBuilder.java @@ -0,0 +1,48 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest; + +import com.skyflow.generated.rest.core.ClientOptions; +import com.skyflow.generated.rest.core.Environment; +import okhttp3.OkHttpClient; + +public final class AsyncApiClientBuilder { + private ClientOptions.Builder clientOptionsBuilder = ClientOptions.builder(); + + private Environment environment; + + public AsyncApiClientBuilder url(String url) { + this.environment = Environment.custom(url); + return this; + } + + /** + * Sets the timeout (in seconds) for the client. Defaults to 60 seconds. + */ + public AsyncApiClientBuilder timeout(int timeout) { + this.clientOptionsBuilder.timeout(timeout); + return this; + } + + /** + * Sets the maximum number of retries for the client. Defaults to 2 retries. + */ + public AsyncApiClientBuilder maxRetries(int maxRetries) { + this.clientOptionsBuilder.maxRetries(maxRetries); + return this; + } + + /** + * Sets the underlying OkHttp client + */ + public AsyncApiClientBuilder httpClient(OkHttpClient httpClient) { + this.clientOptionsBuilder.httpClient(httpClient); + return this; + } + + public AsyncApiClient build() { + clientOptionsBuilder.environment(this.environment); + return new AsyncApiClient(clientOptionsBuilder.build()); + } +} diff --git a/src/main/java/com/skyflow/generated/rest/core/ApiClientApiException.java b/flowvault/src/main/java/com/skyflow/generated/rest/core/ApiClientApiException.java similarity index 99% rename from src/main/java/com/skyflow/generated/rest/core/ApiClientApiException.java rename to flowvault/src/main/java/com/skyflow/generated/rest/core/ApiClientApiException.java index 4fab1d41..be5247eb 100644 --- a/src/main/java/com/skyflow/generated/rest/core/ApiClientApiException.java +++ b/flowvault/src/main/java/com/skyflow/generated/rest/core/ApiClientApiException.java @@ -3,11 +3,12 @@ */ package com.skyflow.generated.rest.core; +import okhttp3.Response; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import okhttp3.Response; /** * This exception type will be thrown for any non-2XX API responses. diff --git a/src/main/java/com/skyflow/generated/rest/core/ApiClientException.java b/flowvault/src/main/java/com/skyflow/generated/rest/core/ApiClientException.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/core/ApiClientException.java rename to flowvault/src/main/java/com/skyflow/generated/rest/core/ApiClientException.java diff --git a/src/main/java/com/skyflow/generated/rest/core/ApiClientHttpResponse.java b/flowvault/src/main/java/com/skyflow/generated/rest/core/ApiClientHttpResponse.java similarity index 99% rename from src/main/java/com/skyflow/generated/rest/core/ApiClientHttpResponse.java rename to flowvault/src/main/java/com/skyflow/generated/rest/core/ApiClientHttpResponse.java index 9c81f1f5..c743352c 100644 --- a/src/main/java/com/skyflow/generated/rest/core/ApiClientHttpResponse.java +++ b/flowvault/src/main/java/com/skyflow/generated/rest/core/ApiClientHttpResponse.java @@ -3,11 +3,12 @@ */ package com.skyflow.generated.rest.core; +import okhttp3.Response; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import okhttp3.Response; public final class ApiClientHttpResponse { diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/core/ClientOptions.java b/flowvault/src/main/java/com/skyflow/generated/rest/core/ClientOptions.java new file mode 100644 index 00000000..2a5f0710 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/core/ClientOptions.java @@ -0,0 +1,171 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.core; + +import okhttp3.OkHttpClient; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +public final class ClientOptions { + private final Environment environment; + + private final Map headers; + + private final Map> headerSuppliers; + + private final OkHttpClient httpClient; + + private final int timeout; + + private ClientOptions( + Environment environment, + Map headers, + Map> headerSuppliers, + OkHttpClient httpClient, + int timeout) { + this.environment = environment; + this.headers = new HashMap<>(); + this.headers.putAll(headers); + this.headers.putAll(new HashMap() { + { + put("X-Fern-Language", "JAVA"); + put("X-Fern-SDK-Name", "com.skyflow.fern:api-sdk"); + put("X-Fern-SDK-Version", "0.0.98"); + } + }); + this.headerSuppliers = headerSuppliers; + this.httpClient = httpClient; + this.timeout = timeout; + } + + 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 int timeout(RequestOptions requestOptions) { + if (requestOptions == null) { + return this.timeout; + } + return requestOptions.getTimeout().orElse(this.timeout); + } + + 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<>(); + + private int maxRetries = 2; + + private Optional timeout = Optional.empty(); + + private OkHttpClient httpClient = null; + + 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; + } + + /** + * Override the timeout in seconds. Defaults to 60 seconds. + */ + public Builder timeout(int timeout) { + this.timeout = Optional.of(timeout); + return this; + } + + /** + * Override the timeout in seconds. Defaults to 60 seconds. + */ + public Builder timeout(Optional timeout) { + this.timeout = timeout; + return this; + } + + /** + * Override the maximum number of retries. Defaults to 2 retries. + */ + public Builder maxRetries(int maxRetries) { + this.maxRetries = maxRetries; + return this; + } + + public Builder httpClient(OkHttpClient httpClient) { + this.httpClient = httpClient; + return this; + } + + public ClientOptions build() { + OkHttpClient.Builder httpClientBuilder = + this.httpClient != null ? this.httpClient.newBuilder() : new OkHttpClient.Builder(); + + if (this.httpClient != null) { + timeout.ifPresent(timeout -> httpClientBuilder + .callTimeout(timeout, TimeUnit.SECONDS) + .connectTimeout(0, TimeUnit.SECONDS) + .writeTimeout(0, TimeUnit.SECONDS) + .readTimeout(0, TimeUnit.SECONDS)); + } else { + httpClientBuilder + .callTimeout(this.timeout.orElse(60), TimeUnit.SECONDS) + .connectTimeout(0, TimeUnit.SECONDS) + .writeTimeout(0, TimeUnit.SECONDS) + .readTimeout(0, TimeUnit.SECONDS) + .addInterceptor(new RetryInterceptor(this.maxRetries)); + } + + this.httpClient = httpClientBuilder.build(); + this.timeout = Optional.of(httpClient.callTimeoutMillis() / 1000); + + return new ClientOptions(environment, headers, headerSuppliers, httpClient, this.timeout.get()); + } + } +} diff --git a/src/main/java/com/skyflow/generated/rest/core/DateTimeDeserializer.java b/flowvault/src/main/java/com/skyflow/generated/rest/core/DateTimeDeserializer.java similarity index 99% rename from src/main/java/com/skyflow/generated/rest/core/DateTimeDeserializer.java rename to flowvault/src/main/java/com/skyflow/generated/rest/core/DateTimeDeserializer.java index 6be10979..a0a6d7c4 100644 --- a/src/main/java/com/skyflow/generated/rest/core/DateTimeDeserializer.java +++ b/flowvault/src/main/java/com/skyflow/generated/rest/core/DateTimeDeserializer.java @@ -8,6 +8,7 @@ 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; diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/core/Environment.java b/flowvault/src/main/java/com/skyflow/generated/rest/core/Environment.java new file mode 100644 index 00000000..2fc27c36 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/core/Environment.java @@ -0,0 +1,20 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.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/src/main/java/com/skyflow/generated/rest/core/FileStream.java b/flowvault/src/main/java/com/skyflow/generated/rest/core/FileStream.java similarity index 99% rename from src/main/java/com/skyflow/generated/rest/core/FileStream.java rename to flowvault/src/main/java/com/skyflow/generated/rest/core/FileStream.java index 6b459431..2131b0a4 100644 --- a/src/main/java/com/skyflow/generated/rest/core/FileStream.java +++ b/flowvault/src/main/java/com/skyflow/generated/rest/core/FileStream.java @@ -3,12 +3,13 @@ */ package com.skyflow.generated.rest.core; -import java.io.InputStream; -import java.util.Objects; import okhttp3.MediaType; import okhttp3.RequestBody; import org.jetbrains.annotations.Nullable; +import java.io.InputStream; +import java.util.Objects; + /** * Represents a file stream with associated metadata for file uploads. */ diff --git a/src/main/java/com/skyflow/generated/rest/core/InputStreamRequestBody.java b/flowvault/src/main/java/com/skyflow/generated/rest/core/InputStreamRequestBody.java similarity index 99% rename from src/main/java/com/skyflow/generated/rest/core/InputStreamRequestBody.java rename to flowvault/src/main/java/com/skyflow/generated/rest/core/InputStreamRequestBody.java index 545f6088..55c3c971 100644 --- a/src/main/java/com/skyflow/generated/rest/core/InputStreamRequestBody.java +++ b/flowvault/src/main/java/com/skyflow/generated/rest/core/InputStreamRequestBody.java @@ -3,9 +3,6 @@ */ package com.skyflow.generated.rest.core; -import java.io.IOException; -import java.io.InputStream; -import java.util.Objects; import okhttp3.MediaType; import okhttp3.RequestBody; import okhttp3.internal.Util; @@ -14,6 +11,10 @@ import okio.Source; import org.jetbrains.annotations.Nullable; +import java.io.IOException; +import java.io.InputStream; +import java.util.Objects; + /** * A custom implementation of OkHttp's RequestBody that wraps an InputStream. * This class allows streaming of data from an InputStream directly to an HTTP request body, diff --git a/src/main/java/com/skyflow/generated/rest/core/MediaTypes.java b/flowvault/src/main/java/com/skyflow/generated/rest/core/MediaTypes.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/core/MediaTypes.java rename to flowvault/src/main/java/com/skyflow/generated/rest/core/MediaTypes.java diff --git a/src/main/java/com/skyflow/generated/rest/core/Nullable.java b/flowvault/src/main/java/com/skyflow/generated/rest/core/Nullable.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/core/Nullable.java rename to flowvault/src/main/java/com/skyflow/generated/rest/core/Nullable.java diff --git a/src/main/java/com/skyflow/generated/rest/core/NullableNonemptyFilter.java b/flowvault/src/main/java/com/skyflow/generated/rest/core/NullableNonemptyFilter.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/core/NullableNonemptyFilter.java rename to flowvault/src/main/java/com/skyflow/generated/rest/core/NullableNonemptyFilter.java diff --git a/src/main/java/com/skyflow/generated/rest/core/ObjectMappers.java b/flowvault/src/main/java/com/skyflow/generated/rest/core/ObjectMappers.java similarity index 99% rename from src/main/java/com/skyflow/generated/rest/core/ObjectMappers.java rename to flowvault/src/main/java/com/skyflow/generated/rest/core/ObjectMappers.java index 3b7894e0..acec32b4 100644 --- a/src/main/java/com/skyflow/generated/rest/core/ObjectMappers.java +++ b/flowvault/src/main/java/com/skyflow/generated/rest/core/ObjectMappers.java @@ -10,6 +10,7 @@ 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 { diff --git a/src/main/java/com/skyflow/generated/rest/core/QueryStringMapper.java b/flowvault/src/main/java/com/skyflow/generated/rest/core/QueryStringMapper.java similarity index 97% rename from src/main/java/com/skyflow/generated/rest/core/QueryStringMapper.java rename to flowvault/src/main/java/com/skyflow/generated/rest/core/QueryStringMapper.java index e9e18fb9..c0687736 100644 --- a/src/main/java/com/skyflow/generated/rest/core/QueryStringMapper.java +++ b/flowvault/src/main/java/com/skyflow/generated/rest/core/QueryStringMapper.java @@ -7,14 +7,11 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; -import java.util.AbstractMap; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Map; import okhttp3.HttpUrl; import okhttp3.MultipartBody; +import java.util.*; + public class QueryStringMapper { private static final ObjectMapper MAPPER = ObjectMappers.JSON_MAPPER; diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/core/RequestOptions.java b/flowvault/src/main/java/com/skyflow/generated/rest/core/RequestOptions.java new file mode 100644 index 00000000..b8a8d14b --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/core/RequestOptions.java @@ -0,0 +1,87 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.core; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +public final class RequestOptions { + private final Optional timeout; + + private final TimeUnit timeoutTimeUnit; + + private final Map headers; + + private final Map> headerSuppliers; + + private RequestOptions( + Optional timeout, + TimeUnit timeoutTimeUnit, + Map headers, + Map> headerSuppliers) { + this.timeout = timeout; + this.timeoutTimeUnit = timeoutTimeUnit; + this.headers = headers; + this.headerSuppliers = headerSuppliers; + } + + public Optional getTimeout() { + return timeout; + } + + public TimeUnit getTimeoutTimeUnit() { + return timeoutTimeUnit; + } + + public Map getHeaders() { + Map headers = new HashMap<>(); + headers.putAll(this.headers); + this.headerSuppliers.forEach((key, supplier) -> { + headers.put(key, supplier.get()); + }); + return headers; + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private Optional timeout = Optional.empty(); + + private TimeUnit timeoutTimeUnit = TimeUnit.SECONDS; + + private final Map headers = new HashMap<>(); + + private final Map> headerSuppliers = new HashMap<>(); + + 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 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 RequestOptions build() { + return new RequestOptions(timeout, timeoutTimeUnit, headers, headerSuppliers); + } + } +} diff --git a/src/main/java/com/skyflow/generated/rest/core/ResponseBodyInputStream.java b/flowvault/src/main/java/com/skyflow/generated/rest/core/ResponseBodyInputStream.java similarity index 99% rename from src/main/java/com/skyflow/generated/rest/core/ResponseBodyInputStream.java rename to flowvault/src/main/java/com/skyflow/generated/rest/core/ResponseBodyInputStream.java index d8df7715..1bb0b5dc 100644 --- a/src/main/java/com/skyflow/generated/rest/core/ResponseBodyInputStream.java +++ b/flowvault/src/main/java/com/skyflow/generated/rest/core/ResponseBodyInputStream.java @@ -3,9 +3,10 @@ */ package com.skyflow.generated.rest.core; +import okhttp3.Response; + 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 diff --git a/src/main/java/com/skyflow/generated/rest/core/ResponseBodyReader.java b/flowvault/src/main/java/com/skyflow/generated/rest/core/ResponseBodyReader.java similarity index 99% rename from src/main/java/com/skyflow/generated/rest/core/ResponseBodyReader.java rename to flowvault/src/main/java/com/skyflow/generated/rest/core/ResponseBodyReader.java index ed894407..e6c1a525 100644 --- a/src/main/java/com/skyflow/generated/rest/core/ResponseBodyReader.java +++ b/flowvault/src/main/java/com/skyflow/generated/rest/core/ResponseBodyReader.java @@ -3,9 +3,10 @@ */ package com.skyflow.generated.rest.core; +import okhttp3.Response; + 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 diff --git a/src/main/java/com/skyflow/generated/rest/core/RetryInterceptor.java b/flowvault/src/main/java/com/skyflow/generated/rest/core/RetryInterceptor.java similarity index 99% rename from src/main/java/com/skyflow/generated/rest/core/RetryInterceptor.java rename to flowvault/src/main/java/com/skyflow/generated/rest/core/RetryInterceptor.java index eda7d265..7a28c3c9 100644 --- a/src/main/java/com/skyflow/generated/rest/core/RetryInterceptor.java +++ b/flowvault/src/main/java/com/skyflow/generated/rest/core/RetryInterceptor.java @@ -3,12 +3,13 @@ */ package com.skyflow.generated.rest.core; +import okhttp3.Interceptor; +import okhttp3.Response; + 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 { diff --git a/src/main/java/com/skyflow/generated/rest/core/Stream.java b/flowvault/src/main/java/com/skyflow/generated/rest/core/Stream.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/core/Stream.java rename to flowvault/src/main/java/com/skyflow/generated/rest/core/Stream.java diff --git a/src/main/java/com/skyflow/generated/rest/core/Suppliers.java b/flowvault/src/main/java/com/skyflow/generated/rest/core/Suppliers.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/core/Suppliers.java rename to flowvault/src/main/java/com/skyflow/generated/rest/core/Suppliers.java diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/resources/flowservice/AsyncFlowserviceClient.java b/flowvault/src/main/java/com/skyflow/generated/rest/resources/flowservice/AsyncFlowserviceClient.java new file mode 100644 index 00000000..1fd59fe9 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/resources/flowservice/AsyncFlowserviceClient.java @@ -0,0 +1,129 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.resources.flowservice; + +import com.skyflow.generated.rest.core.ClientOptions; +import com.skyflow.generated.rest.core.RequestOptions; +import com.skyflow.generated.rest.resources.flowservice.requests.*; +import com.skyflow.generated.rest.types.*; + +import java.util.concurrent.CompletableFuture; + +public class AsyncFlowserviceClient { + protected final ClientOptions clientOptions; + + private final AsyncRawFlowserviceClient rawClient; + + public AsyncFlowserviceClient(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + this.rawClient = new AsyncRawFlowserviceClient(clientOptions); + } + + /** + * Get responses with HTTP metadata like headers + */ + public AsyncRawFlowserviceClient withRawResponse() { + return this.rawClient; + } + + public CompletableFuture delete() { + return this.rawClient.delete().thenApply(response -> response.body()); + } + + public CompletableFuture delete(V1DeleteRequest request) { + return this.rawClient.delete(request).thenApply(response -> response.body()); + } + + public CompletableFuture delete(V1DeleteRequest request, RequestOptions requestOptions) { + return this.rawClient.delete(request, requestOptions).thenApply(response -> response.body()); + } + + public CompletableFuture get() { + return this.rawClient.get().thenApply(response -> response.body()); + } + + public CompletableFuture get(V1GetRequest request) { + return this.rawClient.get(request).thenApply(response -> response.body()); + } + + public CompletableFuture get(V1GetRequest request, RequestOptions requestOptions) { + return this.rawClient.get(request, requestOptions).thenApply(response -> response.body()); + } + + public CompletableFuture insert() { + return this.rawClient.insert().thenApply(response -> response.body()); + } + + public CompletableFuture insert(V1InsertRequest request) { + return this.rawClient.insert(request).thenApply(response -> response.body()); + } + + public CompletableFuture insert(V1InsertRequest request, RequestOptions requestOptions) { + return this.rawClient.insert(request, requestOptions).thenApply(response -> response.body()); + } + + public CompletableFuture update() { + return this.rawClient.update().thenApply(response -> response.body()); + } + + public CompletableFuture update(V1UpdateRequest request) { + return this.rawClient.update(request).thenApply(response -> response.body()); + } + + public CompletableFuture update(V1UpdateRequest request, RequestOptions requestOptions) { + return this.rawClient.update(request, requestOptions).thenApply(response -> response.body()); + } + + public CompletableFuture deletetoken() { + return this.rawClient.deletetoken().thenApply(response -> response.body()); + } + + public CompletableFuture deletetoken(V1FlowDeleteTokenRequest request) { + return this.rawClient.deletetoken(request).thenApply(response -> response.body()); + } + + public CompletableFuture deletetoken( + V1FlowDeleteTokenRequest request, RequestOptions requestOptions) { + return this.rawClient.deletetoken(request, requestOptions).thenApply(response -> response.body()); + } + + public CompletableFuture detokenize() { + return this.rawClient.detokenize().thenApply(response -> response.body()); + } + + public CompletableFuture detokenize(V1FlowDetokenizeRequest request) { + return this.rawClient.detokenize(request).thenApply(response -> response.body()); + } + + public CompletableFuture detokenize( + V1FlowDetokenizeRequest request, RequestOptions requestOptions) { + return this.rawClient.detokenize(request, requestOptions).thenApply(response -> response.body()); + } + + public CompletableFuture tokenize() { + return this.rawClient.tokenize().thenApply(response -> response.body()); + } + + public CompletableFuture tokenize(V1FlowTokenizeRequest request) { + return this.rawClient.tokenize(request).thenApply(response -> response.body()); + } + + public CompletableFuture tokenize( + V1FlowTokenizeRequest request, RequestOptions requestOptions) { + return this.rawClient.tokenize(request, requestOptions).thenApply(response -> response.body()); + } + + public CompletableFuture flowvaultmetrics() { + return this.rawClient.flowvaultmetrics().thenApply(response -> response.body()); + } + + public CompletableFuture flowvaultmetrics(V1FlowVaultMetricsRequest request) { + return this.rawClient.flowvaultmetrics(request).thenApply(response -> response.body()); + } + + public CompletableFuture flowvaultmetrics( + V1FlowVaultMetricsRequest request, RequestOptions requestOptions) { + return this.rawClient.flowvaultmetrics(request, requestOptions).thenApply(response -> response.body()); + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/resources/flowservice/AsyncRawFlowserviceClient.java b/flowvault/src/main/java/com/skyflow/generated/rest/resources/flowservice/AsyncRawFlowserviceClient.java new file mode 100644 index 00000000..17f85d0c --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/resources/flowservice/AsyncRawFlowserviceClient.java @@ -0,0 +1,533 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.resources.flowservice; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.skyflow.generated.rest.core.*; +import com.skyflow.generated.rest.resources.flowservice.requests.*; +import com.skyflow.generated.rest.types.*; +import okhttp3.*; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.util.concurrent.CompletableFuture; + +public class AsyncRawFlowserviceClient { + protected final ClientOptions clientOptions; + + public AsyncRawFlowserviceClient(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + } + + public CompletableFuture> delete() { + return delete(V1DeleteRequest.builder().build()); + } + + public CompletableFuture> delete(V1DeleteRequest request) { + return delete(request, null); + } + + public CompletableFuture> delete( + V1DeleteRequest request, RequestOptions requestOptions) { + HttpUrl httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("v2/records/delete") + .build(); + RequestBody body; + try { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); + } catch (JsonProcessingException e) { + throw new ApiClientException("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") + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + CompletableFuture> future = new CompletableFuture<>(); + client.newCall(okhttpRequest).enqueue(new Callback() { + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { + try (ResponseBody responseBody = response.body()) { + if (response.isSuccessful()) { + future.complete(new ApiClientHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBody.string(), V1DeleteResponse.class), + response)); + return; + } + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + future.completeExceptionally(new ApiClientApiException( + "Error with status code " + response.code(), + response.code(), + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class), + response)); + return; + } catch (IOException e) { + future.completeExceptionally(new ApiClientException("Network error executing HTTP request", e)); + } + } + + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + future.completeExceptionally(new ApiClientException("Network error executing HTTP request", e)); + } + }); + return future; + } + + public CompletableFuture> get() { + return get(V1GetRequest.builder().build()); + } + + public CompletableFuture> get(V1GetRequest request) { + return get(request, null); + } + + public CompletableFuture> get( + V1GetRequest request, RequestOptions requestOptions) { + HttpUrl httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("v2/records/get") + .build(); + RequestBody body; + try { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); + } catch (JsonProcessingException e) { + throw new ApiClientException("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") + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + CompletableFuture> future = new CompletableFuture<>(); + client.newCall(okhttpRequest).enqueue(new Callback() { + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { + try (ResponseBody responseBody = response.body()) { + if (response.isSuccessful()) { + future.complete(new ApiClientHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBody.string(), V1GetResponse.class), + response)); + return; + } + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + future.completeExceptionally(new ApiClientApiException( + "Error with status code " + response.code(), + response.code(), + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class), + response)); + return; + } catch (IOException e) { + future.completeExceptionally(new ApiClientException("Network error executing HTTP request", e)); + } + } + + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + future.completeExceptionally(new ApiClientException("Network error executing HTTP request", e)); + } + }); + return future; + } + + public CompletableFuture> insert() { + return insert(V1InsertRequest.builder().build()); + } + + public CompletableFuture> insert(V1InsertRequest request) { + return insert(request, null); + } + + public CompletableFuture> insert( + V1InsertRequest request, RequestOptions requestOptions) { + HttpUrl httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("v2/records/insert") + .build(); + RequestBody body; + try { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); + } catch (JsonProcessingException e) { + throw new ApiClientException("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") + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + CompletableFuture> future = new CompletableFuture<>(); + client.newCall(okhttpRequest).enqueue(new Callback() { + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { + try (ResponseBody responseBody = response.body()) { + if (response.isSuccessful()) { + future.complete(new ApiClientHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBody.string(), V1InsertResponse.class), + response)); + return; + } + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + future.completeExceptionally(new ApiClientApiException( + "Error with status code " + response.code(), + response.code(), + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class), + response)); + return; + } catch (IOException e) { + future.completeExceptionally(new ApiClientException("Network error executing HTTP request", e)); + } + } + + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + future.completeExceptionally(new ApiClientException("Network error executing HTTP request", e)); + } + }); + return future; + } + + public CompletableFuture> update() { + return update(V1UpdateRequest.builder().build()); + } + + public CompletableFuture> update(V1UpdateRequest request) { + return update(request, null); + } + + public CompletableFuture> update( + V1UpdateRequest request, RequestOptions requestOptions) { + HttpUrl httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("v2/records/update") + .build(); + RequestBody body; + try { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); + } catch (JsonProcessingException e) { + throw new ApiClientException("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") + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + CompletableFuture> future = new CompletableFuture<>(); + client.newCall(okhttpRequest).enqueue(new Callback() { + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { + try (ResponseBody responseBody = response.body()) { + if (response.isSuccessful()) { + future.complete(new ApiClientHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBody.string(), V1UpdateResponse.class), + response)); + return; + } + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + future.completeExceptionally(new ApiClientApiException( + "Error with status code " + response.code(), + response.code(), + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class), + response)); + return; + } catch (IOException e) { + future.completeExceptionally(new ApiClientException("Network error executing HTTP request", e)); + } + } + + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + future.completeExceptionally(new ApiClientException("Network error executing HTTP request", e)); + } + }); + return future; + } + + public CompletableFuture> deletetoken() { + return deletetoken(V1FlowDeleteTokenRequest.builder().build()); + } + + public CompletableFuture> deletetoken( + V1FlowDeleteTokenRequest request) { + return deletetoken(request, null); + } + + public CompletableFuture> deletetoken( + V1FlowDeleteTokenRequest request, RequestOptions requestOptions) { + HttpUrl httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("v2/tokens/delete") + .build(); + RequestBody body; + try { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); + } catch (JsonProcessingException e) { + throw new ApiClientException("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") + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + CompletableFuture> future = new CompletableFuture<>(); + client.newCall(okhttpRequest).enqueue(new Callback() { + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { + try (ResponseBody responseBody = response.body()) { + if (response.isSuccessful()) { + future.complete(new ApiClientHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue( + responseBody.string(), V1FlowDeleteTokenResponse.class), + response)); + return; + } + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + future.completeExceptionally(new ApiClientApiException( + "Error with status code " + response.code(), + response.code(), + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class), + response)); + return; + } catch (IOException e) { + future.completeExceptionally(new ApiClientException("Network error executing HTTP request", e)); + } + } + + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + future.completeExceptionally(new ApiClientException("Network error executing HTTP request", e)); + } + }); + return future; + } + + public CompletableFuture> detokenize() { + return detokenize(V1FlowDetokenizeRequest.builder().build()); + } + + public CompletableFuture> detokenize( + V1FlowDetokenizeRequest request) { + return detokenize(request, null); + } + + public CompletableFuture> detokenize( + V1FlowDetokenizeRequest request, RequestOptions requestOptions) { + HttpUrl httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("v2/tokens/detokenize") + .build(); + RequestBody body; + try { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); + } catch (JsonProcessingException e) { + throw new ApiClientException("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") + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + CompletableFuture> future = new CompletableFuture<>(); + client.newCall(okhttpRequest).enqueue(new Callback() { + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { + try (ResponseBody responseBody = response.body()) { + if (response.isSuccessful()) { + future.complete(new ApiClientHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue( + responseBody.string(), V1FlowDetokenizeResponse.class), + response)); + return; + } + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + future.completeExceptionally(new ApiClientApiException( + "Error with status code " + response.code(), + response.code(), + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class), + response)); + return; + } catch (IOException e) { + future.completeExceptionally(new ApiClientException("Network error executing HTTP request", e)); + } + } + + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + future.completeExceptionally(new ApiClientException("Network error executing HTTP request", e)); + } + }); + return future; + } + + public CompletableFuture> tokenize() { + return tokenize(V1FlowTokenizeRequest.builder().build()); + } + + public CompletableFuture> tokenize(V1FlowTokenizeRequest request) { + return tokenize(request, null); + } + + public CompletableFuture> tokenize( + V1FlowTokenizeRequest request, RequestOptions requestOptions) { + HttpUrl httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("v2/tokens/tokenize") + .build(); + RequestBody body; + try { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); + } catch (JsonProcessingException e) { + throw new ApiClientException("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") + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + CompletableFuture> future = new CompletableFuture<>(); + client.newCall(okhttpRequest).enqueue(new Callback() { + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { + try (ResponseBody responseBody = response.body()) { + if (response.isSuccessful()) { + future.complete(new ApiClientHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue( + responseBody.string(), V1FlowTokenizeResponse.class), + response)); + return; + } + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + future.completeExceptionally(new ApiClientApiException( + "Error with status code " + response.code(), + response.code(), + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class), + response)); + return; + } catch (IOException e) { + future.completeExceptionally(new ApiClientException("Network error executing HTTP request", e)); + } + } + + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + future.completeExceptionally(new ApiClientException("Network error executing HTTP request", e)); + } + }); + return future; + } + + public CompletableFuture> flowvaultmetrics() { + return flowvaultmetrics(V1FlowVaultMetricsRequest.builder().build()); + } + + public CompletableFuture> flowvaultmetrics( + V1FlowVaultMetricsRequest request) { + return flowvaultmetrics(request, null); + } + + public CompletableFuture> flowvaultmetrics( + V1FlowVaultMetricsRequest request, RequestOptions requestOptions) { + HttpUrl httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("v2/vaults/metrics") + .build(); + RequestBody body; + try { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); + } catch (JsonProcessingException e) { + throw new ApiClientException("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") + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + CompletableFuture> future = new CompletableFuture<>(); + client.newCall(okhttpRequest).enqueue(new Callback() { + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { + try (ResponseBody responseBody = response.body()) { + if (response.isSuccessful()) { + future.complete(new ApiClientHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue( + responseBody.string(), V1FlowVaultMetricsResponse.class), + response)); + return; + } + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + future.completeExceptionally(new ApiClientApiException( + "Error with status code " + response.code(), + response.code(), + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class), + response)); + return; + } catch (IOException e) { + future.completeExceptionally(new ApiClientException("Network error executing HTTP request", e)); + } + } + + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + future.completeExceptionally(new ApiClientException("Network error executing HTTP request", e)); + } + }); + return future; + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/resources/flowservice/FlowserviceClient.java b/flowvault/src/main/java/com/skyflow/generated/rest/resources/flowservice/FlowserviceClient.java new file mode 100644 index 00000000..8da9c9a5 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/resources/flowservice/FlowserviceClient.java @@ -0,0 +1,124 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.resources.flowservice; + +import com.skyflow.generated.rest.core.ClientOptions; +import com.skyflow.generated.rest.core.RequestOptions; +import com.skyflow.generated.rest.resources.flowservice.requests.*; +import com.skyflow.generated.rest.types.*; + +public class FlowserviceClient { + protected final ClientOptions clientOptions; + + private final RawFlowserviceClient rawClient; + + public FlowserviceClient(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + this.rawClient = new RawFlowserviceClient(clientOptions); + } + + /** + * Get responses with HTTP metadata like headers + */ + public RawFlowserviceClient withRawResponse() { + return this.rawClient; + } + + public V1DeleteResponse delete() { + return this.rawClient.delete().body(); + } + + public V1DeleteResponse delete(V1DeleteRequest request) { + return this.rawClient.delete(request).body(); + } + + public V1DeleteResponse delete(V1DeleteRequest request, RequestOptions requestOptions) { + return this.rawClient.delete(request, requestOptions).body(); + } + + public V1GetResponse get() { + return this.rawClient.get().body(); + } + + public V1GetResponse get(V1GetRequest request) { + return this.rawClient.get(request).body(); + } + + public V1GetResponse get(V1GetRequest request, RequestOptions requestOptions) { + return this.rawClient.get(request, requestOptions).body(); + } + + public V1InsertResponse insert() { + return this.rawClient.insert().body(); + } + + public V1InsertResponse insert(V1InsertRequest request) { + return this.rawClient.insert(request).body(); + } + + public V1InsertResponse insert(V1InsertRequest request, RequestOptions requestOptions) { + return this.rawClient.insert(request, requestOptions).body(); + } + + public V1UpdateResponse update() { + return this.rawClient.update().body(); + } + + public V1UpdateResponse update(V1UpdateRequest request) { + return this.rawClient.update(request).body(); + } + + public V1UpdateResponse update(V1UpdateRequest request, RequestOptions requestOptions) { + return this.rawClient.update(request, requestOptions).body(); + } + + public V1FlowDeleteTokenResponse deletetoken() { + return this.rawClient.deletetoken().body(); + } + + public V1FlowDeleteTokenResponse deletetoken(V1FlowDeleteTokenRequest request) { + return this.rawClient.deletetoken(request).body(); + } + + public V1FlowDeleteTokenResponse deletetoken(V1FlowDeleteTokenRequest request, RequestOptions requestOptions) { + return this.rawClient.deletetoken(request, requestOptions).body(); + } + + public V1FlowDetokenizeResponse detokenize() { + return this.rawClient.detokenize().body(); + } + + public V1FlowDetokenizeResponse detokenize(V1FlowDetokenizeRequest request) { + return this.rawClient.detokenize(request).body(); + } + + public V1FlowDetokenizeResponse detokenize(V1FlowDetokenizeRequest request, RequestOptions requestOptions) { + return this.rawClient.detokenize(request, requestOptions).body(); + } + + public V1FlowTokenizeResponse tokenize() { + return this.rawClient.tokenize().body(); + } + + public V1FlowTokenizeResponse tokenize(V1FlowTokenizeRequest request) { + return this.rawClient.tokenize(request).body(); + } + + public V1FlowTokenizeResponse tokenize(V1FlowTokenizeRequest request, RequestOptions requestOptions) { + return this.rawClient.tokenize(request, requestOptions).body(); + } + + public V1FlowVaultMetricsResponse flowvaultmetrics() { + return this.rawClient.flowvaultmetrics().body(); + } + + public V1FlowVaultMetricsResponse flowvaultmetrics(V1FlowVaultMetricsRequest request) { + return this.rawClient.flowvaultmetrics(request).body(); + } + + public V1FlowVaultMetricsResponse flowvaultmetrics( + V1FlowVaultMetricsRequest request, RequestOptions requestOptions) { + return this.rawClient.flowvaultmetrics(request, requestOptions).body(); + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/resources/flowservice/RawFlowserviceClient.java b/flowvault/src/main/java/com/skyflow/generated/rest/resources/flowservice/RawFlowserviceClient.java new file mode 100644 index 00000000..f1203085 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/resources/flowservice/RawFlowserviceClient.java @@ -0,0 +1,412 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.resources.flowservice; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.skyflow.generated.rest.core.*; +import com.skyflow.generated.rest.resources.flowservice.requests.*; +import com.skyflow.generated.rest.types.*; +import okhttp3.*; + +import java.io.IOException; + +public class RawFlowserviceClient { + protected final ClientOptions clientOptions; + + public RawFlowserviceClient(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + } + + public ApiClientHttpResponse delete() { + return delete(V1DeleteRequest.builder().build()); + } + + public ApiClientHttpResponse delete(V1DeleteRequest request) { + return delete(request, null); + } + + public ApiClientHttpResponse delete(V1DeleteRequest request, RequestOptions requestOptions) { + HttpUrl httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("v2/records/delete") + .build(); + RequestBody body; + try { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); + } catch (JsonProcessingException e) { + throw new ApiClientException("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") + .addHeader("Accept", "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 new ApiClientHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBody.string(), V1DeleteResponse.class), response); + } + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + throw new ApiClientApiException( + "Error with status code " + response.code(), + response.code(), + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class), + response); + } catch (IOException e) { + throw new ApiClientException("Network error executing HTTP request", e); + } + } + + public ApiClientHttpResponse get() { + return get(V1GetRequest.builder().build()); + } + + public ApiClientHttpResponse get(V1GetRequest request) { + return get(request, null); + } + + public ApiClientHttpResponse get(V1GetRequest request, RequestOptions requestOptions) { + HttpUrl httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("v2/records/get") + .build(); + RequestBody body; + try { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); + } catch (JsonProcessingException e) { + throw new ApiClientException("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") + .addHeader("Accept", "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 new ApiClientHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBody.string(), V1GetResponse.class), response); + } + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + throw new ApiClientApiException( + "Error with status code " + response.code(), + response.code(), + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class), + response); + } catch (IOException e) { + throw new ApiClientException("Network error executing HTTP request", e); + } + } + + public ApiClientHttpResponse insert() { + return insert(V1InsertRequest.builder().build()); + } + + public ApiClientHttpResponse insert(V1InsertRequest request) { + return insert(request, null); + } + + public ApiClientHttpResponse insert(V1InsertRequest request, RequestOptions requestOptions) { + HttpUrl httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("v2/records/insert") + .build(); + RequestBody body; + try { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); + } catch (JsonProcessingException e) { + throw new ApiClientException("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") + .addHeader("Accept", "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 new ApiClientHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBody.string(), V1InsertResponse.class), response); + } + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + throw new ApiClientApiException( + "Error with status code " + response.code(), + response.code(), + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class), + response); + } catch (IOException e) { + throw new ApiClientException("Network error executing HTTP request", e); + } + } + + public ApiClientHttpResponse update() { + return update(V1UpdateRequest.builder().build()); + } + + public ApiClientHttpResponse update(V1UpdateRequest request) { + return update(request, null); + } + + public ApiClientHttpResponse update(V1UpdateRequest request, RequestOptions requestOptions) { + HttpUrl httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("v2/records/update") + .build(); + RequestBody body; + try { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); + } catch (JsonProcessingException e) { + throw new ApiClientException("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") + .addHeader("Accept", "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 new ApiClientHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBody.string(), V1UpdateResponse.class), response); + } + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + throw new ApiClientApiException( + "Error with status code " + response.code(), + response.code(), + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class), + response); + } catch (IOException e) { + throw new ApiClientException("Network error executing HTTP request", e); + } + } + + public ApiClientHttpResponse deletetoken() { + return deletetoken(V1FlowDeleteTokenRequest.builder().build()); + } + + public ApiClientHttpResponse deletetoken(V1FlowDeleteTokenRequest request) { + return deletetoken(request, null); + } + + public ApiClientHttpResponse deletetoken( + V1FlowDeleteTokenRequest request, RequestOptions requestOptions) { + HttpUrl httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("v2/tokens/delete") + .build(); + RequestBody body; + try { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); + } catch (JsonProcessingException e) { + throw new ApiClientException("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") + .addHeader("Accept", "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 new ApiClientHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBody.string(), V1FlowDeleteTokenResponse.class), + response); + } + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + throw new ApiClientApiException( + "Error with status code " + response.code(), + response.code(), + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class), + response); + } catch (IOException e) { + throw new ApiClientException("Network error executing HTTP request", e); + } + } + + public ApiClientHttpResponse detokenize() { + return detokenize(V1FlowDetokenizeRequest.builder().build()); + } + + public ApiClientHttpResponse detokenize(V1FlowDetokenizeRequest request) { + return detokenize(request, null); + } + + public ApiClientHttpResponse detokenize( + V1FlowDetokenizeRequest request, RequestOptions requestOptions) { + HttpUrl httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("v2/tokens/detokenize") + .build(); + RequestBody body; + try { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); + } catch (JsonProcessingException e) { + throw new ApiClientException("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") + .addHeader("Accept", "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 new ApiClientHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBody.string(), V1FlowDetokenizeResponse.class), + response); + } + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + throw new ApiClientApiException( + "Error with status code " + response.code(), + response.code(), + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class), + response); + } catch (IOException e) { + throw new ApiClientException("Network error executing HTTP request", e); + } + } + + public ApiClientHttpResponse tokenize() { + return tokenize(V1FlowTokenizeRequest.builder().build()); + } + + public ApiClientHttpResponse tokenize(V1FlowTokenizeRequest request) { + return tokenize(request, null); + } + + public ApiClientHttpResponse tokenize( + V1FlowTokenizeRequest request, RequestOptions requestOptions) { + HttpUrl httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("v2/tokens/tokenize") + .build(); + RequestBody body; + try { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); + } catch (JsonProcessingException e) { + throw new ApiClientException("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") + .addHeader("Accept", "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 new ApiClientHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBody.string(), V1FlowTokenizeResponse.class), + response); + } + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + throw new ApiClientApiException( + "Error with status code " + response.code(), + response.code(), + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class), + response); + } catch (IOException e) { + throw new ApiClientException("Network error executing HTTP request", e); + } + } + + public ApiClientHttpResponse flowvaultmetrics() { + return flowvaultmetrics(V1FlowVaultMetricsRequest.builder().build()); + } + + public ApiClientHttpResponse flowvaultmetrics(V1FlowVaultMetricsRequest request) { + return flowvaultmetrics(request, null); + } + + public ApiClientHttpResponse flowvaultmetrics( + V1FlowVaultMetricsRequest request, RequestOptions requestOptions) { + HttpUrl httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("v2/vaults/metrics") + .build(); + RequestBody body; + try { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); + } catch (JsonProcessingException e) { + throw new ApiClientException("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") + .addHeader("Accept", "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 new ApiClientHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBody.string(), V1FlowVaultMetricsResponse.class), + response); + } + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + throw new ApiClientApiException( + "Error with status code " + response.code(), + response.code(), + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class), + response); + } catch (IOException e) { + throw new ApiClientException("Network error executing HTTP request", e); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/resources/flowservice/requests/V1DeleteRequest.java b/flowvault/src/main/java/com/skyflow/generated/rest/resources/flowservice/requests/V1DeleteRequest.java new file mode 100644 index 00000000..27e22a8d --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/resources/flowservice/requests/V1DeleteRequest.java @@ -0,0 +1,186 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.resources.flowservice.requests; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.rest.core.ObjectMappers; +import com.skyflow.generated.rest.types.V1UniqueValue; + +import java.util.*; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = V1DeleteRequest.Builder.class) +public final class V1DeleteRequest { + private final Optional vaultId; + + private final Optional tableName; + + private final Optional> skyflowIDs; + + private final Optional> uniqueValues; + + private final Map additionalProperties; + + private V1DeleteRequest( + Optional vaultId, + Optional tableName, + Optional> skyflowIDs, + Optional> uniqueValues, + Map additionalProperties) { + this.vaultId = vaultId; + this.tableName = tableName; + this.skyflowIDs = skyflowIDs; + this.uniqueValues = uniqueValues; + this.additionalProperties = additionalProperties; + } + + /** + * @return ID of the vault where data is being deleted + */ + @JsonProperty("vaultID") + public Optional getVaultId() { + return vaultId; + } + + /** + * @return Name of the table where data is being deleted + */ + @JsonProperty("tableName") + public Optional getTableName() { + return tableName; + } + + /** + * @return Skyflow ID for the record to be deleted + */ + @JsonProperty("skyflowIDs") + public Optional> getSkyflowIDs() { + return skyflowIDs; + } + + /** + * @return List of unique constraint values to query records by data + */ + @JsonProperty("uniqueValues") + public Optional> getUniqueValues() { + return uniqueValues; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V1DeleteRequest && equalTo((V1DeleteRequest) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(V1DeleteRequest other) { + return vaultId.equals(other.vaultId) + && tableName.equals(other.tableName) + && skyflowIDs.equals(other.skyflowIDs) + && uniqueValues.equals(other.uniqueValues); + } + + @Override + public int hashCode() { + return Objects.hash(this.vaultId, this.tableName, this.skyflowIDs, this.uniqueValues); + } + + @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 vaultId = Optional.empty(); + + private Optional tableName = Optional.empty(); + + private Optional> skyflowIDs = Optional.empty(); + + private Optional> uniqueValues = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(V1DeleteRequest other) { + vaultId(other.getVaultId()); + tableName(other.getTableName()); + skyflowIDs(other.getSkyflowIDs()); + uniqueValues(other.getUniqueValues()); + return this; + } + + /** + *

ID of the vault where data is being deleted

+ */ + @JsonSetter(value = "vaultID", nulls = Nulls.SKIP) + public Builder vaultId(Optional vaultId) { + this.vaultId = vaultId; + return this; + } + + public Builder vaultId(String vaultId) { + this.vaultId = Optional.ofNullable(vaultId); + return this; + } + + /** + *

Name of the table where data is being deleted

+ */ + @JsonSetter(value = "tableName", nulls = Nulls.SKIP) + public Builder tableName(Optional tableName) { + this.tableName = tableName; + return this; + } + + public Builder tableName(String tableName) { + this.tableName = Optional.ofNullable(tableName); + return this; + } + + /** + *

Skyflow ID for the record to be deleted

+ */ + @JsonSetter(value = "skyflowIDs", nulls = Nulls.SKIP) + public Builder skyflowIDs(Optional> skyflowIDs) { + this.skyflowIDs = skyflowIDs; + return this; + } + + public Builder skyflowIDs(List skyflowIDs) { + this.skyflowIDs = Optional.ofNullable(skyflowIDs); + return this; + } + + /** + *

List of unique constraint values to query records by data

+ */ + @JsonSetter(value = "uniqueValues", nulls = Nulls.SKIP) + public Builder uniqueValues(Optional> uniqueValues) { + this.uniqueValues = uniqueValues; + return this; + } + + public Builder uniqueValues(List uniqueValues) { + this.uniqueValues = Optional.ofNullable(uniqueValues); + return this; + } + + public V1DeleteRequest build() { + return new V1DeleteRequest(vaultId, tableName, skyflowIDs, uniqueValues, additionalProperties); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/resources/flowservice/requests/V1FlowDeleteTokenRequest.java b/flowvault/src/main/java/com/skyflow/generated/rest/resources/flowservice/requests/V1FlowDeleteTokenRequest.java new file mode 100644 index 00000000..6bc8a4e6 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/resources/flowservice/requests/V1FlowDeleteTokenRequest.java @@ -0,0 +1,122 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.resources.flowservice.requests; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.rest.core.ObjectMappers; + +import java.util.*; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = V1FlowDeleteTokenRequest.Builder.class) +public final class V1FlowDeleteTokenRequest { + private final Optional vaultId; + + private final Optional> tokens; + + private final Map additionalProperties; + + private V1FlowDeleteTokenRequest( + Optional vaultId, Optional> tokens, Map additionalProperties) { + this.vaultId = vaultId; + this.tokens = tokens; + this.additionalProperties = additionalProperties; + } + + /** + * @return Vault ID + */ + @JsonProperty("vaultID") + public Optional getVaultId() { + return vaultId; + } + + /** + * @return Token value + */ + @JsonProperty("tokens") + public Optional> getTokens() { + return tokens; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V1FlowDeleteTokenRequest && equalTo((V1FlowDeleteTokenRequest) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(V1FlowDeleteTokenRequest other) { + return vaultId.equals(other.vaultId) && tokens.equals(other.tokens); + } + + @Override + public int hashCode() { + return Objects.hash(this.vaultId, this.tokens); + } + + @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 vaultId = Optional.empty(); + + private Optional> tokens = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(V1FlowDeleteTokenRequest other) { + vaultId(other.getVaultId()); + tokens(other.getTokens()); + return this; + } + + /** + *

Vault ID

+ */ + @JsonSetter(value = "vaultID", nulls = Nulls.SKIP) + public Builder vaultId(Optional vaultId) { + this.vaultId = vaultId; + return this; + } + + public Builder vaultId(String vaultId) { + this.vaultId = Optional.ofNullable(vaultId); + return this; + } + + /** + *

Token value

+ */ + @JsonSetter(value = "tokens", nulls = Nulls.SKIP) + public Builder tokens(Optional> tokens) { + this.tokens = tokens; + return this; + } + + public Builder tokens(List tokens) { + this.tokens = Optional.ofNullable(tokens); + return this; + } + + public V1FlowDeleteTokenRequest build() { + return new V1FlowDeleteTokenRequest(vaultId, tokens, additionalProperties); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/resources/flowservice/requests/V1FlowDetokenizeRequest.java b/flowvault/src/main/java/com/skyflow/generated/rest/resources/flowservice/requests/V1FlowDetokenizeRequest.java new file mode 100644 index 00000000..bdbe3bf2 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/resources/flowservice/requests/V1FlowDetokenizeRequest.java @@ -0,0 +1,156 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.resources.flowservice.requests; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.rest.core.ObjectMappers; +import com.skyflow.generated.rest.types.V1TokenGroupRedactions; + +import java.util.*; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = V1FlowDetokenizeRequest.Builder.class) +public final class V1FlowDetokenizeRequest { + private final Optional vaultId; + + private final Optional> tokens; + + private final Optional> tokenGroupRedactions; + + private final Map additionalProperties; + + private V1FlowDetokenizeRequest( + Optional vaultId, + Optional> tokens, + Optional> tokenGroupRedactions, + Map additionalProperties) { + this.vaultId = vaultId; + this.tokens = tokens; + this.tokenGroupRedactions = tokenGroupRedactions; + this.additionalProperties = additionalProperties; + } + + /** + * @return ID of the vault where detokenizing + */ + @JsonProperty("vaultID") + public Optional getVaultId() { + return vaultId; + } + + /** + * @return Token to be detokenized + */ + @JsonProperty("tokens") + public Optional> getTokens() { + return tokens; + } + + /** + * @return List of token groups to be redacted. + */ + @JsonProperty("tokenGroupRedactions") + public Optional> getTokenGroupRedactions() { + return tokenGroupRedactions; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V1FlowDetokenizeRequest && equalTo((V1FlowDetokenizeRequest) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(V1FlowDetokenizeRequest other) { + return vaultId.equals(other.vaultId) + && tokens.equals(other.tokens) + && tokenGroupRedactions.equals(other.tokenGroupRedactions); + } + + @Override + public int hashCode() { + return Objects.hash(this.vaultId, this.tokens, this.tokenGroupRedactions); + } + + @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 vaultId = Optional.empty(); + + private Optional> tokens = Optional.empty(); + + private Optional> tokenGroupRedactions = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(V1FlowDetokenizeRequest other) { + vaultId(other.getVaultId()); + tokens(other.getTokens()); + tokenGroupRedactions(other.getTokenGroupRedactions()); + return this; + } + + /** + *

ID of the vault where detokenizing

+ */ + @JsonSetter(value = "vaultID", nulls = Nulls.SKIP) + public Builder vaultId(Optional vaultId) { + this.vaultId = vaultId; + return this; + } + + public Builder vaultId(String vaultId) { + this.vaultId = Optional.ofNullable(vaultId); + return this; + } + + /** + *

Token to be detokenized

+ */ + @JsonSetter(value = "tokens", nulls = Nulls.SKIP) + public Builder tokens(Optional> tokens) { + this.tokens = tokens; + return this; + } + + public Builder tokens(List tokens) { + this.tokens = Optional.ofNullable(tokens); + return this; + } + + /** + *

List of token groups to be redacted.

+ */ + @JsonSetter(value = "tokenGroupRedactions", nulls = Nulls.SKIP) + public Builder tokenGroupRedactions(Optional> tokenGroupRedactions) { + this.tokenGroupRedactions = tokenGroupRedactions; + return this; + } + + public Builder tokenGroupRedactions(List tokenGroupRedactions) { + this.tokenGroupRedactions = Optional.ofNullable(tokenGroupRedactions); + return this; + } + + public V1FlowDetokenizeRequest build() { + return new V1FlowDetokenizeRequest(vaultId, tokens, tokenGroupRedactions, additionalProperties); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/resources/flowservice/requests/V1FlowTokenizeRequest.java b/flowvault/src/main/java/com/skyflow/generated/rest/resources/flowservice/requests/V1FlowTokenizeRequest.java new file mode 100644 index 00000000..a27d7b62 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/resources/flowservice/requests/V1FlowTokenizeRequest.java @@ -0,0 +1,125 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.resources.flowservice.requests; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.rest.core.ObjectMappers; +import com.skyflow.generated.rest.types.V1FlowTokenizeRequestObject; + +import java.util.*; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = V1FlowTokenizeRequest.Builder.class) +public final class V1FlowTokenizeRequest { + private final Optional vaultId; + + private final Optional> data; + + private final Map additionalProperties; + + private V1FlowTokenizeRequest( + Optional vaultId, + Optional> data, + Map additionalProperties) { + this.vaultId = vaultId; + this.data = data; + this.additionalProperties = additionalProperties; + } + + /** + * @return Vault ID. + */ + @JsonProperty("vaultID") + public Optional getVaultId() { + return vaultId; + } + + /** + * @return Data to be tokenized + */ + @JsonProperty("data") + public Optional> getData() { + return data; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V1FlowTokenizeRequest && equalTo((V1FlowTokenizeRequest) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(V1FlowTokenizeRequest other) { + return vaultId.equals(other.vaultId) && data.equals(other.data); + } + + @Override + public int hashCode() { + return Objects.hash(this.vaultId, this.data); + } + + @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 vaultId = Optional.empty(); + + private Optional> data = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(V1FlowTokenizeRequest other) { + vaultId(other.getVaultId()); + data(other.getData()); + return this; + } + + /** + *

Vault ID.

+ */ + @JsonSetter(value = "vaultID", nulls = Nulls.SKIP) + public Builder vaultId(Optional vaultId) { + this.vaultId = vaultId; + return this; + } + + public Builder vaultId(String vaultId) { + this.vaultId = Optional.ofNullable(vaultId); + return this; + } + + /** + *

Data to be tokenized

+ */ + @JsonSetter(value = "data", nulls = Nulls.SKIP) + public Builder data(Optional> data) { + this.data = data; + return this; + } + + public Builder data(List data) { + this.data = Optional.ofNullable(data); + return this; + } + + public V1FlowTokenizeRequest build() { + return new V1FlowTokenizeRequest(vaultId, data, additionalProperties); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/resources/flowservice/requests/V1FlowVaultMetricsRequest.java b/flowvault/src/main/java/com/skyflow/generated/rest/resources/flowservice/requests/V1FlowVaultMetricsRequest.java new file mode 100644 index 00000000..026d2d30 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/resources/flowservice/requests/V1FlowVaultMetricsRequest.java @@ -0,0 +1,96 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.resources.flowservice.requests; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.rest.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 = V1FlowVaultMetricsRequest.Builder.class) +public final class V1FlowVaultMetricsRequest { + private final Optional vaultId; + + private final Map additionalProperties; + + private V1FlowVaultMetricsRequest(Optional vaultId, Map additionalProperties) { + this.vaultId = vaultId; + this.additionalProperties = additionalProperties; + } + + /** + * @return ID of the vault to get metrics for + */ + @JsonProperty("vaultID") + public Optional getVaultId() { + return vaultId; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V1FlowVaultMetricsRequest && equalTo((V1FlowVaultMetricsRequest) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(V1FlowVaultMetricsRequest other) { + return vaultId.equals(other.vaultId); + } + + @Override + public int hashCode() { + return Objects.hash(this.vaultId); + } + + @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 vaultId = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(V1FlowVaultMetricsRequest other) { + vaultId(other.getVaultId()); + return this; + } + + /** + *

ID of the vault to get metrics for

+ */ + @JsonSetter(value = "vaultID", nulls = Nulls.SKIP) + public Builder vaultId(Optional vaultId) { + this.vaultId = vaultId; + return this; + } + + public Builder vaultId(String vaultId) { + this.vaultId = Optional.ofNullable(vaultId); + return this; + } + + public V1FlowVaultMetricsRequest build() { + return new V1FlowVaultMetricsRequest(vaultId, additionalProperties); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/resources/flowservice/requests/V1GetRequest.java b/flowvault/src/main/java/com/skyflow/generated/rest/resources/flowservice/requests/V1GetRequest.java new file mode 100644 index 00000000..415f5291 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/resources/flowservice/requests/V1GetRequest.java @@ -0,0 +1,357 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.resources.flowservice.requests; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.rest.core.ObjectMappers; +import com.skyflow.generated.rest.types.V1ColumnRedactions; +import com.skyflow.generated.rest.types.V1GetRequestData; +import com.skyflow.generated.rest.types.V1UniqueValue; + +import java.util.*; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = V1GetRequest.Builder.class) +public final class V1GetRequest { + private final Optional vaultId; + + private final Optional tableName; + + private final Optional> skyflowIDs; + + private final Optional> columnRedactions; + + private final Optional> columns; + + private final Optional limit; + + private final Optional offset; + + private final Optional> uniqueValues; + + private final Optional> records; + + private final Map additionalProperties; + + private V1GetRequest( + Optional vaultId, + Optional tableName, + Optional> skyflowIDs, + Optional> columnRedactions, + Optional> columns, + Optional limit, + Optional offset, + Optional> uniqueValues, + Optional> records, + Map additionalProperties) { + this.vaultId = vaultId; + this.tableName = tableName; + this.skyflowIDs = skyflowIDs; + this.columnRedactions = columnRedactions; + this.columns = columns; + this.limit = limit; + this.offset = offset; + this.uniqueValues = uniqueValues; + this.records = records; + this.additionalProperties = additionalProperties; + } + + /** + * @return ID of the vault where data is being fetched + */ + @JsonProperty("vaultID") + public Optional getVaultId() { + return vaultId; + } + + /** + * @return Name of the table where data is being fetched + */ + @JsonProperty("tableName") + public Optional getTableName() { + return tableName; + } + + /** + * @return Skyflow ID for the record to be fetched + */ + @JsonProperty("skyflowIDs") + public Optional> getSkyflowIDs() { + return skyflowIDs; + } + + /** + * @return List of columns to be redacted. + */ + @JsonProperty("columnRedactions") + public Optional> getColumnRedactions() { + return columnRedactions; + } + + /** + * @return List of columns to be fetched. + */ + @JsonProperty("columns") + public Optional> getColumns() { + return columns; + } + + /** + * @return Limit for the number of records to be fetched + */ + @JsonProperty("limit") + public Optional getLimit() { + return limit; + } + + /** + * @return Offset for the number of records to be fetched + */ + @JsonProperty("offset") + public Optional getOffset() { + return offset; + } + + /** + * @return List of unique constraint values to query records by data + */ + @JsonProperty("uniqueValues") + public Optional> getUniqueValues() { + return uniqueValues; + } + + /** + * @return List of records to be fetched. This field contains tableName and skyflowIDs belonging to the table. + */ + @JsonProperty("records") + public Optional> getRecords() { + return records; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V1GetRequest && equalTo((V1GetRequest) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(V1GetRequest other) { + return vaultId.equals(other.vaultId) + && tableName.equals(other.tableName) + && skyflowIDs.equals(other.skyflowIDs) + && columnRedactions.equals(other.columnRedactions) + && columns.equals(other.columns) + && limit.equals(other.limit) + && offset.equals(other.offset) + && uniqueValues.equals(other.uniqueValues) + && records.equals(other.records); + } + + @Override + public int hashCode() { + return Objects.hash( + this.vaultId, + this.tableName, + this.skyflowIDs, + this.columnRedactions, + this.columns, + this.limit, + this.offset, + this.uniqueValues, + this.records); + } + + @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 vaultId = Optional.empty(); + + private Optional tableName = Optional.empty(); + + private Optional> skyflowIDs = Optional.empty(); + + private Optional> columnRedactions = Optional.empty(); + + private Optional> columns = Optional.empty(); + + private Optional limit = Optional.empty(); + + private Optional offset = Optional.empty(); + + private Optional> uniqueValues = Optional.empty(); + + private Optional> records = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(V1GetRequest other) { + vaultId(other.getVaultId()); + tableName(other.getTableName()); + skyflowIDs(other.getSkyflowIDs()); + columnRedactions(other.getColumnRedactions()); + columns(other.getColumns()); + limit(other.getLimit()); + offset(other.getOffset()); + uniqueValues(other.getUniqueValues()); + records(other.getRecords()); + return this; + } + + /** + *

ID of the vault where data is being fetched

+ */ + @JsonSetter(value = "vaultID", nulls = Nulls.SKIP) + public Builder vaultId(Optional vaultId) { + this.vaultId = vaultId; + return this; + } + + public Builder vaultId(String vaultId) { + this.vaultId = Optional.ofNullable(vaultId); + return this; + } + + /** + *

Name of the table where data is being fetched

+ */ + @JsonSetter(value = "tableName", nulls = Nulls.SKIP) + public Builder tableName(Optional tableName) { + this.tableName = tableName; + return this; + } + + public Builder tableName(String tableName) { + this.tableName = Optional.ofNullable(tableName); + return this; + } + + /** + *

Skyflow ID for the record to be fetched

+ */ + @JsonSetter(value = "skyflowIDs", nulls = Nulls.SKIP) + public Builder skyflowIDs(Optional> skyflowIDs) { + this.skyflowIDs = skyflowIDs; + return this; + } + + public Builder skyflowIDs(List skyflowIDs) { + this.skyflowIDs = Optional.ofNullable(skyflowIDs); + return this; + } + + /** + *

List of columns to be redacted.

+ */ + @JsonSetter(value = "columnRedactions", nulls = Nulls.SKIP) + public Builder columnRedactions(Optional> columnRedactions) { + this.columnRedactions = columnRedactions; + return this; + } + + public Builder columnRedactions(List columnRedactions) { + this.columnRedactions = Optional.ofNullable(columnRedactions); + return this; + } + + /** + *

List of columns to be fetched.

+ */ + @JsonSetter(value = "columns", nulls = Nulls.SKIP) + public Builder columns(Optional> columns) { + this.columns = columns; + return this; + } + + public Builder columns(List columns) { + this.columns = Optional.ofNullable(columns); + return this; + } + + /** + *

Limit for the number of records to be fetched

+ */ + @JsonSetter(value = "limit", nulls = Nulls.SKIP) + public Builder limit(Optional limit) { + this.limit = limit; + return this; + } + + public Builder limit(Integer limit) { + this.limit = Optional.ofNullable(limit); + return this; + } + + /** + *

Offset for the number of records to be fetched

+ */ + @JsonSetter(value = "offset", nulls = Nulls.SKIP) + public Builder offset(Optional offset) { + this.offset = offset; + return this; + } + + public Builder offset(Integer offset) { + this.offset = Optional.ofNullable(offset); + return this; + } + + /** + *

List of unique constraint values to query records by data

+ */ + @JsonSetter(value = "uniqueValues", nulls = Nulls.SKIP) + public Builder uniqueValues(Optional> uniqueValues) { + this.uniqueValues = uniqueValues; + return this; + } + + public Builder uniqueValues(List uniqueValues) { + this.uniqueValues = Optional.ofNullable(uniqueValues); + return this; + } + + /** + *

List of records to be fetched. This field contains tableName and skyflowIDs belonging to the table.

+ */ + @JsonSetter(value = "records", nulls = Nulls.SKIP) + public Builder records(Optional> records) { + this.records = records; + return this; + } + + public Builder records(List records) { + this.records = Optional.ofNullable(records); + return this; + } + + public V1GetRequest build() { + return new V1GetRequest( + vaultId, + tableName, + skyflowIDs, + columnRedactions, + columns, + limit, + offset, + uniqueValues, + records, + additionalProperties); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/resources/flowservice/requests/V1InsertRequest.java b/flowvault/src/main/java/com/skyflow/generated/rest/resources/flowservice/requests/V1InsertRequest.java new file mode 100644 index 00000000..47a58cb2 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/resources/flowservice/requests/V1InsertRequest.java @@ -0,0 +1,181 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.resources.flowservice.requests; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.rest.core.ObjectMappers; +import com.skyflow.generated.rest.types.V1InsertRecordData; +import com.skyflow.generated.rest.types.V1Upsert; + +import java.util.*; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = V1InsertRequest.Builder.class) +public final class V1InsertRequest { + private final Optional vaultId; + + private final Optional tableName; + + private final Optional> records; + + private final Optional upsert; + + private final Map additionalProperties; + + private V1InsertRequest( + Optional vaultId, + Optional tableName, + Optional> records, + Optional upsert, + Map additionalProperties) { + this.vaultId = vaultId; + this.tableName = tableName; + this.records = records; + this.upsert = upsert; + this.additionalProperties = additionalProperties; + } + + /** + * @return ID of the vault where data is being inserted + */ + @JsonProperty("vaultID") + public Optional getVaultId() { + return vaultId; + } + + /** + * @return Name of the table where data is being inserted + */ + @JsonProperty("tableName") + public Optional getTableName() { + return tableName; + } + + /** + * @return List of data row wise that is to be inserted in the vault + */ + @JsonProperty("records") + public Optional> getRecords() { + return records; + } + + @JsonProperty("upsert") + public Optional getUpsert() { + return upsert; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V1InsertRequest && equalTo((V1InsertRequest) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(V1InsertRequest other) { + return vaultId.equals(other.vaultId) + && tableName.equals(other.tableName) + && records.equals(other.records) + && upsert.equals(other.upsert); + } + + @Override + public int hashCode() { + return Objects.hash(this.vaultId, this.tableName, this.records, this.upsert); + } + + @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 vaultId = Optional.empty(); + + private Optional tableName = Optional.empty(); + + private Optional> records = Optional.empty(); + + private Optional upsert = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(V1InsertRequest other) { + vaultId(other.getVaultId()); + tableName(other.getTableName()); + records(other.getRecords()); + upsert(other.getUpsert()); + return this; + } + + /** + *

ID of the vault where data is being inserted

+ */ + @JsonSetter(value = "vaultID", nulls = Nulls.SKIP) + public Builder vaultId(Optional vaultId) { + this.vaultId = vaultId; + return this; + } + + public Builder vaultId(String vaultId) { + this.vaultId = Optional.ofNullable(vaultId); + return this; + } + + /** + *

Name of the table where data is being inserted

+ */ + @JsonSetter(value = "tableName", nulls = Nulls.SKIP) + public Builder tableName(Optional tableName) { + this.tableName = tableName; + return this; + } + + public Builder tableName(String tableName) { + this.tableName = Optional.ofNullable(tableName); + return this; + } + + /** + *

List of data row wise that is to be inserted in the vault

+ */ + @JsonSetter(value = "records", nulls = Nulls.SKIP) + public Builder records(Optional> records) { + this.records = records; + return this; + } + + public Builder records(List records) { + this.records = Optional.ofNullable(records); + return this; + } + + @JsonSetter(value = "upsert", nulls = Nulls.SKIP) + public Builder upsert(Optional upsert) { + this.upsert = upsert; + return this; + } + + public Builder upsert(V1Upsert upsert) { + this.upsert = Optional.ofNullable(upsert); + return this; + } + + public V1InsertRequest build() { + return new V1InsertRequest(vaultId, tableName, records, upsert, additionalProperties); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/resources/flowservice/requests/V1UpdateRequest.java b/flowvault/src/main/java/com/skyflow/generated/rest/resources/flowservice/requests/V1UpdateRequest.java new file mode 100644 index 00000000..6f8b303e --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/resources/flowservice/requests/V1UpdateRequest.java @@ -0,0 +1,181 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.resources.flowservice.requests; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.rest.core.ObjectMappers; +import com.skyflow.generated.rest.types.FlowEnumUpdateType; +import com.skyflow.generated.rest.types.V1UpdateRecordData; + +import java.util.*; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = V1UpdateRequest.Builder.class) +public final class V1UpdateRequest { + private final Optional vaultId; + + private final Optional tableName; + + private final Optional> records; + + private final Optional updateType; + + private final Map additionalProperties; + + private V1UpdateRequest( + Optional vaultId, + Optional tableName, + Optional> records, + Optional updateType, + Map additionalProperties) { + this.vaultId = vaultId; + this.tableName = tableName; + this.records = records; + this.updateType = updateType; + this.additionalProperties = additionalProperties; + } + + /** + * @return ID of the vault where data is being updated + */ + @JsonProperty("vaultID") + public Optional getVaultId() { + return vaultId; + } + + /** + * @return Name of the table where data is being updated + */ + @JsonProperty("tableName") + public Optional getTableName() { + return tableName; + } + + /** + * @return List of data row wise that is to be updated in the vault + */ + @JsonProperty("records") + public Optional> getRecords() { + return records; + } + + @JsonProperty("updateType") + public Optional getUpdateType() { + return updateType; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V1UpdateRequest && equalTo((V1UpdateRequest) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(V1UpdateRequest other) { + return vaultId.equals(other.vaultId) + && tableName.equals(other.tableName) + && records.equals(other.records) + && updateType.equals(other.updateType); + } + + @Override + public int hashCode() { + return Objects.hash(this.vaultId, this.tableName, this.records, this.updateType); + } + + @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 vaultId = Optional.empty(); + + private Optional tableName = Optional.empty(); + + private Optional> records = Optional.empty(); + + private Optional updateType = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(V1UpdateRequest other) { + vaultId(other.getVaultId()); + tableName(other.getTableName()); + records(other.getRecords()); + updateType(other.getUpdateType()); + return this; + } + + /** + *

ID of the vault where data is being updated

+ */ + @JsonSetter(value = "vaultID", nulls = Nulls.SKIP) + public Builder vaultId(Optional vaultId) { + this.vaultId = vaultId; + return this; + } + + public Builder vaultId(String vaultId) { + this.vaultId = Optional.ofNullable(vaultId); + return this; + } + + /** + *

Name of the table where data is being updated

+ */ + @JsonSetter(value = "tableName", nulls = Nulls.SKIP) + public Builder tableName(Optional tableName) { + this.tableName = tableName; + return this; + } + + public Builder tableName(String tableName) { + this.tableName = Optional.ofNullable(tableName); + return this; + } + + /** + *

List of data row wise that is to be updated in the vault

+ */ + @JsonSetter(value = "records", nulls = Nulls.SKIP) + public Builder records(Optional> records) { + this.records = records; + return this; + } + + public Builder records(List records) { + this.records = Optional.ofNullable(records); + return this; + } + + @JsonSetter(value = "updateType", nulls = Nulls.SKIP) + public Builder updateType(Optional updateType) { + this.updateType = updateType; + return this; + } + + public Builder updateType(FlowEnumUpdateType updateType) { + this.updateType = Optional.ofNullable(updateType); + return this; + } + + public V1UpdateRequest build() { + return new V1UpdateRequest(vaultId, tableName, records, updateType, additionalProperties); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/resources/records/AsyncRawRecordsClient.java b/flowvault/src/main/java/com/skyflow/generated/rest/resources/records/AsyncRawRecordsClient.java new file mode 100644 index 00000000..c91ad763 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/resources/records/AsyncRawRecordsClient.java @@ -0,0 +1,96 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.resources.records; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.skyflow.generated.rest.core.*; +import com.skyflow.generated.rest.resources.records.requests.V1ExecuteQueryRequest; +import com.skyflow.generated.rest.types.V1ExecuteQueryResponse; +import okhttp3.*; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.util.concurrent.CompletableFuture; + +public class AsyncRawRecordsClient { + protected final ClientOptions clientOptions; + + public AsyncRawRecordsClient(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + } + + /** + * Executes a query on the specified vault. + */ + public CompletableFuture> flowServiceExecuteQuery() { + return flowServiceExecuteQuery(V1ExecuteQueryRequest.builder().build()); + } + + /** + * Executes a query on the specified vault. + */ + public CompletableFuture> flowServiceExecuteQuery( + V1ExecuteQueryRequest request) { + return flowServiceExecuteQuery(request, null); + } + + /** + * Executes a query on the specified vault. + */ + public CompletableFuture> flowServiceExecuteQuery( + V1ExecuteQueryRequest request, RequestOptions requestOptions) { + HttpUrl httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("v2/query") + .build(); + RequestBody body; + try { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); + } catch (JsonProcessingException e) { + throw new ApiClientException("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") + .addHeader("Accept", "application/json") + .build(); + OkHttpClient client = clientOptions.httpClient(); + if (requestOptions != null && requestOptions.getTimeout().isPresent()) { + client = clientOptions.httpClientWithTimeout(requestOptions); + } + CompletableFuture> future = new CompletableFuture<>(); + client.newCall(okhttpRequest).enqueue(new Callback() { + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { + try (ResponseBody responseBody = response.body()) { + if (response.isSuccessful()) { + future.complete(new ApiClientHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue( + responseBody.string(), V1ExecuteQueryResponse.class), + response)); + return; + } + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + future.completeExceptionally(new ApiClientApiException( + "Error with status code " + response.code(), + response.code(), + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class), + response)); + return; + } catch (IOException e) { + future.completeExceptionally(new ApiClientException("Network error executing HTTP request", e)); + } + } + + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + future.completeExceptionally(new ApiClientException("Network error executing HTTP request", e)); + } + }); + return future; + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/resources/records/AsyncRecordsClient.java b/flowvault/src/main/java/com/skyflow/generated/rest/resources/records/AsyncRecordsClient.java new file mode 100644 index 00000000..9af54869 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/resources/records/AsyncRecordsClient.java @@ -0,0 +1,51 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.resources.records; + +import com.skyflow.generated.rest.core.ClientOptions; +import com.skyflow.generated.rest.core.RequestOptions; +import com.skyflow.generated.rest.resources.records.requests.V1ExecuteQueryRequest; +import com.skyflow.generated.rest.types.V1ExecuteQueryResponse; + +import java.util.concurrent.CompletableFuture; + +public class AsyncRecordsClient { + protected final ClientOptions clientOptions; + + private final AsyncRawRecordsClient rawClient; + + public AsyncRecordsClient(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + this.rawClient = new AsyncRawRecordsClient(clientOptions); + } + + /** + * Get responses with HTTP metadata like headers + */ + public AsyncRawRecordsClient withRawResponse() { + return this.rawClient; + } + + /** + * Executes a query on the specified vault. + */ + public CompletableFuture flowServiceExecuteQuery() { + return this.rawClient.flowServiceExecuteQuery().thenApply(response -> response.body()); + } + + /** + * Executes a query on the specified vault. + */ + public CompletableFuture flowServiceExecuteQuery(V1ExecuteQueryRequest request) { + return this.rawClient.flowServiceExecuteQuery(request).thenApply(response -> response.body()); + } + + /** + * Executes a query on the specified vault. + */ + public CompletableFuture flowServiceExecuteQuery( + V1ExecuteQueryRequest request, RequestOptions requestOptions) { + return this.rawClient.flowServiceExecuteQuery(request, requestOptions).thenApply(response -> response.body()); + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/resources/records/RawRecordsClient.java b/flowvault/src/main/java/com/skyflow/generated/rest/resources/records/RawRecordsClient.java new file mode 100644 index 00000000..cf9a1db1 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/resources/records/RawRecordsClient.java @@ -0,0 +1,79 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.resources.records; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.skyflow.generated.rest.core.*; +import com.skyflow.generated.rest.resources.records.requests.V1ExecuteQueryRequest; +import com.skyflow.generated.rest.types.V1ExecuteQueryResponse; +import okhttp3.*; + +import java.io.IOException; + +public class RawRecordsClient { + protected final ClientOptions clientOptions; + + public RawRecordsClient(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + } + + /** + * Executes a query on the specified vault. + */ + public ApiClientHttpResponse flowServiceExecuteQuery() { + return flowServiceExecuteQuery(V1ExecuteQueryRequest.builder().build()); + } + + /** + * Executes a query on the specified vault. + */ + public ApiClientHttpResponse flowServiceExecuteQuery(V1ExecuteQueryRequest request) { + return flowServiceExecuteQuery(request, null); + } + + /** + * Executes a query on the specified vault. + */ + public ApiClientHttpResponse flowServiceExecuteQuery( + V1ExecuteQueryRequest request, RequestOptions requestOptions) { + HttpUrl httpUrl = HttpUrl.parse(this.clientOptions.environment().getUrl()) + .newBuilder() + .addPathSegments("v2/query") + .build(); + RequestBody body; + try { + body = RequestBody.create( + ObjectMappers.JSON_MAPPER.writeValueAsBytes(request), MediaTypes.APPLICATION_JSON); + } catch (JsonProcessingException e) { + throw new ApiClientException("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") + .addHeader("Accept", "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 new ApiClientHttpResponse<>( + ObjectMappers.JSON_MAPPER.readValue(responseBody.string(), V1ExecuteQueryResponse.class), + response); + } + String responseBodyString = responseBody != null ? responseBody.string() : "{}"; + throw new ApiClientApiException( + "Error with status code " + response.code(), + response.code(), + ObjectMappers.JSON_MAPPER.readValue(responseBodyString, Object.class), + response); + } catch (IOException e) { + throw new ApiClientException("Network error executing HTTP request", e); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/resources/records/RecordsClient.java b/flowvault/src/main/java/com/skyflow/generated/rest/resources/records/RecordsClient.java new file mode 100644 index 00000000..332c0e1b --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/resources/records/RecordsClient.java @@ -0,0 +1,49 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.resources.records; + +import com.skyflow.generated.rest.core.ClientOptions; +import com.skyflow.generated.rest.core.RequestOptions; +import com.skyflow.generated.rest.resources.records.requests.V1ExecuteQueryRequest; +import com.skyflow.generated.rest.types.V1ExecuteQueryResponse; + +public class RecordsClient { + protected final ClientOptions clientOptions; + + private final RawRecordsClient rawClient; + + public RecordsClient(ClientOptions clientOptions) { + this.clientOptions = clientOptions; + this.rawClient = new RawRecordsClient(clientOptions); + } + + /** + * Get responses with HTTP metadata like headers + */ + public RawRecordsClient withRawResponse() { + return this.rawClient; + } + + /** + * Executes a query on the specified vault. + */ + public V1ExecuteQueryResponse flowServiceExecuteQuery() { + return this.rawClient.flowServiceExecuteQuery().body(); + } + + /** + * Executes a query on the specified vault. + */ + public V1ExecuteQueryResponse flowServiceExecuteQuery(V1ExecuteQueryRequest request) { + return this.rawClient.flowServiceExecuteQuery(request).body(); + } + + /** + * Executes a query on the specified vault. + */ + public V1ExecuteQueryResponse flowServiceExecuteQuery( + V1ExecuteQueryRequest request, RequestOptions requestOptions) { + return this.rawClient.flowServiceExecuteQuery(request, requestOptions).body(); + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/resources/records/requests/V1ExecuteQueryRequest.java b/flowvault/src/main/java/com/skyflow/generated/rest/resources/records/requests/V1ExecuteQueryRequest.java new file mode 100644 index 00000000..2857faca --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/resources/records/requests/V1ExecuteQueryRequest.java @@ -0,0 +1,125 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.resources.records.requests; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.rest.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 = V1ExecuteQueryRequest.Builder.class) +public final class V1ExecuteQueryRequest { + private final Optional vaultId; + + private final Optional query; + + private final Map additionalProperties; + + private V1ExecuteQueryRequest( + Optional vaultId, Optional query, Map additionalProperties) { + this.vaultId = vaultId; + this.query = query; + this.additionalProperties = additionalProperties; + } + + /** + * @return ID of the vault where data is being inserted + */ + @JsonProperty("vaultID") + public Optional getVaultId() { + return vaultId; + } + + /** + * @return Query to execute. + */ + @JsonProperty("query") + public Optional getQuery() { + return query; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V1ExecuteQueryRequest && equalTo((V1ExecuteQueryRequest) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(V1ExecuteQueryRequest other) { + return vaultId.equals(other.vaultId) && query.equals(other.query); + } + + @Override + public int hashCode() { + return Objects.hash(this.vaultId, this.query); + } + + @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 vaultId = Optional.empty(); + + private Optional query = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(V1ExecuteQueryRequest other) { + vaultId(other.getVaultId()); + query(other.getQuery()); + return this; + } + + /** + *

ID of the vault where data is being inserted

+ */ + @JsonSetter(value = "vaultID", nulls = Nulls.SKIP) + public Builder vaultId(Optional vaultId) { + this.vaultId = vaultId; + return this; + } + + public Builder vaultId(String vaultId) { + this.vaultId = Optional.ofNullable(vaultId); + return this; + } + + /** + *

Query to execute.

+ */ + @JsonSetter(value = "query", nulls = Nulls.SKIP) + public Builder query(Optional query) { + this.query = query; + return this; + } + + public Builder query(String query) { + this.query = Optional.ofNullable(query); + return this; + } + + public V1ExecuteQueryRequest build() { + return new V1ExecuteQueryRequest(vaultId, query, additionalProperties); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/types/FlowEnumUpdateType.java b/flowvault/src/main/java/com/skyflow/generated/rest/types/FlowEnumUpdateType.java new file mode 100644 index 00000000..d3c1cea0 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/types/FlowEnumUpdateType.java @@ -0,0 +1,24 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.types; + +import com.fasterxml.jackson.annotation.JsonValue; + +public enum FlowEnumUpdateType { + UPDATE("UPDATE"), + + REPLACE("REPLACE"); + + private final String value; + + FlowEnumUpdateType(String value) { + this.value = value; + } + + @JsonValue + @Override + public String toString() { + return this.value; + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/types/FlowTokenizeResponseObjectToken.java b/flowvault/src/main/java/com/skyflow/generated/rest/types/FlowTokenizeResponseObjectToken.java new file mode 100644 index 00000000..0edfd9a8 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/types/FlowTokenizeResponseObjectToken.java @@ -0,0 +1,188 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.types; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.rest.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 = FlowTokenizeResponseObjectToken.Builder.class) +public final class FlowTokenizeResponseObjectToken { + private final Optional tokenGroupName; + + private final Optional token; + + private final Optional error; + + private final Optional httpCode; + + private final Map additionalProperties; + + private FlowTokenizeResponseObjectToken( + Optional tokenGroupName, + Optional token, + Optional error, + Optional httpCode, + Map additionalProperties) { + this.tokenGroupName = tokenGroupName; + this.token = token; + this.error = error; + this.httpCode = httpCode; + this.additionalProperties = additionalProperties; + } + + /** + * @return Token group Name + */ + @JsonProperty("tokenGroupName") + public Optional getTokenGroupName() { + return tokenGroupName; + } + + /** + * @return Token value + */ + @JsonProperty("token") + public Optional getToken() { + return token; + } + + /** + * @return Error if tokenization failed + */ + @JsonProperty("error") + public Optional getError() { + return error; + } + + /** + * @return HTTP status code of the response + */ + @JsonProperty("httpCode") + public Optional getHttpCode() { + return httpCode; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof FlowTokenizeResponseObjectToken && equalTo((FlowTokenizeResponseObjectToken) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(FlowTokenizeResponseObjectToken other) { + return tokenGroupName.equals(other.tokenGroupName) + && token.equals(other.token) + && error.equals(other.error) + && httpCode.equals(other.httpCode); + } + + @Override + public int hashCode() { + return Objects.hash(this.tokenGroupName, this.token, this.error, this.httpCode); + } + + @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 tokenGroupName = Optional.empty(); + + private Optional token = Optional.empty(); + + private Optional error = Optional.empty(); + + private Optional httpCode = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(FlowTokenizeResponseObjectToken other) { + tokenGroupName(other.getTokenGroupName()); + token(other.getToken()); + error(other.getError()); + httpCode(other.getHttpCode()); + return this; + } + + /** + *

Token group Name

+ */ + @JsonSetter(value = "tokenGroupName", nulls = Nulls.SKIP) + public Builder tokenGroupName(Optional tokenGroupName) { + this.tokenGroupName = tokenGroupName; + return this; + } + + public Builder tokenGroupName(String tokenGroupName) { + this.tokenGroupName = Optional.ofNullable(tokenGroupName); + return this; + } + + /** + *

Token value

+ */ + @JsonSetter(value = "token", nulls = Nulls.SKIP) + public Builder token(Optional token) { + this.token = token; + return this; + } + + public Builder token(String token) { + this.token = Optional.ofNullable(token); + return this; + } + + /** + *

Error if tokenization failed

+ */ + @JsonSetter(value = "error", nulls = Nulls.SKIP) + public Builder error(Optional error) { + this.error = error; + return this; + } + + public Builder error(String error) { + this.error = Optional.ofNullable(error); + return this; + } + + /** + *

HTTP status code of the response

+ */ + @JsonSetter(value = "httpCode", nulls = Nulls.SKIP) + public Builder httpCode(Optional httpCode) { + this.httpCode = httpCode; + return this; + } + + public Builder httpCode(Integer httpCode) { + this.httpCode = Optional.ofNullable(httpCode); + return this; + } + + public FlowTokenizeResponseObjectToken build() { + return new FlowTokenizeResponseObjectToken(tokenGroupName, token, error, httpCode, additionalProperties); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/types/GoogleprotobufAny.java b/flowvault/src/main/java/com/skyflow/generated/rest/types/GoogleprotobufAny.java new file mode 100644 index 00000000..06205978 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/types/GoogleprotobufAny.java @@ -0,0 +1,146 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.types; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.rest.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 = GoogleprotobufAny.Builder.class) +public final class GoogleprotobufAny { + private final Optional type; + + private final Map additionalProperties; + + private GoogleprotobufAny(Optional type, Map additionalProperties) { + this.type = type; + this.additionalProperties = additionalProperties; + } + + /** + * @return A URL/resource name that uniquely identifies the type of the serialized + * protocol buffer message. This string must contain at least + * one "/" character. The last segment of the URL's path must represent + * the fully qualified name of the type (as in + * path/google.protobuf.Duration). The name should be in a canonical form + * (e.g., leading "." is not accepted). + *

In practice, teams usually precompile into the binary all types that they + * expect it to use in the context of Any. However, for URLs which use the + * scheme http, https, or no scheme, one can optionally set up a type + * server that maps type URLs to message definitions as follows:

+ *
    + *
  • If no scheme is provided, https is assumed.
  • + *
  • An HTTP GET on the URL must yield a [google.protobuf.Type][] + * value in binary format, or produce an error.
  • + *
  • Applications are allowed to cache lookup results based on the + * URL, or have them precompiled into a binary to avoid any + * lookup. Therefore, binary compatibility needs to be preserved + * on changes to types. (Use versioned type names to manage + * breaking changes.)
  • + *
+ *

Note: this functionality is not currently available in the official + * protobuf release, and it is not used for type URLs beginning with + * type.googleapis.com. As of May 2023, there are no widely used type server + * implementations and no plans to implement one.

+ *

Schemes other than http, https (or the empty scheme) might be + * used with implementation specific semantics.

+ */ + @JsonProperty("@type") + public Optional getType() { + return type; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof GoogleprotobufAny && equalTo((GoogleprotobufAny) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(GoogleprotobufAny other) { + return type.equals(other.type); + } + + @Override + public int hashCode() { + return Objects.hash(this.type); + } + + @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 type = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(GoogleprotobufAny other) { + type(other.getType()); + return this; + } + + /** + *

A URL/resource name that uniquely identifies the type of the serialized + * protocol buffer message. This string must contain at least + * one "/" character. The last segment of the URL's path must represent + * the fully qualified name of the type (as in + * path/google.protobuf.Duration). The name should be in a canonical form + * (e.g., leading "." is not accepted).

+ *

In practice, teams usually precompile into the binary all types that they + * expect it to use in the context of Any. However, for URLs which use the + * scheme http, https, or no scheme, one can optionally set up a type + * server that maps type URLs to message definitions as follows:

+ *
    + *
  • If no scheme is provided, https is assumed.
  • + *
  • An HTTP GET on the URL must yield a [google.protobuf.Type][] + * value in binary format, or produce an error.
  • + *
  • Applications are allowed to cache lookup results based on the + * URL, or have them precompiled into a binary to avoid any + * lookup. Therefore, binary compatibility needs to be preserved + * on changes to types. (Use versioned type names to manage + * breaking changes.)
  • + *
+ *

Note: this functionality is not currently available in the official + * protobuf release, and it is not used for type URLs beginning with + * type.googleapis.com. As of May 2023, there are no widely used type server + * implementations and no plans to implement one.

+ *

Schemes other than http, https (or the empty scheme) might be + * used with implementation specific semantics.

+ */ + @JsonSetter(value = "@type", nulls = Nulls.SKIP) + public Builder type(Optional type) { + this.type = type; + return this; + } + + public Builder type(String type) { + this.type = Optional.ofNullable(type); + return this; + } + + public GoogleprotobufAny build() { + return new GoogleprotobufAny(type, additionalProperties); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/types/RpcStatus.java b/flowvault/src/main/java/com/skyflow/generated/rest/types/RpcStatus.java new file mode 100644 index 00000000..347ce5c4 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/types/RpcStatus.java @@ -0,0 +1,135 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.types; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.rest.core.ObjectMappers; + +import java.util.*; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = RpcStatus.Builder.class) +public final class RpcStatus { + private final Optional code; + + private final Optional message; + + private final Optional> details; + + private final Map additionalProperties; + + private RpcStatus( + Optional code, + Optional message, + Optional> details, + Map additionalProperties) { + this.code = code; + this.message = message; + this.details = details; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("code") + public Optional getCode() { + return code; + } + + @JsonProperty("message") + public Optional getMessage() { + return message; + } + + @JsonProperty("details") + public Optional> getDetails() { + return details; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof RpcStatus && equalTo((RpcStatus) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(RpcStatus other) { + return code.equals(other.code) && message.equals(other.message) && details.equals(other.details); + } + + @Override + public int hashCode() { + return Objects.hash(this.code, this.message, this.details); + } + + @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 code = Optional.empty(); + + private Optional message = Optional.empty(); + + private Optional> details = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(RpcStatus other) { + code(other.getCode()); + message(other.getMessage()); + details(other.getDetails()); + return this; + } + + @JsonSetter(value = "code", nulls = Nulls.SKIP) + public Builder code(Optional code) { + this.code = code; + return this; + } + + public Builder code(Integer code) { + this.code = Optional.ofNullable(code); + return this; + } + + @JsonSetter(value = "message", nulls = Nulls.SKIP) + public Builder message(Optional message) { + this.message = message; + return this; + } + + public Builder message(String message) { + this.message = Optional.ofNullable(message); + return this; + } + + @JsonSetter(value = "details", nulls = Nulls.SKIP) + public Builder details(Optional> details) { + this.details = details; + return this; + } + + public Builder details(List details) { + this.details = Optional.ofNullable(details); + return this; + } + + public RpcStatus build() { + return new RpcStatus(code, message, details, additionalProperties); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/types/V1ColumnRedactions.java b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1ColumnRedactions.java new file mode 100644 index 00000000..b467a8c0 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1ColumnRedactions.java @@ -0,0 +1,125 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.types; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.rest.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 = V1ColumnRedactions.Builder.class) +public final class V1ColumnRedactions { + private final Optional columnName; + + private final Optional redaction; + + private final Map additionalProperties; + + private V1ColumnRedactions( + Optional columnName, Optional redaction, Map additionalProperties) { + this.columnName = columnName; + this.redaction = redaction; + this.additionalProperties = additionalProperties; + } + + /** + * @return Name of the column to be redacted + */ + @JsonProperty("columnName") + public Optional getColumnName() { + return columnName; + } + + /** + * @return Name of the redaction. Eg: plain_text, redacted, mask1 + */ + @JsonProperty("redaction") + public Optional getRedaction() { + return redaction; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V1ColumnRedactions && equalTo((V1ColumnRedactions) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(V1ColumnRedactions other) { + return columnName.equals(other.columnName) && redaction.equals(other.redaction); + } + + @Override + public int hashCode() { + return Objects.hash(this.columnName, this.redaction); + } + + @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 columnName = Optional.empty(); + + private Optional redaction = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(V1ColumnRedactions other) { + columnName(other.getColumnName()); + redaction(other.getRedaction()); + return this; + } + + /** + *

Name of the column to be redacted

+ */ + @JsonSetter(value = "columnName", nulls = Nulls.SKIP) + public Builder columnName(Optional columnName) { + this.columnName = columnName; + return this; + } + + public Builder columnName(String columnName) { + this.columnName = Optional.ofNullable(columnName); + return this; + } + + /** + *

Name of the redaction. Eg: plain_text, redacted, mask1

+ */ + @JsonSetter(value = "redaction", nulls = Nulls.SKIP) + public Builder redaction(Optional redaction) { + this.redaction = redaction; + return this; + } + + public Builder redaction(String redaction) { + this.redaction = Optional.ofNullable(redaction); + return this; + } + + public V1ColumnRedactions build() { + return new V1ColumnRedactions(columnName, redaction, additionalProperties); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/types/V1DeleteResponse.java b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1DeleteResponse.java new file mode 100644 index 00000000..24bd865d --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1DeleteResponse.java @@ -0,0 +1,93 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.types; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.rest.core.ObjectMappers; + +import java.util.*; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = V1DeleteResponse.Builder.class) +public final class V1DeleteResponse { + private final Optional> records; + + private final Map additionalProperties; + + private V1DeleteResponse(Optional> records, Map additionalProperties) { + this.records = records; + this.additionalProperties = additionalProperties; + } + + /** + * @return List of deleted records with skyflow ID and any partial errors. + */ + @JsonProperty("records") + public Optional> getRecords() { + return records; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V1DeleteResponse && equalTo((V1DeleteResponse) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(V1DeleteResponse other) { + return records.equals(other.records); + } + + @Override + public int hashCode() { + return Objects.hash(this.records); + } + + @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> records = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(V1DeleteResponse other) { + records(other.getRecords()); + return this; + } + + /** + *

List of deleted records with skyflow ID and any partial errors.

+ */ + @JsonSetter(value = "records", nulls = Nulls.SKIP) + public Builder records(Optional> records) { + this.records = records; + return this; + } + + public Builder records(List records) { + this.records = Optional.ofNullable(records); + return this; + } + + public V1DeleteResponse build() { + return new V1DeleteResponse(records, additionalProperties); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/types/V1DeleteResponseObject.java b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1DeleteResponseObject.java new file mode 100644 index 00000000..087ce4c7 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1DeleteResponseObject.java @@ -0,0 +1,156 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.types; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.rest.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 = V1DeleteResponseObject.Builder.class) +public final class V1DeleteResponseObject { + private final Optional skyflowId; + + private final Optional error; + + private final Optional httpCode; + + private final Map additionalProperties; + + private V1DeleteResponseObject( + Optional skyflowId, + Optional error, + Optional httpCode, + Map additionalProperties) { + this.skyflowId = skyflowId; + this.error = error; + this.httpCode = httpCode; + this.additionalProperties = additionalProperties; + } + + /** + * @return Skyflow ID for the deleted record + */ + @JsonProperty("skyflowID") + public Optional getSkyflowId() { + return skyflowId; + } + + /** + * @return Partial Error message if any + */ + @JsonProperty("error") + public Optional getError() { + return error; + } + + /** + * @return HTTP status code of the response + */ + @JsonProperty("httpCode") + public Optional getHttpCode() { + return httpCode; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V1DeleteResponseObject && equalTo((V1DeleteResponseObject) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(V1DeleteResponseObject other) { + return skyflowId.equals(other.skyflowId) && error.equals(other.error) && httpCode.equals(other.httpCode); + } + + @Override + public int hashCode() { + return Objects.hash(this.skyflowId, this.error, this.httpCode); + } + + @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 skyflowId = Optional.empty(); + + private Optional error = Optional.empty(); + + private Optional httpCode = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(V1DeleteResponseObject other) { + skyflowId(other.getSkyflowId()); + error(other.getError()); + httpCode(other.getHttpCode()); + return this; + } + + /** + *

Skyflow ID for the deleted record

+ */ + @JsonSetter(value = "skyflowID", nulls = Nulls.SKIP) + public Builder skyflowId(Optional skyflowId) { + this.skyflowId = skyflowId; + return this; + } + + public Builder skyflowId(String skyflowId) { + this.skyflowId = Optional.ofNullable(skyflowId); + return this; + } + + /** + *

Partial Error message if any

+ */ + @JsonSetter(value = "error", nulls = Nulls.SKIP) + public Builder error(Optional error) { + this.error = error; + return this; + } + + public Builder error(String error) { + this.error = Optional.ofNullable(error); + return this; + } + + /** + *

HTTP status code of the response

+ */ + @JsonSetter(value = "httpCode", nulls = Nulls.SKIP) + public Builder httpCode(Optional httpCode) { + this.httpCode = httpCode; + return this; + } + + public Builder httpCode(Integer httpCode) { + this.httpCode = Optional.ofNullable(httpCode); + return this; + } + + public V1DeleteResponseObject build() { + return new V1DeleteResponseObject(skyflowId, error, httpCode, additionalProperties); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/types/V1DeleteTokenResponseObject.java b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1DeleteTokenResponseObject.java new file mode 100644 index 00000000..f2e1dacd --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1DeleteTokenResponseObject.java @@ -0,0 +1,156 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.types; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.rest.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 = V1DeleteTokenResponseObject.Builder.class) +public final class V1DeleteTokenResponseObject { + private final Optional value; + + private final Optional error; + + private final Optional httpCode; + + private final Map additionalProperties; + + private V1DeleteTokenResponseObject( + Optional value, + Optional error, + Optional httpCode, + Map additionalProperties) { + this.value = value; + this.error = error; + this.httpCode = httpCode; + this.additionalProperties = additionalProperties; + } + + /** + * @return Token value + */ + @JsonProperty("value") + public Optional getValue() { + return value; + } + + /** + * @return Error if deletion failed + */ + @JsonProperty("error") + public Optional getError() { + return error; + } + + /** + * @return HTTP status code of the response + */ + @JsonProperty("httpCode") + public Optional getHttpCode() { + return httpCode; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V1DeleteTokenResponseObject && equalTo((V1DeleteTokenResponseObject) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(V1DeleteTokenResponseObject other) { + return value.equals(other.value) && error.equals(other.error) && httpCode.equals(other.httpCode); + } + + @Override + public int hashCode() { + return Objects.hash(this.value, this.error, this.httpCode); + } + + @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 value = Optional.empty(); + + private Optional error = Optional.empty(); + + private Optional httpCode = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(V1DeleteTokenResponseObject other) { + value(other.getValue()); + error(other.getError()); + httpCode(other.getHttpCode()); + return this; + } + + /** + *

Token value

+ */ + @JsonSetter(value = "value", nulls = Nulls.SKIP) + public Builder value(Optional value) { + this.value = value; + return this; + } + + public Builder value(String value) { + this.value = Optional.ofNullable(value); + return this; + } + + /** + *

Error if deletion failed

+ */ + @JsonSetter(value = "error", nulls = Nulls.SKIP) + public Builder error(Optional error) { + this.error = error; + return this; + } + + public Builder error(String error) { + this.error = Optional.ofNullable(error); + return this; + } + + /** + *

HTTP status code of the response

+ */ + @JsonSetter(value = "httpCode", nulls = Nulls.SKIP) + public Builder httpCode(Optional httpCode) { + this.httpCode = httpCode; + return this; + } + + public Builder httpCode(Integer httpCode) { + this.httpCode = Optional.ofNullable(httpCode); + return this; + } + + public V1DeleteTokenResponseObject build() { + return new V1DeleteTokenResponseObject(value, error, httpCode, additionalProperties); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/types/V1ExecuteQueryRecordResponse.java b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1ExecuteQueryRecordResponse.java new file mode 100644 index 00000000..9de3418b --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1ExecuteQueryRecordResponse.java @@ -0,0 +1,96 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.types; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.rest.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 = V1ExecuteQueryRecordResponse.Builder.class) +public final class V1ExecuteQueryRecordResponse { + private final Optional> data; + + private final Map additionalProperties; + + private V1ExecuteQueryRecordResponse(Optional> data, Map additionalProperties) { + this.data = data; + this.additionalProperties = additionalProperties; + } + + /** + * @return Fields and values for the record. For example, {'field_1':'value_1', 'field_2':'value_2'}. + */ + @JsonProperty("data") + public Optional> getData() { + return data; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V1ExecuteQueryRecordResponse && equalTo((V1ExecuteQueryRecordResponse) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(V1ExecuteQueryRecordResponse other) { + return data.equals(other.data); + } + + @Override + public int hashCode() { + return Objects.hash(this.data); + } + + @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> data = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(V1ExecuteQueryRecordResponse other) { + data(other.getData()); + return this; + } + + /** + *

Fields and values for the record. For example, {'field_1':'value_1', 'field_2':'value_2'}.

+ */ + @JsonSetter(value = "data", nulls = Nulls.SKIP) + public Builder data(Optional> data) { + this.data = data; + return this; + } + + public Builder data(Map data) { + this.data = Optional.ofNullable(data); + return this; + } + + public V1ExecuteQueryRecordResponse build() { + return new V1ExecuteQueryRecordResponse(data, additionalProperties); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/types/V1ExecuteQueryResponse.java b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1ExecuteQueryResponse.java new file mode 100644 index 00000000..f80b54e0 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1ExecuteQueryResponse.java @@ -0,0 +1,118 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.types; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.rest.core.ObjectMappers; + +import java.util.*; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = V1ExecuteQueryResponse.Builder.class) +public final class V1ExecuteQueryResponse { + private final Optional> records; + + private final Optional metadata; + + private final Map additionalProperties; + + private V1ExecuteQueryResponse( + Optional> records, + Optional metadata, + Map additionalProperties) { + this.records = records; + this.metadata = metadata; + this.additionalProperties = additionalProperties; + } + + /** + * @return Records corresponding to the specified query. + */ + @JsonProperty("records") + public Optional> getRecords() { + return records; + } + + @JsonProperty("metadata") + public Optional getMetadata() { + return metadata; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V1ExecuteQueryResponse && equalTo((V1ExecuteQueryResponse) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(V1ExecuteQueryResponse other) { + return records.equals(other.records) && metadata.equals(other.metadata); + } + + @Override + public int hashCode() { + return Objects.hash(this.records, this.metadata); + } + + @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> records = Optional.empty(); + + private Optional metadata = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(V1ExecuteQueryResponse other) { + records(other.getRecords()); + metadata(other.getMetadata()); + return this; + } + + /** + *

Records corresponding to the specified query.

+ */ + @JsonSetter(value = "records", nulls = Nulls.SKIP) + public Builder records(Optional> records) { + this.records = records; + return this; + } + + public Builder records(List records) { + this.records = Optional.ofNullable(records); + return this; + } + + @JsonSetter(value = "metadata", nulls = Nulls.SKIP) + public Builder metadata(Optional metadata) { + this.metadata = metadata; + return this; + } + + public Builder metadata(V1ExecuteQueryResponseMetadata metadata) { + this.metadata = Optional.ofNullable(metadata); + return this; + } + + public V1ExecuteQueryResponse build() { + return new V1ExecuteQueryResponse(records, metadata, additionalProperties); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/types/V1ExecuteQueryResponseMetadata.java b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1ExecuteQueryResponseMetadata.java new file mode 100644 index 00000000..c23da3bd --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1ExecuteQueryResponseMetadata.java @@ -0,0 +1,93 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.types; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.rest.core.ObjectMappers; + +import java.util.*; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = V1ExecuteQueryResponseMetadata.Builder.class) +public final class V1ExecuteQueryResponseMetadata { + private final Optional> columns; + + private final Map additionalProperties; + + private V1ExecuteQueryResponseMetadata(Optional> columns, Map additionalProperties) { + this.columns = columns; + this.additionalProperties = additionalProperties; + } + + /** + * @return Return columns for the query + */ + @JsonProperty("columns") + public Optional> getColumns() { + return columns; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V1ExecuteQueryResponseMetadata && equalTo((V1ExecuteQueryResponseMetadata) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(V1ExecuteQueryResponseMetadata other) { + return columns.equals(other.columns); + } + + @Override + public int hashCode() { + return Objects.hash(this.columns); + } + + @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> columns = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(V1ExecuteQueryResponseMetadata other) { + columns(other.getColumns()); + return this; + } + + /** + *

Return columns for the query

+ */ + @JsonSetter(value = "columns", nulls = Nulls.SKIP) + public Builder columns(Optional> columns) { + this.columns = columns; + return this; + } + + public Builder columns(List columns) { + this.columns = Optional.ofNullable(columns); + return this; + } + + public V1ExecuteQueryResponseMetadata build() { + return new V1ExecuteQueryResponseMetadata(columns, additionalProperties); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/types/V1FlowDeleteTokenResponse.java b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1FlowDeleteTokenResponse.java new file mode 100644 index 00000000..34ca245c --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1FlowDeleteTokenResponse.java @@ -0,0 +1,94 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.types; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.rest.core.ObjectMappers; + +import java.util.*; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = V1FlowDeleteTokenResponse.Builder.class) +public final class V1FlowDeleteTokenResponse { + private final Optional> tokens; + + private final Map additionalProperties; + + private V1FlowDeleteTokenResponse( + Optional> tokens, Map additionalProperties) { + this.tokens = tokens; + this.additionalProperties = additionalProperties; + } + + /** + * @return Tokens data for Delete + */ + @JsonProperty("tokens") + public Optional> getTokens() { + return tokens; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V1FlowDeleteTokenResponse && equalTo((V1FlowDeleteTokenResponse) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(V1FlowDeleteTokenResponse other) { + return tokens.equals(other.tokens); + } + + @Override + public int hashCode() { + return Objects.hash(this.tokens); + } + + @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> tokens = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(V1FlowDeleteTokenResponse other) { + tokens(other.getTokens()); + return this; + } + + /** + *

Tokens data for Delete

+ */ + @JsonSetter(value = "tokens", nulls = Nulls.SKIP) + public Builder tokens(Optional> tokens) { + this.tokens = tokens; + return this; + } + + public Builder tokens(List tokens) { + this.tokens = Optional.ofNullable(tokens); + return this; + } + + public V1FlowDeleteTokenResponse build() { + return new V1FlowDeleteTokenResponse(tokens, additionalProperties); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/types/V1FlowDetokenizeResponse.java b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1FlowDetokenizeResponse.java new file mode 100644 index 00000000..defe1dad --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1FlowDetokenizeResponse.java @@ -0,0 +1,94 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.types; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.rest.core.ObjectMappers; + +import java.util.*; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = V1FlowDetokenizeResponse.Builder.class) +public final class V1FlowDetokenizeResponse { + private final Optional> response; + + private final Map additionalProperties; + + private V1FlowDetokenizeResponse( + Optional> response, Map additionalProperties) { + this.response = response; + this.additionalProperties = additionalProperties; + } + + /** + * @return Detokenized data + */ + @JsonProperty("response") + public Optional> getResponse() { + return response; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V1FlowDetokenizeResponse && equalTo((V1FlowDetokenizeResponse) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(V1FlowDetokenizeResponse other) { + return response.equals(other.response); + } + + @Override + public int hashCode() { + return Objects.hash(this.response); + } + + @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> response = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(V1FlowDetokenizeResponse other) { + response(other.getResponse()); + return this; + } + + /** + *

Detokenized data

+ */ + @JsonSetter(value = "response", nulls = Nulls.SKIP) + public Builder response(Optional> response) { + this.response = response; + return this; + } + + public Builder response(List response) { + this.response = Optional.ofNullable(response); + return this; + } + + public V1FlowDetokenizeResponse build() { + return new V1FlowDetokenizeResponse(response, additionalProperties); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/types/V1FlowDetokenizeResponseObject.java b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1FlowDetokenizeResponseObject.java new file mode 100644 index 00000000..f0cf909d --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1FlowDetokenizeResponseObject.java @@ -0,0 +1,249 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.types; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.rest.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 = V1FlowDetokenizeResponseObject.Builder.class) +public final class V1FlowDetokenizeResponseObject { + private final Optional token; + + private final Optional value; + + private final Optional tokenGroupName; + + private final Optional error; + + private final Optional httpCode; + + private final Optional> metadata; + + private final Map additionalProperties; + + private V1FlowDetokenizeResponseObject( + Optional token, + Optional value, + Optional tokenGroupName, + Optional error, + Optional httpCode, + Optional> metadata, + Map additionalProperties) { + this.token = token; + this.value = value; + this.tokenGroupName = tokenGroupName; + this.error = error; + this.httpCode = httpCode; + this.metadata = metadata; + this.additionalProperties = additionalProperties; + } + + /** + * @return Token to be detokenized + */ + @JsonProperty("token") + public Optional getToken() { + return token; + } + + /** + * @return Detokenized value for the token + */ + @JsonProperty("value") + public Optional getValue() { + return value; + } + + /** + * @return Token group name + */ + @JsonProperty("tokenGroupName") + public Optional getTokenGroupName() { + return tokenGroupName; + } + + /** + * @return Error if detokenization failed + */ + @JsonProperty("error") + public Optional getError() { + return error; + } + + /** + * @return HTTP status code of the response + */ + @JsonProperty("httpCode") + public Optional getHttpCode() { + return httpCode; + } + + /** + * @return Additional metadata associated with the token, such as tableName or skyflowID + */ + @JsonProperty("metadata") + public Optional> getMetadata() { + return metadata; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V1FlowDetokenizeResponseObject && equalTo((V1FlowDetokenizeResponseObject) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(V1FlowDetokenizeResponseObject other) { + return token.equals(other.token) + && value.equals(other.value) + && tokenGroupName.equals(other.tokenGroupName) + && error.equals(other.error) + && httpCode.equals(other.httpCode) + && metadata.equals(other.metadata); + } + + @Override + public int hashCode() { + return Objects.hash(this.token, this.value, this.tokenGroupName, this.error, this.httpCode, this.metadata); + } + + @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 token = Optional.empty(); + + private Optional value = Optional.empty(); + + private Optional tokenGroupName = Optional.empty(); + + private Optional error = Optional.empty(); + + private Optional httpCode = Optional.empty(); + + private Optional> metadata = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(V1FlowDetokenizeResponseObject other) { + token(other.getToken()); + value(other.getValue()); + tokenGroupName(other.getTokenGroupName()); + error(other.getError()); + httpCode(other.getHttpCode()); + metadata(other.getMetadata()); + return this; + } + + /** + *

Token to be detokenized

+ */ + @JsonSetter(value = "token", nulls = Nulls.SKIP) + public Builder token(Optional token) { + this.token = token; + return this; + } + + public Builder token(String token) { + this.token = Optional.ofNullable(token); + return this; + } + + /** + *

Detokenized value for the token

+ */ + @JsonSetter(value = "value", nulls = Nulls.SKIP) + public Builder value(Optional value) { + this.value = value; + return this; + } + + public Builder value(Object value) { + this.value = Optional.ofNullable(value); + return this; + } + + /** + *

Token group name

+ */ + @JsonSetter(value = "tokenGroupName", nulls = Nulls.SKIP) + public Builder tokenGroupName(Optional tokenGroupName) { + this.tokenGroupName = tokenGroupName; + return this; + } + + public Builder tokenGroupName(String tokenGroupName) { + this.tokenGroupName = Optional.ofNullable(tokenGroupName); + return this; + } + + /** + *

Error if detokenization failed

+ */ + @JsonSetter(value = "error", nulls = Nulls.SKIP) + public Builder error(Optional error) { + this.error = error; + return this; + } + + public Builder error(String error) { + this.error = Optional.ofNullable(error); + return this; + } + + /** + *

HTTP status code of the response

+ */ + @JsonSetter(value = "httpCode", nulls = Nulls.SKIP) + public Builder httpCode(Optional httpCode) { + this.httpCode = httpCode; + return this; + } + + public Builder httpCode(Integer httpCode) { + this.httpCode = Optional.ofNullable(httpCode); + return this; + } + + /** + *

Additional metadata associated with the token, such as tableName or skyflowID

+ */ + @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 V1FlowDetokenizeResponseObject build() { + return new V1FlowDetokenizeResponseObject( + token, value, tokenGroupName, error, httpCode, metadata, additionalProperties); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/types/V1FlowTokenizeRequestObject.java b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1FlowTokenizeRequestObject.java new file mode 100644 index 00000000..adefcaed --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1FlowTokenizeRequestObject.java @@ -0,0 +1,153 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.types; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.rest.core.ObjectMappers; + +import java.util.*; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = V1FlowTokenizeRequestObject.Builder.class) +public final class V1FlowTokenizeRequestObject { + private final Optional value; + + private final Optional> tokenGroupNames; + + private final Optional token; + + private final Map additionalProperties; + + private V1FlowTokenizeRequestObject( + Optional value, + Optional> tokenGroupNames, + Optional token, + Map additionalProperties) { + this.value = value; + this.tokenGroupNames = tokenGroupNames; + this.token = token; + this.additionalProperties = additionalProperties; + } + + /** + * @return Token Value + */ + @JsonProperty("value") + public Optional getValue() { + return value; + } + + /** + * @return List of token group names + */ + @JsonProperty("tokenGroupNames") + public Optional> getTokenGroupNames() { + return tokenGroupNames; + } + + /** + * @return Token for the value, in case of BYOT. + */ + @JsonProperty("token") + public Optional getToken() { + return token; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V1FlowTokenizeRequestObject && equalTo((V1FlowTokenizeRequestObject) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(V1FlowTokenizeRequestObject other) { + return value.equals(other.value) && tokenGroupNames.equals(other.tokenGroupNames) && token.equals(other.token); + } + + @Override + public int hashCode() { + return Objects.hash(this.value, this.tokenGroupNames, this.token); + } + + @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 value = Optional.empty(); + + private Optional> tokenGroupNames = Optional.empty(); + + private Optional token = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(V1FlowTokenizeRequestObject other) { + value(other.getValue()); + tokenGroupNames(other.getTokenGroupNames()); + token(other.getToken()); + return this; + } + + /** + *

Token Value

+ */ + @JsonSetter(value = "value", nulls = Nulls.SKIP) + public Builder value(Optional value) { + this.value = value; + return this; + } + + public Builder value(Object value) { + this.value = Optional.ofNullable(value); + return this; + } + + /** + *

List of token group names

+ */ + @JsonSetter(value = "tokenGroupNames", nulls = Nulls.SKIP) + public Builder tokenGroupNames(Optional> tokenGroupNames) { + this.tokenGroupNames = tokenGroupNames; + return this; + } + + public Builder tokenGroupNames(List tokenGroupNames) { + this.tokenGroupNames = Optional.ofNullable(tokenGroupNames); + return this; + } + + /** + *

Token for the value, in case of BYOT.

+ */ + @JsonSetter(value = "token", nulls = Nulls.SKIP) + public Builder token(Optional token) { + this.token = token; + return this; + } + + public Builder token(Object token) { + this.token = Optional.ofNullable(token); + return this; + } + + public V1FlowTokenizeRequestObject build() { + return new V1FlowTokenizeRequestObject(value, tokenGroupNames, token, additionalProperties); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/types/V1FlowTokenizeResponse.java b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1FlowTokenizeResponse.java new file mode 100644 index 00000000..cf071b86 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1FlowTokenizeResponse.java @@ -0,0 +1,94 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.types; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.rest.core.ObjectMappers; + +import java.util.*; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = V1FlowTokenizeResponse.Builder.class) +public final class V1FlowTokenizeResponse { + private final Optional> response; + + private final Map additionalProperties; + + private V1FlowTokenizeResponse( + Optional> response, Map additionalProperties) { + this.response = response; + this.additionalProperties = additionalProperties; + } + + /** + * @return Tokenized data + */ + @JsonProperty("response") + public Optional> getResponse() { + return response; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V1FlowTokenizeResponse && equalTo((V1FlowTokenizeResponse) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(V1FlowTokenizeResponse other) { + return response.equals(other.response); + } + + @Override + public int hashCode() { + return Objects.hash(this.response); + } + + @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> response = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(V1FlowTokenizeResponse other) { + response(other.getResponse()); + return this; + } + + /** + *

Tokenized data

+ */ + @JsonSetter(value = "response", nulls = Nulls.SKIP) + public Builder response(Optional> response) { + this.response = response; + return this; + } + + public Builder response(List response) { + this.response = Optional.ofNullable(response); + return this; + } + + public V1FlowTokenizeResponse build() { + return new V1FlowTokenizeResponse(response, additionalProperties); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/types/V1FlowTokenizeResponseObject.java b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1FlowTokenizeResponseObject.java new file mode 100644 index 00000000..eb4a15ac --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1FlowTokenizeResponseObject.java @@ -0,0 +1,124 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.types; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.rest.core.ObjectMappers; + +import java.util.*; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = V1FlowTokenizeResponseObject.Builder.class) +public final class V1FlowTokenizeResponseObject { + private final Optional value; + + private final Optional> tokens; + + private final Map additionalProperties; + + private V1FlowTokenizeResponseObject( + Optional value, + Optional> tokens, + Map additionalProperties) { + this.value = value; + this.tokens = tokens; + this.additionalProperties = additionalProperties; + } + + /** + * @return Value of token + */ + @JsonProperty("value") + public Optional getValue() { + return value; + } + + /** + * @return Token value + */ + @JsonProperty("tokens") + public Optional> getTokens() { + return tokens; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V1FlowTokenizeResponseObject && equalTo((V1FlowTokenizeResponseObject) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(V1FlowTokenizeResponseObject other) { + return value.equals(other.value) && tokens.equals(other.tokens); + } + + @Override + public int hashCode() { + return Objects.hash(this.value, this.tokens); + } + + @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 value = Optional.empty(); + + private Optional> tokens = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(V1FlowTokenizeResponseObject other) { + value(other.getValue()); + tokens(other.getTokens()); + return this; + } + + /** + *

Value of token

+ */ + @JsonSetter(value = "value", nulls = Nulls.SKIP) + public Builder value(Optional value) { + this.value = value; + return this; + } + + public Builder value(Object value) { + this.value = Optional.ofNullable(value); + return this; + } + + /** + *

Token value

+ */ + @JsonSetter(value = "tokens", nulls = Nulls.SKIP) + public Builder tokens(Optional> tokens) { + this.tokens = tokens; + return this; + } + + public Builder tokens(List tokens) { + this.tokens = Optional.ofNullable(tokens); + return this; + } + + public V1FlowTokenizeResponseObject build() { + return new V1FlowTokenizeResponseObject(value, tokens, additionalProperties); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/types/V1FlowVaultMetricsData.java b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1FlowVaultMetricsData.java new file mode 100644 index 00000000..5345de38 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1FlowVaultMetricsData.java @@ -0,0 +1,96 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.types; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.rest.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 = V1FlowVaultMetricsData.Builder.class) +public final class V1FlowVaultMetricsData { + private final Optional> tables; + + private final Map additionalProperties; + + private V1FlowVaultMetricsData(Optional> tables, Map additionalProperties) { + this.tables = tables; + this.additionalProperties = additionalProperties; + } + + /** + * @return Map of table names to their metrics + */ + @JsonProperty("tables") + public Optional> getTables() { + return tables; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V1FlowVaultMetricsData && equalTo((V1FlowVaultMetricsData) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(V1FlowVaultMetricsData other) { + return tables.equals(other.tables); + } + + @Override + public int hashCode() { + return Objects.hash(this.tables); + } + + @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> tables = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(V1FlowVaultMetricsData other) { + tables(other.getTables()); + return this; + } + + /** + *

Map of table names to their metrics

+ */ + @JsonSetter(value = "tables", nulls = Nulls.SKIP) + public Builder tables(Optional> tables) { + this.tables = tables; + return this; + } + + public Builder tables(Map tables) { + this.tables = Optional.ofNullable(tables); + return this; + } + + public V1FlowVaultMetricsData build() { + return new V1FlowVaultMetricsData(tables, additionalProperties); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/types/V1FlowVaultMetricsResponse.java b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1FlowVaultMetricsResponse.java new file mode 100644 index 00000000..f933bce5 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1FlowVaultMetricsResponse.java @@ -0,0 +1,121 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.types; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.rest.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 = V1FlowVaultMetricsResponse.Builder.class) +public final class V1FlowVaultMetricsResponse { + private final Optional data; + + private final Optional> error; + + private final Map additionalProperties; + + private V1FlowVaultMetricsResponse( + Optional data, + Optional> error, + Map additionalProperties) { + this.data = data; + this.error = error; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("data") + public Optional getData() { + return data; + } + + /** + * @return Error information, if any + */ + @JsonProperty("error") + public Optional> getError() { + return error; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V1FlowVaultMetricsResponse && equalTo((V1FlowVaultMetricsResponse) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(V1FlowVaultMetricsResponse other) { + return data.equals(other.data) && error.equals(other.error); + } + + @Override + public int hashCode() { + return Objects.hash(this.data, this.error); + } + + @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 data = Optional.empty(); + + private Optional> error = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(V1FlowVaultMetricsResponse other) { + data(other.getData()); + error(other.getError()); + return this; + } + + @JsonSetter(value = "data", nulls = Nulls.SKIP) + public Builder data(Optional data) { + this.data = data; + return this; + } + + public Builder data(V1FlowVaultMetricsData data) { + this.data = Optional.ofNullable(data); + return this; + } + + /** + *

Error information, if any

+ */ + @JsonSetter(value = "error", nulls = Nulls.SKIP) + public Builder error(Optional> error) { + this.error = error; + return this; + } + + public Builder error(Map error) { + this.error = Optional.ofNullable(error); + return this; + } + + public V1FlowVaultMetricsResponse build() { + return new V1FlowVaultMetricsResponse(data, error, additionalProperties); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/types/V1GetRequestData.java b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1GetRequestData.java new file mode 100644 index 00000000..0c0cfab9 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1GetRequestData.java @@ -0,0 +1,216 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.types; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.rest.core.ObjectMappers; + +import java.util.*; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = V1GetRequestData.Builder.class) +public final class V1GetRequestData { + private final Optional tableName; + + private final Optional> skyflowIDs; + + private final Optional> columnRedactions; + + private final Optional> columns; + + private final Optional> uniqueValues; + + private final Map additionalProperties; + + private V1GetRequestData( + Optional tableName, + Optional> skyflowIDs, + Optional> columnRedactions, + Optional> columns, + Optional> uniqueValues, + Map additionalProperties) { + this.tableName = tableName; + this.skyflowIDs = skyflowIDs; + this.columnRedactions = columnRedactions; + this.columns = columns; + this.uniqueValues = uniqueValues; + this.additionalProperties = additionalProperties; + } + + /** + * @return Name of the table where data is being fetched + */ + @JsonProperty("tableName") + public Optional getTableName() { + return tableName; + } + + /** + * @return Skyflow ID for the record to be fetched + */ + @JsonProperty("skyflowIDs") + public Optional> getSkyflowIDs() { + return skyflowIDs; + } + + /** + * @return List of columns to be redacted. + */ + @JsonProperty("columnRedactions") + public Optional> getColumnRedactions() { + return columnRedactions; + } + + /** + * @return List of columns to be fetched. + */ + @JsonProperty("columns") + public Optional> getColumns() { + return columns; + } + + /** + * @return List of unique constraint values to query records by data + */ + @JsonProperty("uniqueValues") + public Optional> getUniqueValues() { + return uniqueValues; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V1GetRequestData && equalTo((V1GetRequestData) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(V1GetRequestData other) { + return tableName.equals(other.tableName) + && skyflowIDs.equals(other.skyflowIDs) + && columnRedactions.equals(other.columnRedactions) + && columns.equals(other.columns) + && uniqueValues.equals(other.uniqueValues); + } + + @Override + public int hashCode() { + return Objects.hash(this.tableName, this.skyflowIDs, this.columnRedactions, this.columns, this.uniqueValues); + } + + @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 tableName = Optional.empty(); + + private Optional> skyflowIDs = Optional.empty(); + + private Optional> columnRedactions = Optional.empty(); + + private Optional> columns = Optional.empty(); + + private Optional> uniqueValues = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(V1GetRequestData other) { + tableName(other.getTableName()); + skyflowIDs(other.getSkyflowIDs()); + columnRedactions(other.getColumnRedactions()); + columns(other.getColumns()); + uniqueValues(other.getUniqueValues()); + return this; + } + + /** + *

Name of the table where data is being fetched

+ */ + @JsonSetter(value = "tableName", nulls = Nulls.SKIP) + public Builder tableName(Optional tableName) { + this.tableName = tableName; + return this; + } + + public Builder tableName(String tableName) { + this.tableName = Optional.ofNullable(tableName); + return this; + } + + /** + *

Skyflow ID for the record to be fetched

+ */ + @JsonSetter(value = "skyflowIDs", nulls = Nulls.SKIP) + public Builder skyflowIDs(Optional> skyflowIDs) { + this.skyflowIDs = skyflowIDs; + return this; + } + + public Builder skyflowIDs(List skyflowIDs) { + this.skyflowIDs = Optional.ofNullable(skyflowIDs); + return this; + } + + /** + *

List of columns to be redacted.

+ */ + @JsonSetter(value = "columnRedactions", nulls = Nulls.SKIP) + public Builder columnRedactions(Optional> columnRedactions) { + this.columnRedactions = columnRedactions; + return this; + } + + public Builder columnRedactions(List columnRedactions) { + this.columnRedactions = Optional.ofNullable(columnRedactions); + return this; + } + + /** + *

List of columns to be fetched.

+ */ + @JsonSetter(value = "columns", nulls = Nulls.SKIP) + public Builder columns(Optional> columns) { + this.columns = columns; + return this; + } + + public Builder columns(List columns) { + this.columns = Optional.ofNullable(columns); + return this; + } + + /** + *

List of unique constraint values to query records by data

+ */ + @JsonSetter(value = "uniqueValues", nulls = Nulls.SKIP) + public Builder uniqueValues(Optional> uniqueValues) { + this.uniqueValues = uniqueValues; + return this; + } + + public Builder uniqueValues(List uniqueValues) { + this.uniqueValues = Optional.ofNullable(uniqueValues); + return this; + } + + public V1GetRequestData build() { + return new V1GetRequestData( + tableName, skyflowIDs, columnRedactions, columns, uniqueValues, additionalProperties); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/types/V1GetResponse.java b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1GetResponse.java new file mode 100644 index 00000000..a64a7588 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1GetResponse.java @@ -0,0 +1,93 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.types; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.rest.core.ObjectMappers; + +import java.util.*; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = V1GetResponse.Builder.class) +public final class V1GetResponse { + private final Optional> records; + + private final Map additionalProperties; + + private V1GetResponse(Optional> records, Map additionalProperties) { + this.records = records; + this.additionalProperties = additionalProperties; + } + + /** + * @return List of fetched records with skyflow ID, tokens, data, and any partial errors + */ + @JsonProperty("records") + public Optional> getRecords() { + return records; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V1GetResponse && equalTo((V1GetResponse) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(V1GetResponse other) { + return records.equals(other.records); + } + + @Override + public int hashCode() { + return Objects.hash(this.records); + } + + @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> records = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(V1GetResponse other) { + records(other.getRecords()); + return this; + } + + /** + *

List of fetched records with skyflow ID, tokens, data, and any partial errors

+ */ + @JsonSetter(value = "records", nulls = Nulls.SKIP) + public Builder records(Optional> records) { + this.records = records; + return this; + } + + public Builder records(List records) { + this.records = Optional.ofNullable(records); + return this; + } + + public V1GetResponse build() { + return new V1GetResponse(records, additionalProperties); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/types/V1InsertRecordData.java b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1InsertRecordData.java new file mode 100644 index 00000000..7de162cc --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1InsertRecordData.java @@ -0,0 +1,182 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.types; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.rest.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 = V1InsertRecordData.Builder.class) +public final class V1InsertRecordData { + private final Optional> data; + + private final Optional> tokens; + + private final Optional tableName; + + private final Optional upsert; + + private final Map additionalProperties; + + private V1InsertRecordData( + Optional> data, + Optional> tokens, + Optional tableName, + Optional upsert, + Map additionalProperties) { + this.data = data; + this.tokens = tokens; + this.tableName = tableName; + this.upsert = upsert; + this.additionalProperties = additionalProperties; + } + + /** + * @return Columns names and values + */ + @JsonProperty("data") + public Optional> getData() { + return data; + } + + /** + * @return undocumented_field; Tokens data for the columns if any + */ + @JsonProperty("tokens") + public Optional> getTokens() { + return tokens; + } + + /** + * @return Table name for the record + */ + @JsonProperty("tableName") + public Optional getTableName() { + return tableName; + } + + @JsonProperty("upsert") + public Optional getUpsert() { + return upsert; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V1InsertRecordData && equalTo((V1InsertRecordData) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(V1InsertRecordData other) { + return data.equals(other.data) + && tokens.equals(other.tokens) + && tableName.equals(other.tableName) + && upsert.equals(other.upsert); + } + + @Override + public int hashCode() { + return Objects.hash(this.data, this.tokens, this.tableName, this.upsert); + } + + @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> data = Optional.empty(); + + private Optional> tokens = Optional.empty(); + + private Optional tableName = Optional.empty(); + + private Optional upsert = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(V1InsertRecordData other) { + data(other.getData()); + tokens(other.getTokens()); + tableName(other.getTableName()); + upsert(other.getUpsert()); + return this; + } + + /** + *

Columns names and values

+ */ + @JsonSetter(value = "data", nulls = Nulls.SKIP) + public Builder data(Optional> data) { + this.data = data; + return this; + } + + public Builder data(Map data) { + this.data = Optional.ofNullable(data); + return this; + } + + /** + *

undocumented_field; Tokens data for the columns if any

+ */ + @JsonSetter(value = "tokens", nulls = Nulls.SKIP) + public Builder tokens(Optional> tokens) { + this.tokens = tokens; + return this; + } + + public Builder tokens(Map tokens) { + this.tokens = Optional.ofNullable(tokens); + return this; + } + + /** + *

Table name for the record

+ */ + @JsonSetter(value = "tableName", nulls = Nulls.SKIP) + public Builder tableName(Optional tableName) { + this.tableName = tableName; + return this; + } + + public Builder tableName(String tableName) { + this.tableName = Optional.ofNullable(tableName); + return this; + } + + @JsonSetter(value = "upsert", nulls = Nulls.SKIP) + public Builder upsert(Optional upsert) { + this.upsert = upsert; + return this; + } + + public Builder upsert(V1Upsert upsert) { + this.upsert = Optional.ofNullable(upsert); + return this; + } + + public V1InsertRecordData build() { + return new V1InsertRecordData(data, tokens, tableName, upsert, additionalProperties); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/types/V1InsertResponse.java b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1InsertResponse.java new file mode 100644 index 00000000..4ff3ff2d --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1InsertResponse.java @@ -0,0 +1,93 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.types; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.rest.core.ObjectMappers; + +import java.util.*; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = V1InsertResponse.Builder.class) +public final class V1InsertResponse { + private final Optional> records; + + private final Map additionalProperties; + + private V1InsertResponse(Optional> records, Map additionalProperties) { + this.records = records; + this.additionalProperties = additionalProperties; + } + + /** + * @return List of inserted records with skyflow ID, tokens, data, and any partial errors. + */ + @JsonProperty("records") + public Optional> getRecords() { + return records; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V1InsertResponse && equalTo((V1InsertResponse) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(V1InsertResponse other) { + return records.equals(other.records); + } + + @Override + public int hashCode() { + return Objects.hash(this.records); + } + + @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> records = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(V1InsertResponse other) { + records(other.getRecords()); + return this; + } + + /** + *

List of inserted records with skyflow ID, tokens, data, and any partial errors.

+ */ + @JsonSetter(value = "records", nulls = Nulls.SKIP) + public Builder records(Optional> records) { + this.records = records; + return this; + } + + public Builder records(List records) { + this.records = Optional.ofNullable(records); + return this; + } + + public V1InsertResponse build() { + return new V1InsertResponse(records, additionalProperties); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/types/V1RecordResponseObject.java b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1RecordResponseObject.java new file mode 100644 index 00000000..b6c2467c --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1RecordResponseObject.java @@ -0,0 +1,280 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.types; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.rest.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 = V1RecordResponseObject.Builder.class) +public final class V1RecordResponseObject { + private final Optional skyflowId; + + private final Optional> tokens; + + private final Optional> data; + + private final Optional> hashedData; + + private final Optional error; + + private final Optional httpCode; + + private final Optional tableName; + + private final Map additionalProperties; + + private V1RecordResponseObject( + Optional skyflowId, + Optional> tokens, + Optional> data, + Optional> hashedData, + Optional error, + Optional httpCode, + Optional tableName, + Map additionalProperties) { + this.skyflowId = skyflowId; + this.tokens = tokens; + this.data = data; + this.hashedData = hashedData; + this.error = error; + this.httpCode = httpCode; + this.tableName = tableName; + this.additionalProperties = additionalProperties; + } + + /** + * @return Skyflow ID for the inserted record + */ + @JsonProperty("skyflowID") + public Optional getSkyflowId() { + return skyflowId; + } + + /** + * @return Tokens data for the columns if any + */ + @JsonProperty("tokens") + public Optional> getTokens() { + return tokens; + } + + /** + * @return Columns names and values + */ + @JsonProperty("data") + public Optional> getData() { + return data; + } + + /** + * @return Hashed Data for the columns if any + */ + @JsonProperty("hashedData") + public Optional> getHashedData() { + return hashedData; + } + + /** + * @return Partial Error message if any + */ + @JsonProperty("error") + public Optional getError() { + return error; + } + + /** + * @return HTTP status code of the response + */ + @JsonProperty("httpCode") + public Optional getHttpCode() { + return httpCode; + } + + /** + * @return Name of the table record belongs to + */ + @JsonProperty("tableName") + public Optional getTableName() { + return tableName; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V1RecordResponseObject && equalTo((V1RecordResponseObject) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(V1RecordResponseObject other) { + return skyflowId.equals(other.skyflowId) + && tokens.equals(other.tokens) + && data.equals(other.data) + && hashedData.equals(other.hashedData) + && error.equals(other.error) + && httpCode.equals(other.httpCode) + && tableName.equals(other.tableName); + } + + @Override + public int hashCode() { + return Objects.hash( + this.skyflowId, this.tokens, this.data, this.hashedData, this.error, this.httpCode, this.tableName); + } + + @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 skyflowId = Optional.empty(); + + private Optional> tokens = Optional.empty(); + + private Optional> data = Optional.empty(); + + private Optional> hashedData = Optional.empty(); + + private Optional error = Optional.empty(); + + private Optional httpCode = Optional.empty(); + + private Optional tableName = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(V1RecordResponseObject other) { + skyflowId(other.getSkyflowId()); + tokens(other.getTokens()); + data(other.getData()); + hashedData(other.getHashedData()); + error(other.getError()); + httpCode(other.getHttpCode()); + tableName(other.getTableName()); + return this; + } + + /** + *

Skyflow ID for the inserted record

+ */ + @JsonSetter(value = "skyflowID", nulls = Nulls.SKIP) + public Builder skyflowId(Optional skyflowId) { + this.skyflowId = skyflowId; + return this; + } + + public Builder skyflowId(String skyflowId) { + this.skyflowId = Optional.ofNullable(skyflowId); + return this; + } + + /** + *

Tokens data for the columns if any

+ */ + @JsonSetter(value = "tokens", nulls = Nulls.SKIP) + public Builder tokens(Optional> tokens) { + this.tokens = tokens; + return this; + } + + public Builder tokens(Map tokens) { + this.tokens = Optional.ofNullable(tokens); + return this; + } + + /** + *

Columns names and values

+ */ + @JsonSetter(value = "data", nulls = Nulls.SKIP) + public Builder data(Optional> data) { + this.data = data; + return this; + } + + public Builder data(Map data) { + this.data = Optional.ofNullable(data); + return this; + } + + /** + *

Hashed Data for the columns if any

+ */ + @JsonSetter(value = "hashedData", nulls = Nulls.SKIP) + public Builder hashedData(Optional> hashedData) { + this.hashedData = hashedData; + return this; + } + + public Builder hashedData(Map hashedData) { + this.hashedData = Optional.ofNullable(hashedData); + return this; + } + + /** + *

Partial Error message if any

+ */ + @JsonSetter(value = "error", nulls = Nulls.SKIP) + public Builder error(Optional error) { + this.error = error; + return this; + } + + public Builder error(String error) { + this.error = Optional.ofNullable(error); + return this; + } + + /** + *

HTTP status code of the response

+ */ + @JsonSetter(value = "httpCode", nulls = Nulls.SKIP) + public Builder httpCode(Optional httpCode) { + this.httpCode = httpCode; + return this; + } + + public Builder httpCode(Integer httpCode) { + this.httpCode = Optional.ofNullable(httpCode); + return this; + } + + /** + *

Name of the table record belongs to

+ */ + @JsonSetter(value = "tableName", nulls = Nulls.SKIP) + public Builder tableName(Optional tableName) { + this.tableName = tableName; + return this; + } + + public Builder tableName(String tableName) { + this.tableName = Optional.ofNullable(tableName); + return this; + } + + public V1RecordResponseObject build() { + return new V1RecordResponseObject( + skyflowId, tokens, data, hashedData, error, httpCode, tableName, additionalProperties); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/types/V1TokenGroupRedactions.java b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1TokenGroupRedactions.java new file mode 100644 index 00000000..5d5fc97f --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1TokenGroupRedactions.java @@ -0,0 +1,125 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.types; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.rest.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 = V1TokenGroupRedactions.Builder.class) +public final class V1TokenGroupRedactions { + private final Optional tokenGroupName; + + private final Optional redaction; + + private final Map additionalProperties; + + private V1TokenGroupRedactions( + Optional tokenGroupName, Optional redaction, Map additionalProperties) { + this.tokenGroupName = tokenGroupName; + this.redaction = redaction; + this.additionalProperties = additionalProperties; + } + + /** + * @return Name of the token group to be redacted + */ + @JsonProperty("tokenGroupName") + public Optional getTokenGroupName() { + return tokenGroupName; + } + + /** + * @return Name of the redaction. Eg: plain_text, redacted, mask1 + */ + @JsonProperty("redaction") + public Optional getRedaction() { + return redaction; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V1TokenGroupRedactions && equalTo((V1TokenGroupRedactions) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(V1TokenGroupRedactions other) { + return tokenGroupName.equals(other.tokenGroupName) && redaction.equals(other.redaction); + } + + @Override + public int hashCode() { + return Objects.hash(this.tokenGroupName, this.redaction); + } + + @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 tokenGroupName = Optional.empty(); + + private Optional redaction = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(V1TokenGroupRedactions other) { + tokenGroupName(other.getTokenGroupName()); + redaction(other.getRedaction()); + return this; + } + + /** + *

Name of the token group to be redacted

+ */ + @JsonSetter(value = "tokenGroupName", nulls = Nulls.SKIP) + public Builder tokenGroupName(Optional tokenGroupName) { + this.tokenGroupName = tokenGroupName; + return this; + } + + public Builder tokenGroupName(String tokenGroupName) { + this.tokenGroupName = Optional.ofNullable(tokenGroupName); + return this; + } + + /** + *

Name of the redaction. Eg: plain_text, redacted, mask1

+ */ + @JsonSetter(value = "redaction", nulls = Nulls.SKIP) + public Builder redaction(Optional redaction) { + this.redaction = redaction; + return this; + } + + public Builder redaction(String redaction) { + this.redaction = Optional.ofNullable(redaction); + return this; + } + + public V1TokenGroupRedactions build() { + return new V1TokenGroupRedactions(tokenGroupName, redaction, additionalProperties); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/types/V1UniqueValue.java b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1UniqueValue.java new file mode 100644 index 00000000..3b188c6d --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1UniqueValue.java @@ -0,0 +1,96 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.types; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.rest.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 = V1UniqueValue.Builder.class) +public final class V1UniqueValue { + private final Optional> data; + + private final Map additionalProperties; + + private V1UniqueValue(Optional> data, Map additionalProperties) { + this.data = data; + this.additionalProperties = additionalProperties; + } + + /** + * @return Columns names and values for unique value entry + */ + @JsonProperty("data") + public Optional> getData() { + return data; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V1UniqueValue && equalTo((V1UniqueValue) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(V1UniqueValue other) { + return data.equals(other.data); + } + + @Override + public int hashCode() { + return Objects.hash(this.data); + } + + @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> data = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(V1UniqueValue other) { + data(other.getData()); + return this; + } + + /** + *

Columns names and values for unique value entry

+ */ + @JsonSetter(value = "data", nulls = Nulls.SKIP) + public Builder data(Optional> data) { + this.data = data; + return this; + } + + public Builder data(Map data) { + this.data = Optional.ofNullable(data); + return this; + } + + public V1UniqueValue build() { + return new V1UniqueValue(data, additionalProperties); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/types/V1UpdateRecordData.java b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1UpdateRecordData.java new file mode 100644 index 00000000..5a904005 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1UpdateRecordData.java @@ -0,0 +1,188 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.types; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.rest.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 = V1UpdateRecordData.Builder.class) +public final class V1UpdateRecordData { + private final Optional skyflowId; + + private final Optional> data; + + private final Optional> tokens; + + private final Optional tableName; + + private final Map additionalProperties; + + private V1UpdateRecordData( + Optional skyflowId, + Optional> data, + Optional> tokens, + Optional tableName, + Map additionalProperties) { + this.skyflowId = skyflowId; + this.data = data; + this.tokens = tokens; + this.tableName = tableName; + this.additionalProperties = additionalProperties; + } + + /** + * @return Skyflow ID for the record to be updated + */ + @JsonProperty("skyflowID") + public Optional getSkyflowId() { + return skyflowId; + } + + /** + * @return List of data row wise that is to be updated in the vault + */ + @JsonProperty("data") + public Optional> getData() { + return data; + } + + /** + * @return undocumented_field; Tokens data for the columns if any + */ + @JsonProperty("tokens") + public Optional> getTokens() { + return tokens; + } + + /** + * @return Table name for the record + */ + @JsonProperty("tableName") + public Optional getTableName() { + return tableName; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V1UpdateRecordData && equalTo((V1UpdateRecordData) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(V1UpdateRecordData other) { + return skyflowId.equals(other.skyflowId) + && data.equals(other.data) + && tokens.equals(other.tokens) + && tableName.equals(other.tableName); + } + + @Override + public int hashCode() { + return Objects.hash(this.skyflowId, this.data, this.tokens, this.tableName); + } + + @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 skyflowId = Optional.empty(); + + private Optional> data = Optional.empty(); + + private Optional> tokens = Optional.empty(); + + private Optional tableName = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(V1UpdateRecordData other) { + skyflowId(other.getSkyflowId()); + data(other.getData()); + tokens(other.getTokens()); + tableName(other.getTableName()); + return this; + } + + /** + *

Skyflow ID for the record to be updated

+ */ + @JsonSetter(value = "skyflowID", nulls = Nulls.SKIP) + public Builder skyflowId(Optional skyflowId) { + this.skyflowId = skyflowId; + return this; + } + + public Builder skyflowId(String skyflowId) { + this.skyflowId = Optional.ofNullable(skyflowId); + return this; + } + + /** + *

List of data row wise that is to be updated in the vault

+ */ + @JsonSetter(value = "data", nulls = Nulls.SKIP) + public Builder data(Optional> data) { + this.data = data; + return this; + } + + public Builder data(Map data) { + this.data = Optional.ofNullable(data); + return this; + } + + /** + *

undocumented_field; Tokens data for the columns if any

+ */ + @JsonSetter(value = "tokens", nulls = Nulls.SKIP) + public Builder tokens(Optional> tokens) { + this.tokens = tokens; + return this; + } + + public Builder tokens(Map tokens) { + this.tokens = Optional.ofNullable(tokens); + return this; + } + + /** + *

Table name for the record

+ */ + @JsonSetter(value = "tableName", nulls = Nulls.SKIP) + public Builder tableName(Optional tableName) { + this.tableName = tableName; + return this; + } + + public Builder tableName(String tableName) { + this.tableName = Optional.ofNullable(tableName); + return this; + } + + public V1UpdateRecordData build() { + return new V1UpdateRecordData(skyflowId, data, tokens, tableName, additionalProperties); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/types/V1UpdateResponse.java b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1UpdateResponse.java new file mode 100644 index 00000000..38dfe0ab --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1UpdateResponse.java @@ -0,0 +1,93 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.types; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.rest.core.ObjectMappers; + +import java.util.*; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = V1UpdateResponse.Builder.class) +public final class V1UpdateResponse { + private final Optional> records; + + private final Map additionalProperties; + + private V1UpdateResponse(Optional> records, Map additionalProperties) { + this.records = records; + this.additionalProperties = additionalProperties; + } + + /** + * @return List of updated records with skyflow ID, tokens, data, and any partial errors + */ + @JsonProperty("records") + public Optional> getRecords() { + return records; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V1UpdateResponse && equalTo((V1UpdateResponse) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(V1UpdateResponse other) { + return records.equals(other.records); + } + + @Override + public int hashCode() { + return Objects.hash(this.records); + } + + @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> records = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(V1UpdateResponse other) { + records(other.getRecords()); + return this; + } + + /** + *

List of updated records with skyflow ID, tokens, data, and any partial errors

+ */ + @JsonSetter(value = "records", nulls = Nulls.SKIP) + public Builder records(Optional> records) { + this.records = records; + return this; + } + + public Builder records(List records) { + this.records = Optional.ofNullable(records); + return this; + } + + public V1UpdateResponse build() { + return new V1UpdateResponse(records, additionalProperties); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/generated/rest/types/V1Upsert.java b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1Upsert.java new file mode 100644 index 00000000..a0c27b0e --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/generated/rest/types/V1Upsert.java @@ -0,0 +1,118 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.types; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.skyflow.generated.rest.core.ObjectMappers; + +import java.util.*; + +@JsonInclude(JsonInclude.Include.NON_ABSENT) +@JsonDeserialize(builder = V1Upsert.Builder.class) +public final class V1Upsert { + private final Optional updateType; + + private final Optional> uniqueColumns; + + private final Map additionalProperties; + + private V1Upsert( + Optional updateType, + Optional> uniqueColumns, + Map additionalProperties) { + this.updateType = updateType; + this.uniqueColumns = uniqueColumns; + this.additionalProperties = additionalProperties; + } + + @JsonProperty("updateType") + public Optional getUpdateType() { + return updateType; + } + + /** + * @return Name of a unique columns in the table. Uses upsert operations to check if a record exists based on the unique column's value. If a matching record exists, the record updates with the values you provide. If a matching record doesn't exist, the upsert operation inserts a new record. + */ + @JsonProperty("uniqueColumns") + public Optional> getUniqueColumns() { + return uniqueColumns; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + return other instanceof V1Upsert && equalTo((V1Upsert) other); + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + private boolean equalTo(V1Upsert other) { + return updateType.equals(other.updateType) && uniqueColumns.equals(other.uniqueColumns); + } + + @Override + public int hashCode() { + return Objects.hash(this.updateType, this.uniqueColumns); + } + + @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 updateType = Optional.empty(); + + private Optional> uniqueColumns = Optional.empty(); + + @JsonAnySetter + private Map additionalProperties = new HashMap<>(); + + private Builder() {} + + public Builder from(V1Upsert other) { + updateType(other.getUpdateType()); + uniqueColumns(other.getUniqueColumns()); + return this; + } + + @JsonSetter(value = "updateType", nulls = Nulls.SKIP) + public Builder updateType(Optional updateType) { + this.updateType = updateType; + return this; + } + + public Builder updateType(FlowEnumUpdateType updateType) { + this.updateType = Optional.ofNullable(updateType); + return this; + } + + /** + *

Name of a unique columns in the table. Uses upsert operations to check if a record exists based on the unique column's value. If a matching record exists, the record updates with the values you provide. If a matching record doesn't exist, the upsert operation inserts a new record.

+ */ + @JsonSetter(value = "uniqueColumns", nulls = Nulls.SKIP) + public Builder uniqueColumns(Optional> uniqueColumns) { + this.uniqueColumns = uniqueColumns; + return this; + } + + public Builder uniqueColumns(List uniqueColumns) { + this.uniqueColumns = Optional.ofNullable(uniqueColumns); + return this; + } + + public V1Upsert build() { + return new V1Upsert(updateType, uniqueColumns, additionalProperties); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/utils/Constants.java b/flowvault/src/main/java/com/skyflow/utils/Constants.java new file mode 100644 index 00000000..acbd471f --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/utils/Constants.java @@ -0,0 +1,51 @@ +package com.skyflow.utils; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +public final class Constants extends BaseConstants { + public static final String SDK_NAME = "Skyflow Java SDK"; + public static final String SDK_VERSION; + public static final String VAULT_DOMAIN = ".skyvault."; + public static final String SDK_PREFIX; + public static final String SDK_METRIC_NAME_VERSION_PREFIX = "skyflow-flowvault-java@"; + public static final Integer MAX_BULK_DATA_SIZE = 10000; + public static final Integer INSERT_BATCH_SIZE = 50; + public static final Integer MAX_INSERT_BATCH_SIZE = 1000; + public static final Integer INSERT_CONCURRENCY_LIMIT = 1; + public static final Integer MAX_INSERT_CONCURRENCY_LIMIT = 10; + public static final Integer DETOKENIZE_BATCH_SIZE = 50; + public static final Integer DETOKENIZE_CONCURRENCY_LIMIT = 1; + public static final Integer MAX_DETOKENIZE_BATCH_SIZE = 1000; + public static final Integer MAX_DETOKENIZE_CONCURRENCY_LIMIT = 10; + public static final Integer DELETE_TOKENS_BATCH_SIZE = 50; + public static final Integer DELETE_TOKENS_CONCURRENCY_LIMIT = 1; + public static final Integer MAX_DELETE_TOKENS_BATCH_SIZE = 1000; + public static final Integer MAX_DELETE_TOKENS_CONCURRENCY_LIMIT = 10; + public static final Integer TOKENIZE_BATCH_SIZE = 50; + public static final Integer TOKENIZE_CONCURRENCY_LIMIT = 1; + public static final Integer MAX_TOKENIZE_BATCH_SIZE = 1000; + public static final Integer MAX_TOKENIZE_CONCURRENCY_LIMIT = 10; + public static final String DEFAULT_SDK_VERSION = "v3"; + public static final String CONTEXT_KEY_REGEX = "^[a-zA-Z0-9_]+$"; + + static { + String sdkVersion; + // Use a static initializer block to read the properties file + Properties properties = new Properties(); + try (InputStream input = Constants.class.getClassLoader().getResourceAsStream("sdk.properties")) { + if (input == null) { + sdkVersion = DEFAULT_SDK_VERSION; + } else { + properties.load(input); + sdkVersion = properties.getProperty("sdk.version", DEFAULT_SDK_VERSION); + } + } catch (IOException ex) { + sdkVersion = DEFAULT_SDK_VERSION; + } + SDK_VERSION = sdkVersion; + SDK_PREFIX = SDK_NAME + " " + SDK_VERSION; + } + +} diff --git a/flowvault/src/main/java/com/skyflow/utils/SkyflowRetryInterceptor.java b/flowvault/src/main/java/com/skyflow/utils/SkyflowRetryInterceptor.java new file mode 100644 index 00000000..703c698e --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/utils/SkyflowRetryInterceptor.java @@ -0,0 +1,97 @@ +package com.skyflow.utils; + +import okhttp3.Interceptor; +import okhttp3.Response; + +import java.io.IOException; +import java.util.Random; + +/** + * Retries failed requests with exponential backoff and jitter. + *

+ * This exists as hand-written code rather than using the generated + * {@code com.skyflow.generated.rest.core.RetryInterceptor} because that one only accepts a retry + * count — it has no way to configure the backoff delays that {@code VaultConfig} exposes. It also + * keeps its backoff counter on the interceptor instance, so a single shared instance exhausts its + * retry budget once for the whole client rather than once per request; this implementation keeps + * that state per call. + *

+ * Retries the same statuses the generated interceptor does: 408, 429, and any 5xx. + */ +public final class SkyflowRetryInterceptor implements Interceptor { + + /** Fraction of the computed delay applied as random jitter, so retries do not align. */ + private static final double JITTER_FACTOR = 0.2; + + private final int maxRetries; + private final long initialRetryDelayMillis; + private final long maxRetryDelayMillis; + private final Random random = new Random(); + + public SkyflowRetryInterceptor(int maxRetries, long initialRetryDelayMillis, long maxRetryDelayMillis) { + if (maxRetries < 0) { + throw new IllegalArgumentException("maxRetries must be non-negative"); + } + if (initialRetryDelayMillis < 0) { + throw new IllegalArgumentException("initialRetryDelayMillis must be non-negative"); + } + if (maxRetryDelayMillis < 0) { + throw new IllegalArgumentException("maxRetryDelayMillis must be non-negative"); + } + this.maxRetries = maxRetries; + this.initialRetryDelayMillis = initialRetryDelayMillis; + this.maxRetryDelayMillis = maxRetryDelayMillis; + } + + @Override + public Response intercept(Chain chain) throws IOException { + Response response = chain.proceed(chain.request()); + // Retry budget is scoped to this call, not to the interceptor instance. + for (int attempt = 1; attempt <= maxRetries && shouldRetry(response.code()); attempt++) { + sleep(backoffMillis(attempt)); + response.close(); + response = chain.proceed(chain.request()); + } + return response; + } + + /** Exponential growth from the initial delay, capped at the maximum, then jittered. */ + long backoffMillis(int attempt) { + long delay = initialRetryDelayMillis; + for (int i = 1; i < attempt && delay < maxRetryDelayMillis; i++) { + delay = delay > maxRetryDelayMillis / 2 ? maxRetryDelayMillis : delay * 2; + } + delay = Math.min(delay, maxRetryDelayMillis); + long jitter = (long) (delay * JITTER_FACTOR); + if (jitter <= 0) { + return delay; + } + // delay +/- up to JITTER_FACTOR, never negative. + return Math.max(0, delay - jitter + random.nextInt((int) Math.min(2 * jitter + 1, Integer.MAX_VALUE))); + } + + static boolean shouldRetry(int statusCode) { + return statusCode == 408 || statusCode == 429 || statusCode >= 500; + } + + private static void sleep(long millis) throws IOException { + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IOException("Interrupted while waiting to retry request", e); + } + } + + public int getMaxRetries() { + return maxRetries; + } + + public long getInitialRetryDelayMillis() { + return initialRetryDelayMillis; + } + + public long getMaxRetryDelayMillis() { + return maxRetryDelayMillis; + } +} diff --git a/flowvault/src/main/java/com/skyflow/utils/Utils.java b/flowvault/src/main/java/com/skyflow/utils/Utils.java new file mode 100644 index 00000000..e4261a00 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/utils/Utils.java @@ -0,0 +1,1012 @@ +package com.skyflow.utils; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.*; + +import com.google.gson.JsonObject; +import com.skyflow.config.VaultConfig; +import com.skyflow.enums.Env; +import com.skyflow.errors.ErrorCode; +import com.skyflow.errors.ErrorMessage; +import com.skyflow.errors.SkyflowException; +import com.skyflow.generated.rest.core.ApiClientApiException; +import com.skyflow.generated.rest.core.ObjectMappers; +import com.skyflow.generated.rest.resources.flowservice.requests.V1FlowDetokenizeRequest; +import com.skyflow.generated.rest.resources.flowservice.requests.V1InsertRequest; +import com.skyflow.generated.rest.types.FlowEnumUpdateType; +import com.skyflow.generated.rest.types.FlowTokenizeResponseObjectToken; +import com.skyflow.generated.rest.types.V1DeleteTokenResponseObject; +import com.skyflow.generated.rest.types.V1FlowDeleteTokenResponse; +import com.skyflow.generated.rest.types.V1FlowDetokenizeResponse; +import com.skyflow.generated.rest.types.V1FlowDetokenizeResponseObject; +import com.skyflow.generated.rest.types.V1FlowTokenizeRequestObject; +import com.skyflow.generated.rest.types.V1FlowTokenizeResponse; +import com.skyflow.generated.rest.types.V1FlowTokenizeResponseObject; +import com.skyflow.generated.rest.types.V1InsertRecordData; +import com.skyflow.generated.rest.types.V1InsertResponse; +import com.skyflow.generated.rest.types.V1RecordResponseObject; +import com.skyflow.generated.rest.types.V1TokenGroupRedactions; +import com.skyflow.generated.rest.types.V1Upsert; +import com.skyflow.logs.ErrorLogs; +import com.skyflow.utils.logger.LogUtil; +import com.skyflow.vault.data.BulkDeleteTokensRequest; +import com.skyflow.vault.data.BulkDeleteTokensResponseRecord; +import com.skyflow.vault.data.BulkTokenizeRequestRecord; +import com.skyflow.vault.data.BulkTokenizeResponseRecord; +import com.skyflow.vault.data.DeleteTokensRecord; +import com.skyflow.vault.data.TokenizeRequestRecord; +import com.skyflow.vault.data.TokenizeResponseToken; +import com.skyflow.vault.data.BulkDeleteTokensResponse; +import com.skyflow.vault.data.BulkDetokenizeRequest; +import com.skyflow.vault.data.BulkDetokenizeResponse; +import com.skyflow.vault.data.BulkDetokenizeResponseRecord; +import com.skyflow.vault.data.BulkInsertRequest; +import com.skyflow.vault.data.BulkInsertResponse; +import com.skyflow.vault.data.BulkInsertResponseRecord; +import com.skyflow.vault.data.BulkTokenizeRequest; +import com.skyflow.vault.data.BulkTokenizeResponse; +import com.skyflow.vault.data.ErrorRecord; +import com.skyflow.vault.data.InsertRequest; +import com.skyflow.vault.data.InsertRequestRecord; +import com.skyflow.vault.data.TokenGroupRedactions; +import com.skyflow.vault.data.UpsertOptions; + +import io.github.cdimascio.dotenv.Dotenv; +import io.github.cdimascio.dotenv.DotenvException; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public final class Utils extends BaseUtils { + + // Spellings the vault has used for the per-record status, in precedence order. + private static final String[] HTTP_CODE_KEYS = {"http_code", "httpCode", "statusCode"}; + + public static String getVaultUrl(String clusterId, Env env) { + // The 3-arg overload is inherited from common's BaseUtils, which keeps the older + // getVaultURL spelling (shared with v2), so it is qualified rather than renamed here. + return BaseUtils.getVaultURL(clusterId, env, Constants.VAULT_DOMAIN); + } + + public static JsonObject getMetrics() { + JsonObject details = getCommonMetrics(); + String sdkVersion = Constants.SDK_VERSION; + details.addProperty(Constants.SDK_METRIC_NAME_VERSION, Constants.SDK_METRIC_NAME_VERSION_PREFIX + sdkVersion); + return details; + } + + public static String getEnvVaultUrl() throws SkyflowException { + try { + String vaultUrl = System.getenv("VAULT_URL"); + if (vaultUrl == null) { + Dotenv dotenv = Dotenv.load(); + vaultUrl = dotenv.get("VAULT_URL"); + } + if (vaultUrl != null && vaultUrl.trim().isEmpty()) { + LogUtil.printErrorLog(ErrorLogs.EMPTY_VAULT_URL.getLog()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyVaultUrl.getMessage()); + } else if (vaultUrl != null && !isValidUrl(vaultUrl)) { + LogUtil.printErrorLog(ErrorLogs.INVALID_VAULT_URL_FORMAT.getLog()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.InvalidVaultUrlFormat.getMessage()); + } + return vaultUrl; + } catch (DotenvException e) { + return null; + } + } + + public static boolean isValidUrl(String url) { + URL parsedUrl; + try { + parsedUrl = new URL(url); + } catch (MalformedURLException e) { + return false; + } + + if (!parsedUrl.getProtocol().equalsIgnoreCase("https")) { + return false; + } else { + return parsedUrl.getHost() != null && !parsedUrl.getHost().isEmpty(); + } + } + + // Mirrors the "present" test used by the request validators: null and blank both count as absent. + private static boolean hasText(String value) { + return value != null && !value.trim().isEmpty(); + } + + private static V1Upsert toV1Upsert(UpsertOptions upsert) { + V1Upsert.Builder builder = V1Upsert.builder().uniqueColumns(upsert.getUniqueColumns()); + // updateType is a String on the request; the legal values come from the wire enum itself + // so there is a single source of truth. Validations rejects anything that does not match. + String updateType = upsert.getUpdateType(); + for (FlowEnumUpdateType type : FlowEnumUpdateType.values()) { + if (type.toString().equalsIgnoreCase(updateType)) { + builder.updateType(type); + break; + } + } + return builder.build(); + } + + public static V1InsertRequest getInsertRequestBody(InsertRequest request, VaultConfig config) { + List records = request.getRecords(); + List insertRecordDataList = new ArrayList<>(); + // tableName and upsert must reach the wire at exactly one level: the vault rejects a body + // that carries either at both the request and the record level. validateTableAndUpsertPlacement + // has already forced the caller to pick one, so mirror that choice here rather than copying + // the request-level value down onto every record. + for (InsertRequestRecord record : records) { + V1InsertRecordData.Builder data = V1InsertRecordData.builder() + .data(record.getData()); + // A blank record-level table name counts as absent, matching validateInsertRequest. + if (hasText(record.getTableName())) { + data.tableName(record.getTableName()); + } + if (record.getTokens() != null && !record.getTokens().isEmpty()) { + data.tokens(record.getTokens()); + } + UpsertOptions recordUpsert = record.getUpsert(); + if (recordUpsert != null && recordUpsert.getUniqueColumns() != null + && !recordUpsert.getUniqueColumns().isEmpty()) { + data.upsert(toV1Upsert(recordUpsert)); + } + insertRecordDataList.add(data.build()); + } + + V1InsertRequest.Builder builder = V1InsertRequest.builder() + .vaultId(config.getVaultId()) + .records(insertRecordDataList); + if (hasText(request.getTableName())) { + builder.tableName(request.getTableName()); + } + UpsertOptions requestUpsert = request.getUpsert(); + if (requestUpsert != null && requestUpsert.getUniqueColumns() != null + && !requestUpsert.getUniqueColumns().isEmpty()) { + builder.upsert(toV1Upsert(requestUpsert)); + } + return builder.build(); + } + + private static String extractRequestId(Map> headers) { + if (headers == null) return null; + List ids = headers.get(BaseConstants.REQUEST_ID_HEADER_KEY); + return (ids == null || ids.isEmpty()) ? null : ids.get(0); + } + + // ── Bulk (batched/concurrent) request-body builders ────────────────────── + + // BulkInsertRequest is an InsertRequest, so the bulk body is built exactly the same way. + public static com.skyflow.generated.rest.resources.flowservice.requests.V1InsertRequest getBulkInsertRequestBody(BulkInsertRequest request, VaultConfig config) { + return getInsertRequestBody(request, config); + } + + public static V1FlowDetokenizeRequest getBulkDetokenizeRequestBody(BulkDetokenizeRequest request, String vaultId) { + V1FlowDetokenizeRequest.Builder builder = V1FlowDetokenizeRequest.builder() + .vaultId(vaultId) + .tokens(request.getTokens()); + if (request.getTokenGroupRedactions() != null && !request.getTokenGroupRedactions().isEmpty()) { + List tokenGroupRedactionsList = new ArrayList<>(); + for (TokenGroupRedactions tokenGroupRedactions : request.getTokenGroupRedactions()) { + tokenGroupRedactionsList.add(V1TokenGroupRedactions.builder() + .tokenGroupName(tokenGroupRedactions.getTokenGroupName()) + .redaction(tokenGroupRedactions.getRedaction()) + .build()); + } + builder.tokenGroupRedactions(tokenGroupRedactionsList); + } + return builder.build(); + } + + public static com.skyflow.generated.rest.resources.flowservice.requests.V1FlowDeleteTokenRequest getBulkDeleteTokensRequestBody(BulkDeleteTokensRequest request, String vaultId) { + return com.skyflow.generated.rest.resources.flowservice.requests.V1FlowDeleteTokenRequest.builder() + .vaultId(vaultId) + .tokens(request.getTokens()) + .build(); + } + + public static com.skyflow.generated.rest.resources.flowservice.requests.V1FlowTokenizeRequest getBulkTokenizeRequestBody( + List records, String vaultId) { + List dataList = new ArrayList<>(); + for (BulkTokenizeRequestRecord record : records) { + dataList.add(buildTokenizeRequestObject(record)); + } + return com.skyflow.generated.rest.resources.flowservice.requests.V1FlowTokenizeRequest.builder() + .vaultId(vaultId) + .data(dataList) + .build(); + } + + // ── Bulk batching, exception-handling and response-formatting helpers ──── + + public static List> createBulkInsertBatches(List records, int batchSize) { + List> batches = new ArrayList<>(); + for (int i = 0; i < records.size(); i += batchSize) { + batches.add(records.subList(i, Math.min(i + batchSize, records.size()))); + } + return batches; + } + + public static List createBulkDetokenizeBatches(V1FlowDetokenizeRequest request, int batchSize) { + List detokenizeRequests = new ArrayList<>(); + List tokens = request.getTokens().get(); + + for (int i = 0; i < tokens.size(); i += batchSize) { + List batchTokens = tokens.subList(i, Math.min(i + batchSize, tokens.size())); + List tokenGroupRedactions = null; + if (request.getTokenGroupRedactions().isPresent() && !request.getTokenGroupRedactions().get().isEmpty()) { + tokenGroupRedactions = request.getTokenGroupRedactions().get(); + } + V1FlowDetokenizeRequest batchRequest = V1FlowDetokenizeRequest.builder() + .vaultId(request.getVaultId()) + .tokens(new ArrayList<>(batchTokens)) + .tokenGroupRedactions(tokenGroupRedactions) + .build(); + + detokenizeRequests.add(batchRequest); + } + + return detokenizeRequests; + } + + public static List createBulkDeleteTokensBatches( + com.skyflow.generated.rest.resources.flowservice.requests.V1FlowDeleteTokenRequest request, int batchSize) { + List batches = new ArrayList<>(); + List tokens = request.getTokens().get(); + for (int i = 0; i < tokens.size(); i += batchSize) { + List batchTokens = tokens.subList(i, Math.min(i + batchSize, tokens.size())); + com.skyflow.generated.rest.resources.flowservice.requests.V1FlowDeleteTokenRequest batchRequest = + com.skyflow.generated.rest.resources.flowservice.requests.V1FlowDeleteTokenRequest.builder() + .vaultId(request.getVaultId()) + .tokens(new ArrayList<>(batchTokens)) + .build(); + batches.add(batchRequest); + } + return batches; + } + + /** + * Splits the caller's records into batches, in order and without gaps. + * + *

A batch is closed early when the next record repeats a value already in it. The response + * carries no record identifier, so {@link #formatBulkTokenizeResponse} tells one record's rows + * from the next by watching the value change — which only works while values are distinct within + * a request. Batches stay contiguous, so a batch's records still occupy consecutive positions in + * the caller's list and their indexes follow from the batch's start. + */ + public static List> createBulkTokenizeBatches( + List records, int batchSize) { + List> batches = new ArrayList<>(); + if (records == null || records.isEmpty()) return batches; + List current = new ArrayList<>(); + Set valuesInBatch = new HashSet<>(); + Set valueKeysInBatch = new HashSet<>(); + for (BulkTokenizeRequestRecord record : records) { + Object value = record == null ? null : record.getValue(); + String valueKey = String.valueOf(value); + boolean repeatsValue = valuesInBatch.contains(value) || valueKeysInBatch.contains(valueKey); + if (!current.isEmpty() && (current.size() >= batchSize || repeatsValue)) { + batches.add(current); + current = new ArrayList<>(); + valuesInBatch = new HashSet<>(); + valueKeysInBatch = new HashSet<>(); + } + current.add(record); + valuesInBatch.add(value); + valueKeysInBatch.add(valueKey); + } + batches.add(current); + return batches; + } + // Error bodies routinely carry a key whose value is explicitly null (e.g. "skyflowID": null on a + // failed record), so containsKey() is not enough to know a value is usable — read through these + // helpers. Anything that throws here masks the real server error with a parsing crash. + + /** Value as a String, or null when the key is absent or explicitly null. */ + private static String readString(Map recordMap, String key) { + Object value = recordMap.get(key); + return value == null ? null : value.toString(); + } + + /** First usable HTTP status among the known spellings, else {@code fallback}. */ + private static int readHttpCode(Map recordMap, int fallback) { + for (String key : HTTP_CODE_KEYS) { + Object value = recordMap.get(key); + if (value instanceof Number) { + return ((Number) value).intValue(); + } + if (value instanceof String) { + try { + return Integer.parseInt(((String) value).trim()); + } catch (NumberFormatException ignored) { + // fall through to the next spelling + } + } + } + return fallback; + } + + /** + * Error text from "error", else "message", else a placeholder. Never null: a null message on an + * error record would make it read as a success downstream, since that is how failures are counted. + */ + private static String readErrorMessage(Map recordMap) { + String error = readString(recordMap, "error"); + if (error != null) { + return error; + } + String message = readString(recordMap, "message"); + return message != null ? message : "Unknown error"; + } + + public static BulkInsertResponseRecord createInsertErrorRecord(Map recordMap, int indexNumber, String requestId) { + BulkInsertResponseRecord err = null; + if (recordMap != null) { + int code = readHttpCode(recordMap, 500); + String skyflowID = readString(recordMap, "skyflowID"); + String tableName = readString(recordMap, "tableName"); + String message = readErrorMessage(recordMap); + err = new BulkInsertResponseRecord(indexNumber, tableName, skyflowID, null, null, code, message, requestId); + } + return err; + } + + public static BulkDetokenizeResponseRecord createDetokenizeErrorRecord(Map recordMap, int indexNumber, String requestId) { + BulkDetokenizeResponseRecord err = null; + if (recordMap != null) { + int code = readHttpCode(recordMap, 500); + // the failing token is echoed back so the caller can tell which one it was + String token = readString(recordMap, "token"); + String tokenGroupName = readString(recordMap, "tokenGroupName"); + String message = readErrorMessage(recordMap); + err = new BulkDetokenizeResponseRecord(indexNumber, token, null, tokenGroupName, null, code, message, requestId); + } + return err; + } + + public static ErrorRecord createErrorRecord(Map recordMap, int indexNumber, String requestId) { + ErrorRecord err = null; + if (recordMap != null) { + int code = readHttpCode(recordMap, 500); + String message = readErrorMessage(recordMap); + err = new ErrorRecord(indexNumber, message, code, requestId); + } + return err; + } + + // Errors are parsed into ErrorRecord (shared with the other bulk ops), then projected onto + // the unified BulkInsertResponseRecord shape that bulk insert now returns. + public static List handleBulkInsertBatchException( + Throwable ex, List batch, int batchNumber, int batchSize + ) { + List allRecords = new ArrayList<>(); + Throwable cause = ex.getCause(); + if (cause instanceof ApiClientApiException) { + ApiClientApiException apiException = (ApiClientApiException) cause; + String requestId = extractRequestId(apiException.headers()); + Object rawBody = apiException.body(); + Map responseBody = (rawBody instanceof Map) ? (Map) rawBody : null; + int indexNumber = batchNumber > 0 ? batchNumber * batchSize : 0; + if (responseBody != null) { + if (responseBody.containsKey("records")) { + Object recordss = responseBody.get("records"); + if (recordss instanceof List) { + List recordsList = (List) recordss; + for (Object record : recordsList) { + if (record instanceof Map) { + Map recordMap = (Map) record; + BulkInsertResponseRecord err = createInsertErrorRecord(recordMap, indexNumber, requestId); + allRecords.add(err); + indexNumber++; + } + } + } + } else if (responseBody.containsKey("error")) { + Object errField = responseBody.get("error"); + Map recordMap = (errField instanceof Map) ? (Map) errField : null; + String fallbackMsg = (errField instanceof String) ? (String) errField : null; + for (int j = 0; j < batch.size(); j++) { + BulkInsertResponseRecord err = null; + if(recordMap != null){ + err = createInsertErrorRecord(recordMap, indexNumber, requestId); + } else { + String errorMessage = null; + if (fallbackMsg != null){ + errorMessage = fallbackMsg; + } else { + errorMessage = apiException.getMessage(); + } + err = new BulkInsertResponseRecord(indexNumber, null, null, null, null, apiException.statusCode(), errorMessage, requestId); + + } + allRecords.add(err); + indexNumber++; + } + } + } + + if (allRecords.isEmpty()) { + for (int j = 0; j < batch.size(); j++) { + allRecords.add(new BulkInsertResponseRecord(indexNumber, null, null, null, null, apiException.statusCode(), apiException.getMessage(), requestId)); + indexNumber++; + } + } + } else { + int indexNumber = batchNumber > 0 ? batchNumber * batchSize : 0; + for (int j = 0; j < batch.size(); j++) { + String message = null; + if (cause != null && cause.getMessage() != null){ + message = cause.getMessage(); + } + if (cause != null && cause.getLocalizedMessage() !=null) { + message = cause.getLocalizedMessage(); + } + if (cause != null && cause.getCause() !=null) { + message = cause.getCause().toString(); + } + if (message == null || message.isEmpty() || message.trim().isEmpty()){ + message = ex.getMessage(); + } + BulkInsertResponseRecord err = new BulkInsertResponseRecord(indexNumber, null, null, null, null, 500, message, null); + allRecords.add(err); + indexNumber++; + } + } + return allRecords; + } + + // Errors are parsed into ErrorRecord (shared with the other bulk ops), then projected onto + // the unified BulkDetokenizeResponseRecord shape that bulk detokenize now returns. + public static List handleBulkDetokenizeBatchException( + Throwable ex, V1FlowDetokenizeRequest batch, int batchNumber, int batchSize + ) { + List allRecords = new ArrayList<>(); + Throwable cause = ex.getCause(); + if (cause instanceof ApiClientApiException) { + ApiClientApiException apiException = (ApiClientApiException) cause; + String requestId = extractRequestId(apiException.headers()); + Object rawBody = apiException.body(); + Map responseBody = (rawBody instanceof Map) ? (Map) rawBody : null; + int indexNumber = batchNumber * batchSize; + int tokenCount = batch.getTokens().isPresent() ? batch.getTokens().get().size() : 0; + if (responseBody != null) { + if (responseBody.containsKey("response")) { + Object recordss = responseBody.get("response"); + if (recordss instanceof List) { + List recordsList = (List) recordss; + for (Object record : recordsList) { + if (record instanceof Map) { + Map recordMap = (Map) record; + BulkDetokenizeResponseRecord err = createDetokenizeErrorRecord(recordMap, indexNumber, requestId); + allRecords.add(err); + indexNumber++; + } + } + } + } else if (responseBody.containsKey("error")) { + Object errField = responseBody.get("error"); + Map recordMap = (errField instanceof Map) ? (Map) errField : null; + String fallbackMsg = (errField instanceof String) ? (String) errField : null; + for (int j = 0; j < tokenCount; j++) { + BulkDetokenizeResponseRecord err = null; + if (recordMap != null) { + err = createDetokenizeErrorRecord(recordMap, indexNumber, requestId); + } else { + String errorMessage = null; + if (fallbackMsg != null) { + errorMessage = fallbackMsg; + } else { + errorMessage = apiException.getMessage(); + } + err = new BulkDetokenizeResponseRecord(indexNumber, null, null, null, null, apiException.statusCode(), errorMessage, requestId); + } + allRecords.add(err); + indexNumber++; + } + } + } + + if (allRecords.isEmpty()) { + for (int j = 0; j < tokenCount; j++) { + allRecords.add(new BulkDetokenizeResponseRecord(indexNumber, null, null, null, null, apiException.statusCode(), apiException.getMessage(), requestId)); + indexNumber++; + } + } + } else { + int indexNumber = batchNumber * batchSize; + String message = null; + if (cause != null && cause.getMessage() != null){ + message = cause.getMessage(); + } + if (cause != null && cause.getLocalizedMessage() !=null) { + message = cause.getLocalizedMessage(); + } + if (cause != null && cause.getCause() !=null) { + message = cause.getCause().toString(); + } + if (message == null || message.isEmpty() || message.trim().isEmpty()){ + message = ex.getMessage(); + } + for (int j = 0; j < batch.getTokens().get().size(); j++) { + BulkDetokenizeResponseRecord err = new BulkDetokenizeResponseRecord(indexNumber, null, null, null, null, 500, message, null); + allRecords.add(err); + indexNumber++; + } + } + return allRecords; + } + + public static List handleBulkDeleteTokensBatchException( + Throwable ex, + com.skyflow.generated.rest.resources.flowservice.requests.V1FlowDeleteTokenRequest batch, + int batchNumber, int batchSize + ) { + List errorRecords = new ArrayList<>(); + List batchTokens = (batch != null && batch.getTokens().isPresent()) + ? batch.getTokens().get() : new ArrayList<>(); + int startIndex = batchNumber * batchSize; + Throwable cause = ex.getCause(); + if (cause instanceof ApiClientApiException) { + ApiClientApiException apiException = (ApiClientApiException) cause; + String requestId = extractRequestId(apiException.headers()); + Object rawBody = apiException.body(); + Map responseBody = (rawBody instanceof Map) ? (Map) rawBody : null; + if (responseBody != null) { + if (responseBody.containsKey("tokens")) { + Object tokensList = responseBody.get("tokens"); + if (tokensList instanceof List) { + List recordsList = (List) tokensList; + for (int position = 0; position < recordsList.size(); position++) { + Object record = recordsList.get(position); + if (record instanceof Map) { + Map recordMap = (Map) record; + errorRecords.add(createDeleteTokensErrorRecord(recordMap, + startIndex + position, tokenAt(batchTokens, position), requestId)); + } + } + } + } else if (responseBody.containsKey("error")) { + Object errField = responseBody.get("error"); + Map recordMap = (errField instanceof Map) ? (Map) errField : null; + String fallbackMsg = (errField instanceof String) ? (String) errField : null; + for (int position = 0; position < batchTokens.size(); position++) { + errorRecords.add((recordMap != null) + ? createDeleteTokensErrorRecord(recordMap, startIndex + position, + tokenAt(batchTokens, position), requestId) + : new BulkDeleteTokensResponseRecord( + startIndex + position, tokenAt(batchTokens, position), + apiException.statusCode(), + fallbackMsg != null ? fallbackMsg : apiException.getMessage(), + requestId)); + } + } + } + if (errorRecords.isEmpty()) { + for (int position = 0; position < batchTokens.size(); position++) { + errorRecords.add(new BulkDeleteTokensResponseRecord( + startIndex + position, tokenAt(batchTokens, position), + apiException.statusCode(), apiException.getMessage(), requestId)); + } + } + } else { + // a transport-level failure never reached the API, so there is no id to report + for (int position = 0; position < batchTokens.size(); position++) { + errorRecords.add(new BulkDeleteTokensResponseRecord( + startIndex + position, tokenAt(batchTokens, position), 500, ex.getMessage())); + } + } + return errorRecords; + } + + private static String tokenAt(List tokens, int position) { + return (tokens != null && position < tokens.size()) ? tokens.get(position) : null; + } + + private static BulkDeleteTokensResponseRecord createDeleteTokensErrorRecord( + Map recordMap, int index, String requestedToken, String requestId) { + int code = 500; + if (recordMap.containsKey("http_code")) { + code = (Integer) recordMap.get("http_code"); + } else if (recordMap.containsKey("httpCode")) { + code = (Integer) recordMap.get("httpCode"); + } else if (recordMap.containsKey("statusCode")) { + code = (Integer) recordMap.get("statusCode"); + } + String message = recordMap.containsKey("error") ? (String) recordMap.get("error") : + recordMap.containsKey("message") ? (String) recordMap.get("message") : "Unknown error"; + Object echoedToken = recordMap.get("value"); + String token = (echoedToken instanceof String) ? (String) echoedToken : requestedToken; + return new BulkDeleteTokensResponseRecord(index, token, code, message, requestId); + } + + public static List handleBulkTokenizeBatchException( + Throwable ex, List batchRecords, int startIndex) { + String message; + int httpCode; + String requestId = null; + Throwable cause = ex.getCause(); + if (cause instanceof ApiClientApiException) { + ApiClientApiException apiException = (ApiClientApiException) cause; + // a rejected request still describes each record in its body - prefer that detail over + // the bare status code, and only synthesise entries when there is nothing to read + List fromBody = + tokenizeRecordsFromErrorBody(apiException, batchRecords, startIndex); + if (fromBody != null) { + return fromBody; + } + httpCode = apiException.statusCode(); + message = extractBatchErrorMessage(apiException); + requestId = extractRequestId(apiException.headers()); + } else { + // a transport-level failure never reached the API, so there is no id to report + httpCode = 500; + message = ex.getMessage(); + } + // a batch-level failure fails every token group of every value in that batch + List errorRecords = new ArrayList<>(); + if (batchRecords == null) return errorRecords; + for (int position = 0; position < batchRecords.size(); position++) { + BulkTokenizeRequestRecord requested = batchRecords.get(position); + List tokens = new ArrayList<>(); + List groupNames = requested.getTokenGroupNames(); + if (groupNames == null || groupNames.isEmpty()) { + tokens.add(new TokenizeResponseToken(null, null, httpCode, message, requestId)); + } else { + for (String groupName : groupNames) { + tokens.add(new TokenizeResponseToken(groupName, null, httpCode, message, requestId)); + } + } + errorRecords.add(new BulkTokenizeResponseRecord( + startIndex + position, requested.getValue(), tokens)); + } + return errorRecords; + } + + /** + * Rebuilds the response records from a rejected request's body. + * + *

The API answers a 4xx with the same {@code response} array it would have returned on + * success, one row per rejected (value, token group) carrying its own message - for example + * "Invalid request. BYOT token should contain one token group." Reading it keeps the caller's + * error identical whether the request was rejected outright or reported per record. + * + * @return null when the body is absent or in an unfamiliar shape, so the caller can fall back + * to summarising the batch by its status code + */ + private static List tokenizeRecordsFromErrorBody( + ApiClientApiException apiException, + List batchRecords, + int startIndex) { + Object rawBody = apiException.body(); + if (!(rawBody instanceof Map) || !((Map) rawBody).containsKey("response")) { + return null; + } + try { + V1FlowTokenizeResponse parsed = + ObjectMappers.JSON_MAPPER.convertValue(rawBody, V1FlowTokenizeResponse.class); + if (!parsed.getResponse().isPresent() || parsed.getResponse().get().isEmpty()) { + return null; + } + return groupTokenizeRows(parsed.getResponse().get(), batchRecords, startIndex, + extractRequestId(apiException.headers())); + } catch (RuntimeException ignored) { + // body did not deserialise into the shape we know; let the caller summarise instead + return null; + } + } + + /** Pulls the most specific message available from a failed batch response body. */ + private static String extractBatchErrorMessage(ApiClientApiException apiException) { + Object rawBody = apiException.body(); + if (rawBody instanceof Map) { + Object errField = ((Map) rawBody).get("error"); + if (errField instanceof String) { + return (String) errField; + } + if (errField instanceof Map) { + Map errMap = (Map) errField; + Object message = errMap.containsKey("error") ? errMap.get("error") : errMap.get("message"); + if (message instanceof String) { + return (String) message; + } + } + } + return apiException.getMessage(); + } + + private static BulkTokenizeRequestRecord recordAt(List records, int position) { + return (records != null && position < records.size()) ? records.get(position) : null; + } + + public static BulkInsertResponse formatBulkInsertResponse(V1InsertResponse response, int batch, int batchSize, Map> headers) { + BulkInsertResponse formattedResponse = null; + List records = new ArrayList<>(); + if (response != null && response.getRecords().isPresent()) { + List record = response.getRecords().get(); + int indexNumber = batch * batchSize; + int recordsSize = record.size(); + for (int index = 0; index < recordsSize; index++) { + V1RecordResponseObject current = record.get(index); + String reqID = null; + if(current.getError().isPresent()){ + reqID = extractRequestId(headers); + } + records.add(new BulkInsertResponseRecord( + indexNumber, + current.getTableName().orElse(null), + current.getSkyflowId().orElse(null), + current.getTokens().orElse(null), + current.getHashedData().orElse(null), + current.getHttpCode().orElse(current.getError().isPresent() ? 500 : 200), + current.getError().orElse(null), + reqID)); + indexNumber++; + } + formattedResponse = new BulkInsertResponse(records); + } + return formattedResponse; + } + + public static BulkDetokenizeResponse formatBulkDetokenizeResponse(V1FlowDetokenizeResponse response, int batch, int batchSize, Map> headers) { + if (response != null && response.getResponse().isPresent()) { + List record = response.getResponse().get(); + List records = new ArrayList<>(); + int indexNumber = batch * batchSize; + int recordsSize = record.size(); + for (int index = 0; index < recordsSize; index++) { + V1FlowDetokenizeResponseObject current = record.get(index); + Map data = null; + if(current.getMetadata().isPresent()){ + data = current.getMetadata().get(); + if (data.containsKey("skyflowID")) { + Object value = data.remove("skyflowID"); + data.put("skyflowId", value); + } + } + String reqID = null; + if(current.getError().isPresent()){ + reqID = extractRequestId(headers); + } + records.add(new BulkDetokenizeResponseRecord( + indexNumber, + current.getToken().orElse(null), + current.getValue().orElse(null), + current.getTokenGroupName().orElse(null), + data, + current.getHttpCode().orElse(current.getError().isPresent() ? 500 : 200), + current.getError().orElse(null), + reqID)); + indexNumber++; + } + return new BulkDetokenizeResponse(records); + } + return null; + } + + public static BulkDeleteTokensResponse formatBulkDeleteTokensResponse( + V1FlowDeleteTokenResponse response, + com.skyflow.generated.rest.resources.flowservice.requests.V1FlowDeleteTokenRequest batchRequest, + int batch, int batchSize, Map> headers) { + if (response != null && response.getTokens().isPresent()) { + List records = response.getTokens().get(); + List requestedTokens = batchRequest != null && batchRequest.getTokens().isPresent() + ? batchRequest.getTokens().get() : null; + List responseRecords = new ArrayList<>(); + int indexNumber = batch * batchSize; + // one id per API call, so every error this batch reports carries the same one + String requestId = extractRequestId(headers); + for (int position = 0; position < records.size(); position++) { + V1DeleteTokenResponseObject record = records.get(position); + boolean failed = isFailedRecord(record); + // The API echoes the token back on both paths, but fall back to the token we sent at + // this position so an error record is never missing its token. + String tokenValue = record.getValue().orElse( + requestedTokens != null && position < requestedTokens.size() + ? requestedTokens.get(position) : null); + responseRecords.add(new BulkDeleteTokensResponseRecord( + indexNumber, + tokenValue, + record.getHttpCode().orElse(failed ? 500 : 200), + failed ? record.getError().get() : null, + requestId + )); + indexNumber++; + } + return new BulkDeleteTokensResponse(responseRecords); + } + return null; + } + + /** Converts one wire record into the unified success/error record shape. */ + /** Maps one SDK request record to the wire object, carrying the BYOT token when supplied. */ + private static V1FlowTokenizeRequestObject buildTokenizeRequestObject(TokenizeRequestRecord record) { + V1FlowTokenizeRequestObject.Builder builder = V1FlowTokenizeRequestObject.builder() + .value(record.getValue()) + .tokenGroupNames(record.getTokenGroupNames()); + if (record.getToken() != null) { + builder = builder.token(record.getToken()); + } + return builder.build(); + } + + private static List buildTokenizeResponseTokens( + V1FlowTokenizeResponseObject record, String requestId) { + List tokens = new ArrayList<>(); + if (record.getTokens().isPresent()) { + for (FlowTokenizeResponseObjectToken tokenObj : record.getTokens().get()) { + boolean failed = tokenObj.getError().isPresent() + && tokenObj.getError().get() != null + && !tokenObj.getError().get().isEmpty(); + tokens.add(new TokenizeResponseToken( + tokenObj.getTokenGroupName().orElse(null), + tokenObj.getToken().orElse(null), + tokenObj.getHttpCode().orElse(failed ? 500 : 200), + failed ? tokenObj.getError().get() : null, + requestId + )); + } + } else { + // the API reports one flat row per (value, token group) instead of a nested tokens + // array; the generated type has no fields for those, so they land in additionalProperties + TokenizeResponseToken flat = flatToken(record, requestId); + if (flat != null) { + tokens.add(flat); + } + } + return tokens; + } + + /** + * Reads a flat {@code tokenGroupName}/{@code token}/{@code error}/{@code httpCode} row out of + * the wire object's unmodelled properties. Returns null when the row carries none of them, so a + * genuinely token-less record still reports an empty list rather than a phantom entry. + */ + private static TokenizeResponseToken flatToken(V1FlowTokenizeResponseObject record, String requestId) { + Map extras = record.getAdditionalProperties(); + if (extras == null || extras.isEmpty()) { + return null; + } + boolean carriesTokenFields = extras.containsKey("token") + || extras.containsKey("tokenGroupName") + || extras.containsKey("error") + || extras.containsKey("httpCode"); + if (!carriesTokenFields) { + return null; + } + String error = asNonEmptyString(extras.get("error")); + String token = asNonEmptyString(extras.get("token")); + Integer httpCode = extras.get("httpCode") instanceof Number + ? ((Number) extras.get("httpCode")).intValue() + : (error != null ? 500 : 200); + return new TokenizeResponseToken( + asNonEmptyString(extras.get("tokenGroupName")), token, httpCode, error, requestId); + } + + /** The API sends "" for a token or error that does not apply; normalise both to null. */ + private static String asNonEmptyString(Object value) { + if (!(value instanceof String)) { + return null; + } + String text = (String) value; + return text.isEmpty() ? null : text; + } + + /** + * A record counts as failed only when the API returned a non-empty error message together with + * a non-2xx status, mirroring the check the bulk path has always used. + */ + private static boolean isFailedRecord(V1DeleteTokenResponseObject record) { + return record.getError().isPresent() + && record.getError().get() != null + && !record.getError().get().isEmpty() + && record.getHttpCode().orElse(200) != 200; + } + + public static BulkTokenizeResponse formatBulkTokenizeResponse( + V1FlowTokenizeResponse response, + List batchRecords, + int startIndex, + Map> headers) { + if (response != null && response.getResponse().isPresent()) { + List rows = response.getResponse().get(); + // one id per API call, so every error this batch reports carries the same one + String requestId = extractRequestId(headers); + return new BulkTokenizeResponse(groupTokenizeRows(rows, batchRecords, startIndex, requestId)); + } + return null; + } + + /** + * Folds the response rows back onto the records that produced them. + * + *

The API emits one row per (value, token group) rather than one per record, and a record + * rejected outright yields a single row instead of one per group — so row count is not a + * function of the request. Rows do arrive in request order, though, and each carries its value, + * which is enough: a row belongs to the record being filled while it matches that record's value + * and the record has not yet taken as many rows as it asked for token groups. Anything else + * starts the next record. Batching keeps values distinct within a request (see + * {@link #createBulkTokenizeBatches}), so the value comparison never straddles two records. + * + *

A response already grouped one-row-per-record folds through this unchanged, since each row + * then matches exactly one record before the value moves on. + */ + private static List groupTokenizeRows( + List rows, + List batchRecords, + int startIndex, + String requestId) { + List responseRecords = new ArrayList<>(); + if (batchRecords == null || batchRecords.isEmpty()) { + // nothing to correlate against; fall back to one record per row + for (int position = 0; position < rows.size(); position++) { + responseRecords.add(new BulkTokenizeResponseRecord(startIndex + position, + rows.get(position).getValue().orElse(null), + buildTokenizeResponseTokens(rows.get(position), requestId))); + } + return responseRecords; + } + + int recordPosition = 0; + int rowsTakenByRecord = 0; + List tokens = new ArrayList<>(); + for (V1FlowTokenizeResponseObject row : rows) { + Object rowValue = row.getValue().orElse(null); + while (recordPosition < batchRecords.size() + && !acceptsRow(batchRecords.get(recordPosition), rowValue, rowsTakenByRecord)) { + responseRecords.add(new BulkTokenizeResponseRecord(startIndex + recordPosition, + batchRecords.get(recordPosition).getValue(), tokens)); + tokens = new ArrayList<>(); + rowsTakenByRecord = 0; + recordPosition++; + } + if (recordPosition >= batchRecords.size()) { + // more rows than the request can account for; keep them rather than drop them + responseRecords.add(new BulkTokenizeResponseRecord(startIndex + recordPosition, + rowValue, buildTokenizeResponseTokens(row, requestId))); + recordPosition++; + continue; + } + tokens.addAll(buildTokenizeResponseTokens(row, requestId)); + rowsTakenByRecord++; + } + // close the record in flight, then any records the response never mentioned + while (recordPosition < batchRecords.size()) { + responseRecords.add(new BulkTokenizeResponseRecord(startIndex + recordPosition, + batchRecords.get(recordPosition).getValue(), tokens)); + tokens = new ArrayList<>(); + recordPosition++; + } + return responseRecords; + } + + /** A record takes a row while the value still matches and it has room for another token group. */ + private static boolean acceptsRow(BulkTokenizeRequestRecord record, Object rowValue, int rowsTaken) { + List groups = record.getTokenGroupNames(); + int capacity = (groups == null || groups.isEmpty()) ? 1 : groups.size(); + if (rowsTaken >= capacity) { + return false; + } + // the API echoes the value back; a row that omits it can only belong to the record in flight + return rowValue == null || valuesMatch(record.getValue(), rowValue); + } + + /** + * Compares a requested value with the one echoed back. JSON round-tripping turns numbers into + * Double/Integer and objects into Maps, so fall back to string form when equals() disagrees. + */ + private static boolean valuesMatch(Object requested, Object echoed) { + if (Objects.equals(requested, echoed)) { + return true; + } + if (requested == null || echoed == null) { + return false; + } + return String.valueOf(requested).equals(String.valueOf(echoed)); + } + +} diff --git a/flowvault/src/main/java/com/skyflow/utils/validations/Validations.java b/flowvault/src/main/java/com/skyflow/utils/validations/Validations.java new file mode 100644 index 00000000..39565674 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/utils/validations/Validations.java @@ -0,0 +1,459 @@ +package com.skyflow.utils.validations; + +import com.skyflow.config.Credentials; +import com.skyflow.config.VaultConfig; +import com.skyflow.enums.InterfaceName; +import com.skyflow.generated.rest.types.FlowEnumUpdateType; +import com.skyflow.errors.ErrorCode; +import com.skyflow.errors.ErrorMessage; +import com.skyflow.errors.SkyflowException; +import com.skyflow.logs.ErrorLogs; +import com.skyflow.utils.Constants; +import com.skyflow.utils.Utils; +import com.skyflow.utils.logger.LogUtil; +import com.skyflow.vault.data.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class Validations extends BaseValidations { + private Validations() { + super(); + } + + public static void validateCredentials(Credentials credentials) throws SkyflowException { + int nonNullMembers = 0; + String path = credentials.getPath(); + String credentialsString = credentials.getCredentialsString(); + String token = credentials.getToken(); + String apiKey = credentials.getApiKey(); + Object context = credentials.getContext(); + ArrayList roles = credentials.getRoles(); + + if (path != null) nonNullMembers++; + if (credentialsString != null) nonNullMembers++; + if (token != null) nonNullMembers++; + if (apiKey != null) nonNullMembers++; + + if (nonNullMembers > 1) { + LogUtil.printErrorLog(ErrorLogs.MULTIPLE_TOKEN_GENERATION_MEANS_PASSED.getLog()); + throw new SkyflowException( + ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.MultipleTokenGenerationMeansPassed.getMessage() + ); + } else if (nonNullMembers < 1) { + LogUtil.printErrorLog(ErrorLogs.NO_TOKEN_GENERATION_MEANS_PASSED.getLog()); + throw new SkyflowException( + ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.NoTokenGenerationMeansPassed.getMessage() + ); + } else if (path != null && path.trim().isEmpty()) { + LogUtil.printErrorLog(ErrorLogs.EMPTY_CREDENTIALS_PATH.getLog()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyCredentialFilePath.getMessage()); + } else if (credentialsString != null && credentialsString.trim().isEmpty()) { + LogUtil.printErrorLog(ErrorLogs.EMPTY_CREDENTIALS_STRING.getLog()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyCredentialsString.getMessage()); + } else if (token != null && token.trim().isEmpty()) { + LogUtil.printErrorLog(ErrorLogs.EMPTY_TOKEN_VALUE.getLog()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyToken.getMessage()); + } else if (apiKey != null) { + if (apiKey.trim().isEmpty()) { + LogUtil.printErrorLog(ErrorLogs.EMPTY_API_KEY_VALUE.getLog()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyApikey.getMessage()); + } else { + Pattern pattern = Pattern.compile(Constants.API_KEY_REGEX); + Matcher matcher = pattern.matcher(apiKey); + if (!matcher.matches()) { + LogUtil.printErrorLog(ErrorLogs.INVALID_API_KEY.getLog()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.InvalidApikey.getMessage()); + } + } + } else if (roles != null) { + if (roles.isEmpty()) { + LogUtil.printErrorLog(ErrorLogs.EMPTY_ROLES.getLog()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyRoles.getMessage()); + } else { + for (int index = 0; index < roles.size(); index++) { + String role = roles.get(index); + if (role == null || role.trim().isEmpty()) { + LogUtil.printErrorLog(Utils.parameterizedString( + ErrorLogs.EMPTY_OR_NULL_ROLE_IN_ROLES.getLog(), Integer.toString(index) + )); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyRoleInRoles.getMessage()); + } + } + } + } + if (context != null) { + if (context instanceof String) { + String ctxStr = (String) context; + if (ctxStr.trim().isEmpty()) { + LogUtil.printErrorLog(ErrorLogs.EMPTY_OR_NULL_CONTEXT.getLog()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyContext.getMessage()); + } + } else if (context instanceof Map) { + Map ctxMap = (Map) context; + if (ctxMap.isEmpty()) { + LogUtil.printErrorLog(ErrorLogs.EMPTY_OR_NULL_CONTEXT.getLog()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyContext.getMessage()); + } + Pattern ctxKeyPattern = Pattern.compile(Constants.CONTEXT_KEY_REGEX); + for (Object key : ctxMap.keySet()) { + if (key == null || !ctxKeyPattern.matcher(key.toString()).matches()) { + String keyStr = key == null ? "null" : key.toString(); + LogUtil.printErrorLog(Utils.parameterizedString( + ErrorLogs.INVALID_CONTEXT_MAP_KEY.getLog(), keyStr)); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), + Utils.parameterizedString(ErrorMessage.InvalidContextMapKey.getMessage(), keyStr)); + } + } + } else { + LogUtil.printErrorLog(ErrorLogs.INVALID_CONTEXT_TYPE.getLog()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.InvalidContextType.getMessage()); + } + } + } + + + public static void validateVaultConfiguration(VaultConfig vaultConfig) throws SkyflowException { + String vaultId = vaultConfig.getVaultId(); + String clusterId = vaultConfig.getClusterId(); + String vaultUrl = vaultConfig.getVaultUrl(); + Credentials credentials = vaultConfig.getCredentials(); + + if (vaultId == null) { + LogUtil.printErrorLog(ErrorLogs.VAULT_ID_IS_REQUIRED.getLog()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.InvalidVaultId.getMessage()); + } else if (vaultId.trim().isEmpty()) { + LogUtil.printErrorLog(ErrorLogs.EMPTY_VAULT_ID.getLog()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyVaultId.getMessage()); + } else if (credentials != null) { + validateCredentials(credentials); + } + + if (vaultUrl != null) { + if (vaultUrl.trim().isEmpty()) { + LogUtil.printErrorLog(ErrorLogs.EMPTY_VAULT_URL.getLog()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyVaultUrl.getMessage()); + } else if (!Utils.isValidUrl(vaultUrl)) { + LogUtil.printErrorLog(ErrorLogs.INVALID_VAULT_URL_FORMAT.getLog()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.InvalidVaultUrlFormat.getMessage()); + } + } else if (Utils.getEnvVaultUrl() == null) { + if (clusterId == null) { + LogUtil.printErrorLog(ErrorLogs.EITHER_VAULT_URL_OR_CLUSTER_ID_REQUIRED.getLog()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EitherVaultUrlOrClusterIdRequired.getMessage()); + } else if (clusterId.trim().isEmpty()) { + LogUtil.printErrorLog(ErrorLogs.EMPTY_CLUSTER_ID.getLog()); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyClusterId.getMessage()); + } + } + } + + + public static void validateInsertRequest(InsertRequest insertRequest) throws SkyflowException { + if (insertRequest == null) { + LogUtil.printErrorLog(Utils.parameterizedString( + ErrorLogs.INSERT_REQUEST_NULL.getLog(), InterfaceName.INSERT.getName() + )); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.InsertRequestNull.getMessage()); + } + List records = insertRequest.getRecords(); + if (records == null) { + LogUtil.printErrorLog(Utils.parameterizedString( + ErrorLogs.RECORDS_IS_REQUIRED.getLog(), InterfaceName.INSERT.getName() + )); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.RecordsKeyError.getMessage()); + } else if (records.isEmpty()) { + LogUtil.printErrorLog(Utils.parameterizedString( + ErrorLogs.EMPTY_RECORDS.getLog(), InterfaceName.INSERT.getName() + )); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyRecords.getMessage()); + } + + for (InsertRequestRecord record : records) { + if (record == null) { + LogUtil.printErrorLog(Utils.parameterizedString( + ErrorLogs.INVALID_RECORD.getLog(), InterfaceName.INSERT.getName() + )); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.InvalidRecord.getMessage()); + } + validateUpsertOptions(record.getUpsert()); + } + validateUpsertOptions(insertRequest.getUpsert()); + validateTableAndUpsertPlacement(insertRequest, records); + + for (InsertRequestRecord record : records) { + if (record.getData() != null) { + for (String key : record.getData().keySet()) { + if (key == null || key.trim().isEmpty()) { + LogUtil.printErrorLog(Utils.parameterizedString( + ErrorLogs.EMPTY_OR_NULL_KEY_IN_VALUES.getLog(), InterfaceName.INSERT.getName() + )); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyKeyInRecords.getMessage()); + } else { + Object value = record.getData().get(key); + if (value == null || value.toString().trim().isEmpty()) { + LogUtil.printErrorLog(Utils.parameterizedString( + ErrorLogs.EMPTY_OR_NULL_VALUE_IN_VALUES.getLog(), + InterfaceName.INSERT.getName(), key + )); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyValueInValues.getMessage()); + } + } + } + } + } + } + + /** + * Table name must live at exactly one level — either on the request, or on every record. + * Upsert is optional, but wherever it is supplied it must sit at the same level as the + * table name; it need not appear on every record. + */ + private static void validateTableAndUpsertPlacement( + InsertRequest insertRequest, List records) throws SkyflowException { + boolean tableAtRequest = hasText(insertRequest.getTableName()); + boolean upsertAtRequest = insertRequest.getUpsert() != null; + + int recordsWithTable = 0; + boolean upsertAtRecords = false; + for (InsertRequestRecord record : records) { + if (hasText(record.getTableName())) recordsWithTable++; + if (record.getUpsert() != null) upsertAtRecords = true; + } + boolean tableAtRecords = recordsWithTable > 0; + + // ── table name: exactly one level ──────────────────────────────────── + if (tableAtRequest && tableAtRecords) { + LogUtil.printErrorLog(Utils.parameterizedString( + ErrorLogs.TABLE_SPECIFIED_AT_BOTH_PLACE.getLog(), InterfaceName.INSERT.getName())); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), + ErrorMessage.TableSpecifiedInRequestAndRecordObject.getMessage()); + } + if (!tableAtRequest && recordsWithTable != records.size()) { + LogUtil.printErrorLog(Utils.parameterizedString( + ErrorLogs.TABLE_NOT_SPECIFIED_AT_BOTH_PLACE.getLog(), InterfaceName.INSERT.getName())); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), + ErrorMessage.TableNotSpecifiedInRequestAndRecordObject.getMessage()); + } + + // ── upsert (optional) must match the table name's level ────────────── + if (upsertAtRecords && !tableAtRecords) { + LogUtil.printErrorLog(Utils.parameterizedString( + ErrorLogs.UPSERT_TABLE_REQUEST_AT_RECORD_LEVEL.getLog(), InterfaceName.INSERT.getName())); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), + ErrorMessage.UpsertTableRequestAtRecordLevel.getMessage()); + } + if (upsertAtRequest && !tableAtRequest) { + LogUtil.printErrorLog(Utils.parameterizedString( + ErrorLogs.UPSERT_TABLE_REQUEST_AT_REQUEST_LEVEL.getLog(), InterfaceName.INSERT.getName())); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), + ErrorMessage.UpsertTableRequestAtRequestLevel.getMessage()); + } + } + + private static boolean hasText(String value) { + return value != null && !value.trim().isEmpty(); + } + + private static void validateUpsertOptions(UpsertOptions upsert) throws SkyflowException { + if (upsert == null) { + return; + } + if (upsert.getUniqueColumns() == null || upsert.getUniqueColumns().isEmpty()) { + LogUtil.printErrorLog(Utils.parameterizedString( + ErrorLogs.EMPTY_UPSERT_VALUES.getLog(), InterfaceName.INSERT.getName() + )); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyUpsertValues.getMessage()); + } + // updateType is a free-form String on the request, but only the wire enum's values reach + // the wire. Reject anything else here rather than silently dropping it during mapping. + String updateType = upsert.getUpdateType(); + if (updateType != null && !isKnownUpdateType(updateType)) { + LogUtil.printErrorLog(Utils.parameterizedString( + ErrorLogs.INVALID_UPSERT_UPDATE_TYPE.getLog(), InterfaceName.INSERT.getName() + )); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), + ErrorMessage.InvalidUpsertUpdateType.getMessage()); + } + } + + private static boolean isKnownUpdateType(String updateType) { + for (FlowEnumUpdateType type : FlowEnumUpdateType.values()) { + if (type.toString().equalsIgnoreCase(updateType)) { + return true; + } + } + return false; + } + + public static void validateDetokenizeRequest(DetokenizeRequest request) throws SkyflowException { + if (request == null) { + LogUtil.printErrorLog(Utils.parameterizedString( + ErrorLogs.DETOKENIZE_REQUEST_NULL.getLog(), InterfaceName.DETOKENIZE.getName() + )); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.DetokenizeRequestNull.getMessage()); + } + List tokens = request.getTokens(); + if (tokens == null || tokens.isEmpty()) { + LogUtil.printErrorLog(Utils.parameterizedString( + ErrorLogs.EMPTY_DETOKENIZE_DATA.getLog(), InterfaceName.DETOKENIZE.getName() + )); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyDetokenizeData.getMessage()); + } + + for (int index = 0; index < tokens.size(); index++) { + String token = tokens.get(index); + if (token == null || token.trim().isEmpty()) { + LogUtil.printErrorLog(Utils.parameterizedString( + ErrorLogs.EMPTY_OR_NULL_TOKEN_IN_DETOKENIZE_DATA.getLog(), + InterfaceName.DETOKENIZE.getName(), + String.valueOf(index))); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyTokenInDetokenizeData.getMessage()); + } + } + + List groupRedactions = request.getTokenGroupRedactions(); + if (groupRedactions != null && !groupRedactions.isEmpty()) { + for (TokenGroupRedactions group : groupRedactions) { + if (group == null) { + LogUtil.printErrorLog(Utils.parameterizedString(ErrorLogs.NULL_TOKEN_REDACTION_GROUP_OBJECT.getLog(), InterfaceName.DETOKENIZE.getName())); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.NullTokenGroupRedactions.getMessage()); + } + String groupName = group.getTokenGroupName(); + String redaction = group.getRedaction(); + if (groupName == null || groupName.trim().isEmpty()) { + LogUtil.printErrorLog(Utils.parameterizedString(ErrorLogs.NULL_TOKEN_GROUP_NAME_IN_TOKEN_GROUP.getLog(), InterfaceName.DETOKENIZE.getName())); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.NullTokenGroupNameInTokenGroup.getMessage()); + } + if (redaction == null || redaction.trim().isEmpty()) { + LogUtil.printErrorLog(Utils.parameterizedString(ErrorLogs.EMPTY_OR_NULL_REDACTION_IN_TOKEN_GROUP.getLog(), InterfaceName.DETOKENIZE.getName())); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.NullRedactionInTokenGroup.getMessage()); + } + } + } + + } + + // ── Bulk (batched/concurrent) request validations ──────────────────────── + + // BulkInsertRequest is an InsertRequest with no extra state, so the field rules are identical. + // The one addition: records must be BulkInsertRequestRecord, since BulkInsertResponse hands + // them back as such from getRecordsToRetry(). `records` is typed to the parent (it is + // inherited), so this is enforced here rather than by the compiler. + // The service accepts at most Constants.MAX_BULK_DATA_SIZE items per bulk call; batching + // splits the payload but does not lift that ceiling. + private static void validateBulkDataSize( + int size, ErrorLogs log, ErrorMessage message, InterfaceName interfaceName) throws SkyflowException { + if (size > Constants.MAX_BULK_DATA_SIZE) { + LogUtil.printErrorLog(Utils.parameterizedString(log.getLog(), interfaceName.getName())); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), message.getMessage()); + } + } + + public static void validateBulkInsertRequest(BulkInsertRequest insertRequest) throws SkyflowException { + validateInsertRequest(insertRequest); + validateBulkDataSize(insertRequest.getRecords().size(), ErrorLogs.RECORD_SIZE_EXCEED, + ErrorMessage.RecordSizeExceedError, InterfaceName.INSERT); + + for (InsertRequestRecord record : insertRequest.getRecords()) { + if (!(record instanceof BulkInsertRequestRecord)) { + LogUtil.printErrorLog(Utils.parameterizedString( + ErrorLogs.INVALID_RECORD.getLog(), InterfaceName.INSERT.getName() + )); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.InvalidRecord.getMessage()); + } + } + } + + // BulkDetokenizeRequest is a DetokenizeRequest with no extra state, so the rules are identical. + public static void validateBulkDetokenizeRequest(BulkDetokenizeRequest request) throws SkyflowException { + validateDetokenizeRequest(request); + validateBulkDataSize(request.getTokens().size(), ErrorLogs.TOKENS_SIZE_EXCEED, + ErrorMessage.TokensSizeExceedError, InterfaceName.DETOKENIZE); + } + + public static void validateBulkDeleteTokensRequest(BulkDeleteTokensRequest request) throws SkyflowException { + if (request == null) { + LogUtil.printErrorLog(Utils.parameterizedString( + ErrorLogs.DELETE_TOKENS_REQUEST_NULL.getLog(), InterfaceName.DELETE.getName() + )); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.DeleteTokensRequestNull.getMessage()); + } + List tokens = request.getTokens(); + if (tokens == null || tokens.isEmpty()) { + LogUtil.printErrorLog(Utils.parameterizedString( + ErrorLogs.EMPTY_DELETE_TOKENS_DATA.getLog(), InterfaceName.DELETE.getName() + )); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyDeleteTokensData.getMessage()); + } + validateBulkDataSize(tokens.size(), ErrorLogs.DELETE_TOKENS_SIZE_EXCEED, + ErrorMessage.DeleteTokensSizeExceedError, InterfaceName.DELETE); + + for (int index = 0; index < tokens.size(); index++) { + String token = tokens.get(index); + if (token == null || token.trim().isEmpty()) { + LogUtil.printErrorLog(Utils.parameterizedString( + ErrorLogs.EMPTY_OR_NULL_TOKEN_IN_DELETE_TOKENS_DATA.getLog(), + InterfaceName.DELETE.getName(), + String.valueOf(index))); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyTokenInDeleteTokensData.getMessage()); + } + } + } + + public static void validateBulkTokenizeRequest(BulkTokenizeRequest request) throws SkyflowException { + if (request == null) { + LogUtil.printErrorLog(Utils.parameterizedString( + ErrorLogs.TOKENIZE_REQUEST_NULL.getLog(), InterfaceName.TOKENIZE.getName() + )); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.TokenizeRequestNull.getMessage()); + } + List records = request.getRecords(); + if (records == null || records.isEmpty()) { + LogUtil.printErrorLog(Utils.parameterizedString( + ErrorLogs.EMPTY_TOKENIZE_DATA.getLog(), InterfaceName.TOKENIZE.getName() + )); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyTokenizeData.getMessage()); + } + validateBulkDataSize(records.size(), ErrorLogs.TOKENIZE_DATA_SIZE_EXCEED, + ErrorMessage.TokenizeDataSizeExceedError, InterfaceName.TOKENIZE); + + for (int i = 0; i < records.size(); i++) { + BulkTokenizeRequestRecord record = records.get(i); + if (record == null) { + LogUtil.printErrorLog(Utils.parameterizedString( + ErrorLogs.TOKENIZE_RECORD_NULL.getLog(), InterfaceName.TOKENIZE.getName() + )); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.TokenizeRecordNull.getMessage()); + } + Object value = record.getValue(); + boolean isInvalid = value == null + || (value instanceof String && ((String) value).trim().isEmpty()); + if (isInvalid) { + LogUtil.printErrorLog(Utils.parameterizedString( + ErrorLogs.EMPTY_VALUE_IN_TOKENIZE_RECORD.getLog(), InterfaceName.TOKENIZE.getName() + )); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyValueInTokenizeRecord.getMessage()); + } + List tokenGroupNames = record.getTokenGroupNames(); + if (tokenGroupNames != null) { + for (int j = 0; j < tokenGroupNames.size(); j++) { + String groupName = tokenGroupNames.get(j); + if (groupName == null || groupName.trim().isEmpty()) { + LogUtil.printErrorLog(Utils.parameterizedString( + ErrorLogs.EMPTY_TOKEN_GROUP_NAME_IN_TOKENIZE_RECORD.getLog(), + InterfaceName.TOKENIZE.getName(), + String.valueOf(j) + )); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyTokenGroupNameInTokenizeRecord.getMessage()); + } + } + } + } + } + +} diff --git a/flowvault/src/main/java/com/skyflow/vault/controller/VaultController.java b/flowvault/src/main/java/com/skyflow/vault/controller/VaultController.java new file mode 100644 index 00000000..d405e1b4 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/controller/VaultController.java @@ -0,0 +1,959 @@ +package com.skyflow.vault.controller; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.function.Function; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; +import com.skyflow.VaultClient; +import com.skyflow.config.Credentials; +import com.skyflow.config.VaultConfig; +import com.skyflow.errors.SkyflowException; +import com.skyflow.generated.rest.core.ApiClientApiException; +import com.skyflow.generated.rest.core.ApiClientHttpResponse; +import com.skyflow.generated.rest.core.RequestOptions; +import com.skyflow.generated.rest.resources.flowservice.requests.V1InsertRequest; +import com.skyflow.generated.rest.types.V1FlowDeleteTokenResponse; +import com.skyflow.generated.rest.types.V1FlowTokenizeResponse; +import com.skyflow.generated.rest.types.V1InsertRecordData; +import com.skyflow.generated.rest.types.V1Upsert; +import com.skyflow.generated.rest.types.V1InsertResponse; +import com.skyflow.logs.ErrorLogs; +import com.skyflow.logs.InfoLogs; +import com.skyflow.logs.WarningLogs; +import com.skyflow.utils.Constants; +import com.skyflow.utils.Utils; +import com.skyflow.utils.logger.LogUtil; +import com.skyflow.utils.validations.Validations; +import com.skyflow.vault.data.BulkDeleteTokensOptions; +import com.skyflow.vault.data.BulkTokenizeOptions; +import com.skyflow.vault.data.BulkDeleteTokensRequest; +import com.skyflow.vault.data.BulkDeleteTokensResponseRecord; +import com.skyflow.vault.data.BulkTokenizeRequestRecord; +import com.skyflow.vault.data.BulkTokenizeResponseRecord; +import com.skyflow.vault.data.TokenizeResponseToken; +import com.skyflow.vault.data.BulkDeleteTokensResponse; +import com.skyflow.vault.data.BulkDetokenizeRequest; +import com.skyflow.vault.data.BulkDetokenizeResponse; +import com.skyflow.vault.data.BulkDetokenizeResponseRecord; +import com.skyflow.vault.data.BulkInsertRequest; +import com.skyflow.vault.data.BulkInsertResponse; +import com.skyflow.vault.data.BulkInsertResponseRecord; +import com.skyflow.vault.data.BulkTokenizeRequest; +import com.skyflow.vault.data.BulkTokenizeResponse; +import com.skyflow.vault.data.BulkDetokenizeOptions; +import com.skyflow.vault.data.BulkInsertOptions; +import com.skyflow.vault.data.DeleteTokensOptions; +import com.skyflow.vault.data.ErrorRecord; +import com.skyflow.vault.data.InsertRequestRecord; +import com.skyflow.vault.data.RequestContext; +import com.skyflow.vault.data.RequestInterceptor; +import com.skyflow.vault.data.TokenizeOptions; + +import io.github.cdimascio.dotenv.Dotenv; +import io.github.cdimascio.dotenv.DotenvException; + +public final class VaultController extends VaultClient { + private static final Gson gson = new GsonBuilder().serializeNulls().create(); + private JsonObject metrics = Utils.getMetrics(); + + public VaultController(VaultConfig vaultConfig, Credentials credentials) throws SkyflowException { + super(vaultConfig, credentials); + } + + /** + * Immutable per-call batch size / concurrency limit. Computed fresh on every bulk call + * instead of being stored on instance fields, since a VaultController instance is cached + * and reused (see Skyflow#vaultClientsMap) and may be invoked concurrently from multiple + * threads. + */ + private static final class BatchConfig { + final int batchSize; + final int concurrencyLimit; + + BatchConfig(int batchSize, int concurrencyLimit) { + this.batchSize = batchSize; + this.concurrencyLimit = concurrencyLimit; + } + } + + private RequestOptions buildRequestOptions(RequestContext context) { + RequestOptions.Builder builder = RequestOptions.builder() + .addHeader(Constants.SDK_METRICS_HEADER_KEY, metrics.toString()); + context.getHeaders().forEach((k, v) -> builder.addHeader(k.toString(), v)); + return builder.build(); + } + + // ── Bulk Insert ─────────────────────────────────────────────────────────── + + public BulkInsertResponse bulkInsert(BulkInsertRequest insertRequest) throws SkyflowException { + return bulkInsert(insertRequest, null); + } + + public BulkInsertResponse bulkInsert(BulkInsertRequest insertRequest, BulkInsertOptions options) throws SkyflowException { + LogUtil.printInfoLog(InfoLogs.INSERT_TRIGGERED.getLog()); + try { + LogUtil.printInfoLog(InfoLogs.VALIDATE_INSERT_REQUEST.getLog()); + Validations.validateBulkInsertRequest(insertRequest); + BatchConfig cfg = configureInsertConcurrencyAndBatchSize(insertRequest.getRecords().size()); + + setBearerToken(); + V1InsertRequest request = Utils.getBulkInsertRequestBody(insertRequest, this.getVaultConfig()); + RequestInterceptor interceptor = options != null ? options.getInterceptor() : null; + return this.processBulkInsertSync(request, insertRequest.getRecords(), interceptor, cfg); + } catch (ApiClientApiException e) { + String bodyString = gson.toJson(e.body()); + LogUtil.printErrorLog(ErrorLogs.INSERT_RECORDS_REJECTED.getLog()); + throw new SkyflowException(e.statusCode(), e, e.headers(), bodyString); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + LogUtil.printErrorLog(ErrorLogs.INSERT_RECORDS_REJECTED.getLog()); + throw new SkyflowException(e.getMessage()); + } catch (ExecutionException e) { + LogUtil.printErrorLog(ErrorLogs.INSERT_RECORDS_REJECTED.getLog()); + Throwable cause = e.getCause(); + throw new SkyflowException(cause != null && cause.getMessage() != null ? cause.getMessage() : e.getMessage()); + } + } + + public CompletableFuture bulkInsertAsync(BulkInsertRequest insertRequest) throws SkyflowException { + return bulkInsertAsync(insertRequest, null); + } + + public CompletableFuture bulkInsertAsync(BulkInsertRequest insertRequest, BulkInsertOptions options) throws SkyflowException { + LogUtil.printInfoLog(InfoLogs.INSERT_TRIGGERED.getLog()); + try { + LogUtil.printInfoLog(InfoLogs.VALIDATE_INSERT_REQUEST.getLog()); + Validations.validateBulkInsertRequest(insertRequest); + BatchConfig cfg = configureInsertConcurrencyAndBatchSize(insertRequest.getRecords().size()); + + setBearerToken(); + V1InsertRequest request = Utils.getBulkInsertRequestBody(insertRequest, this.getVaultConfig()); + RequestInterceptor interceptor = options != null ? options.getInterceptor() : null; + List> futures = this.insertBatchFutures(request, interceptor, cfg); + + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) + .thenApply(v -> { + List records = new ArrayList<>(); + + for (CompletableFuture future : futures) { + BulkInsertResponse futureResponse = future.join(); + if (futureResponse != null && futureResponse.getRecords() != null) { + records.addAll(futureResponse.getRecords()); + } + } + + return new BulkInsertResponse(records, insertRequest.getRecords()); + }); + } catch (ApiClientApiException e) { + String bodyString = gson.toJson(e.body()); + LogUtil.printErrorLog(ErrorLogs.INSERT_RECORDS_REJECTED.getLog()); + throw new SkyflowException(e.statusCode(), e, e.headers(), bodyString); + } + } + + // ── Bulk Detokenize ─────────────────────────────────────────────────────── + + public BulkDetokenizeResponse bulkDetokenize(BulkDetokenizeRequest detokenizeRequest) throws SkyflowException { + return bulkDetokenize(detokenizeRequest, null); + } + + public BulkDetokenizeResponse bulkDetokenize(BulkDetokenizeRequest detokenizeRequest, BulkDetokenizeOptions options) throws SkyflowException { + LogUtil.printInfoLog(InfoLogs.DETOKENIZE_TRIGGERED.getLog()); + try { + LogUtil.printInfoLog(InfoLogs.VALIDATE_DETOKENIZE_REQUEST.getLog()); + Validations.validateBulkDetokenizeRequest(detokenizeRequest); + BatchConfig cfg = configureDetokenizeConcurrencyAndBatchSize(detokenizeRequest.getTokens().size()); + setBearerToken(); + com.skyflow.generated.rest.resources.flowservice.requests.V1FlowDetokenizeRequest request = + Utils.getBulkDetokenizeRequestBody(detokenizeRequest, this.getVaultConfig().getVaultId()); + RequestInterceptor interceptor = options != null ? options.getInterceptor() : null; + return this.processBulkDetokenizeSync(request, detokenizeRequest.getTokens(), interceptor, cfg); + } catch (ApiClientApiException e) { + String bodyString = gson.toJson(e.body()); + LogUtil.printErrorLog(ErrorLogs.DETOKENIZE_REQUEST_REJECTED.getLog()); + throw new SkyflowException(e.statusCode(), e, e.headers(), bodyString); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new SkyflowException(e.getMessage()); + } catch (ExecutionException e) { + throw new SkyflowException(e.getMessage()); + } + } + + public CompletableFuture bulkDetokenizeAsync(BulkDetokenizeRequest detokenizeRequest) throws SkyflowException { + return bulkDetokenizeAsync(detokenizeRequest, null); + } + + public CompletableFuture bulkDetokenizeAsync(BulkDetokenizeRequest detokenizeRequest, BulkDetokenizeOptions options) throws SkyflowException { + LogUtil.printInfoLog(InfoLogs.DETOKENIZE_TRIGGERED.getLog()); + ExecutorService executor = null; + try { + LogUtil.printInfoLog(InfoLogs.VALIDATE_DETOKENIZE_REQUEST.getLog()); + Validations.validateBulkDetokenizeRequest(detokenizeRequest); + BatchConfig cfg = configureDetokenizeConcurrencyAndBatchSize(detokenizeRequest.getTokens().size()); + setBearerToken(); + com.skyflow.generated.rest.resources.flowservice.requests.V1FlowDetokenizeRequest request = + Utils.getBulkDetokenizeRequestBody(detokenizeRequest, this.getVaultConfig().getVaultId()); + RequestInterceptor interceptor = options != null ? options.getInterceptor() : null; + + LogUtil.printInfoLog(InfoLogs.PROCESSING_BATCHES.getLog()); + + List records = new ArrayList<>(); + + List batches = + Utils.createBulkDetokenizeBatches(request, cfg.batchSize); + + executor = Executors.newFixedThreadPool(cfg.concurrencyLimit); + List> futures = this.detokenizeBatchFutures(executor, batches, interceptor, cfg.batchSize); + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) + .thenApply(v -> { + for (CompletableFuture future : futures) { + BulkDetokenizeResponse futureResponse = future.join(); + if (futureResponse != null && futureResponse.getRecords() != null) { + records.addAll(futureResponse.getRecords()); + } + } + LogUtil.printInfoLog(InfoLogs.DETOKENIZE_REQUEST_RESOLVED.getLog()); + return new BulkDetokenizeResponse(records, detokenizeRequest.getTokens()); + }); + } catch (ApiClientApiException e) { + String bodyString = gson.toJson(e.body()); + LogUtil.printErrorLog(ErrorLogs.DETOKENIZE_REQUEST_REJECTED.getLog()); + throw new SkyflowException(e.statusCode(), e, e.headers(), bodyString); + } catch (SkyflowException e) { + LogUtil.printErrorLog(ErrorLogs.DETOKENIZE_REQUEST_REJECTED.getLog()); + throw e; + } catch (Exception e) { + LogUtil.printErrorLog(ErrorLogs.DETOKENIZE_REQUEST_REJECTED.getLog()); + throw new SkyflowException(e.getMessage()); + } finally { + if (executor != null) executor.shutdown(); + } + } + + // ── Bulk Delete Tokens ──────────────────────────────────────────────────── + + public BulkDeleteTokensResponse bulkDeleteTokens(BulkDeleteTokensRequest deleteTokensRequest) throws SkyflowException { + return bulkDeleteTokens(deleteTokensRequest, null); + } + + public BulkDeleteTokensResponse bulkDeleteTokens(BulkDeleteTokensRequest deleteTokensRequest, BulkDeleteTokensOptions options) throws SkyflowException { + LogUtil.printInfoLog(InfoLogs.DELETE_TOKENS_TRIGGERED.getLog()); + try { + LogUtil.printInfoLog(InfoLogs.VALIDATE_DELETE_TOKENS_REQUEST.getLog()); + Validations.validateBulkDeleteTokensRequest(deleteTokensRequest); + BatchConfig cfg = configureDeleteTokensConcurrencyAndBatchSize(deleteTokensRequest.getTokens().size()); + setBearerToken(); + com.skyflow.generated.rest.resources.flowservice.requests.V1FlowDeleteTokenRequest request = + Utils.getBulkDeleteTokensRequestBody(deleteTokensRequest, this.getVaultConfig().getVaultId()); + RequestInterceptor interceptor = options != null ? options.getInterceptor() : null; + return this.processBulkDeleteTokensSync(request, deleteTokensRequest.getTokens(), interceptor, cfg); + } catch (ApiClientApiException e) { + String bodyString = gson.toJson(e.body()); + LogUtil.printErrorLog(ErrorLogs.DELETE_REQUEST_REJECTED.getLog()); + throw new SkyflowException(e.statusCode(), e, e.headers(), bodyString); + } catch (ExecutionException | InterruptedException e) { + LogUtil.printErrorLog(ErrorLogs.DELETE_REQUEST_REJECTED.getLog()); + throw new SkyflowException(e.getMessage()); + } + } + + public CompletableFuture bulkDeleteTokensAsync(BulkDeleteTokensRequest deleteTokensRequest) throws SkyflowException { + return bulkDeleteTokensAsync(deleteTokensRequest, null); + } + + public CompletableFuture bulkDeleteTokensAsync(BulkDeleteTokensRequest deleteTokensRequest, BulkDeleteTokensOptions options) throws SkyflowException { + LogUtil.printInfoLog(InfoLogs.DELETE_TOKENS_TRIGGERED.getLog()); + ExecutorService executor = null; + try { + LogUtil.printInfoLog(InfoLogs.VALIDATE_DELETE_TOKENS_REQUEST.getLog()); + Validations.validateBulkDeleteTokensRequest(deleteTokensRequest); + BatchConfig cfg = configureDeleteTokensConcurrencyAndBatchSize(deleteTokensRequest.getTokens().size()); + setBearerToken(); + com.skyflow.generated.rest.resources.flowservice.requests.V1FlowDeleteTokenRequest request = + Utils.getBulkDeleteTokensRequestBody(deleteTokensRequest, this.getVaultConfig().getVaultId()); + RequestInterceptor interceptor = options != null ? options.getInterceptor() : null; + + LogUtil.printInfoLog(InfoLogs.PROCESSING_BATCHES.getLog()); + + List responseRecords = Collections.synchronizedList(new ArrayList<>()); + + List batches = + Utils.createBulkDeleteTokensBatches(request, cfg.batchSize); + + executor = Executors.newFixedThreadPool(cfg.concurrencyLimit); + List> futures = + this.deleteTokensBatchFutures(executor, batches, interceptor, cfg.batchSize); + + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) + .thenApply(v -> { + for (CompletableFuture future : futures) { + BulkDeleteTokensResponse futureResponse = future.join(); + if (futureResponse != null && futureResponse.getRecords() != null) { + responseRecords.addAll(futureResponse.getRecords()); + } + } + LogUtil.printInfoLog(InfoLogs.DELETE_TOKENS_REQUEST_RESOLVED.getLog()); + return new BulkDeleteTokensResponse( + sortByIndex(responseRecords), deleteTokensRequest.getTokens()); + }); + } catch (ApiClientApiException e) { + String bodyString = gson.toJson(e.body()); + LogUtil.printErrorLog(ErrorLogs.DELETE_REQUEST_REJECTED.getLog()); + throw new SkyflowException(e.statusCode(), e, e.headers(), bodyString); + } catch (SkyflowException e) { + LogUtil.printErrorLog(ErrorLogs.DELETE_REQUEST_REJECTED.getLog()); + throw e; + } catch (Exception e) { + LogUtil.printErrorLog(ErrorLogs.DELETE_REQUEST_REJECTED.getLog()); + throw new SkyflowException(e.getMessage()); + } finally { + if (executor != null) executor.shutdown(); + } + } + + // ── Bulk Tokenize ───────────────────────────────────────────────────────── + + public BulkTokenizeResponse bulkTokenize(BulkTokenizeRequest tokenizeRequest) throws SkyflowException { + return bulkTokenize(tokenizeRequest, null); + } + + public BulkTokenizeResponse bulkTokenize(BulkTokenizeRequest tokenizeRequest, BulkTokenizeOptions options) throws SkyflowException { + LogUtil.printInfoLog(InfoLogs.TOKENIZE_TRIGGERED.getLog()); + try { + LogUtil.printInfoLog(InfoLogs.VALIDATING_TOKENIZE_REQUEST.getLog()); + Validations.validateBulkTokenizeRequest(tokenizeRequest); + BatchConfig cfg = configureTokenizeConcurrencyAndBatchSize(tokenizeRequest.getRecords().size()); + setBearerToken(); + RequestInterceptor interceptor = options != null ? options.getInterceptor() : null; + return this.processBulkTokenizeSync(tokenizeRequest.getRecords(), interceptor, cfg); + } catch (ApiClientApiException e) { + String bodyString = gson.toJson(e.body()); + LogUtil.printErrorLog(ErrorLogs.TOKENIZE_REQUEST_REJECTED.getLog()); + throw new SkyflowException(e.statusCode(), e, e.headers(), bodyString); + } catch (SkyflowException e) { + LogUtil.printErrorLog(ErrorLogs.TOKENIZE_REQUEST_REJECTED.getLog()); + throw e; + } catch (ExecutionException | InterruptedException e) { + LogUtil.printErrorLog(ErrorLogs.TOKENIZE_REQUEST_REJECTED.getLog()); + throw new SkyflowException(e.getMessage()); + } + } + + public CompletableFuture bulkTokenizeAsync(BulkTokenizeRequest tokenizeRequest) throws SkyflowException { + return bulkTokenizeAsync(tokenizeRequest, null); + } + + public CompletableFuture bulkTokenizeAsync(BulkTokenizeRequest tokenizeRequest, BulkTokenizeOptions options) throws SkyflowException { + LogUtil.printInfoLog(InfoLogs.TOKENIZE_TRIGGERED.getLog()); + ExecutorService executor = null; + try { + LogUtil.printInfoLog(InfoLogs.VALIDATING_TOKENIZE_REQUEST.getLog()); + Validations.validateBulkTokenizeRequest(tokenizeRequest); + BatchConfig cfg = configureTokenizeConcurrencyAndBatchSize(tokenizeRequest.getRecords().size()); + setBearerToken(); + RequestInterceptor interceptor = options != null ? options.getInterceptor() : null; + + LogUtil.printInfoLog(InfoLogs.PROCESSING_BATCHES.getLog()); + + List responseRecords = Collections.synchronizedList(new ArrayList<>()); + + List> batches = + Utils.createBulkTokenizeBatches(tokenizeRequest.getRecords(), cfg.batchSize); + + executor = Executors.newFixedThreadPool(cfg.concurrencyLimit); + List> futures = + this.tokenizeBatchFutures(executor, batches, interceptor); + + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) + .thenApply(v -> { + for (CompletableFuture future : futures) { + BulkTokenizeResponse futureResponse = future.join(); + if (futureResponse != null && futureResponse.getRecords() != null) { + responseRecords.addAll(futureResponse.getRecords()); + } + } + LogUtil.printInfoLog(InfoLogs.TOKENIZE_REQUEST_RESOLVED.getLog()); + return new BulkTokenizeResponse( + sortTokenizeByIndex(responseRecords), tokenizeRequest.getRecords()); + }); + } catch (ApiClientApiException e) { + String bodyString = gson.toJson(e.body()); + LogUtil.printErrorLog(ErrorLogs.TOKENIZE_REQUEST_REJECTED.getLog()); + throw new SkyflowException(e.statusCode(), e, e.headers(), bodyString); + } catch (SkyflowException e) { + LogUtil.printErrorLog(ErrorLogs.TOKENIZE_REQUEST_REJECTED.getLog()); + throw e; + } catch (Exception e) { + LogUtil.printErrorLog(ErrorLogs.TOKENIZE_REQUEST_REJECTED.getLog()); + throw new SkyflowException(e.getMessage()); + } finally { + if (executor != null) executor.shutdown(); + } + } + + // ── Bulk private helpers ────────────────────────────────────────────────── + + private BulkDeleteTokensResponse processBulkDeleteTokensSync( + com.skyflow.generated.rest.resources.flowservice.requests.V1FlowDeleteTokenRequest deleteTokensRequest, + List originalTokens, + RequestInterceptor interceptor, + BatchConfig cfg + ) throws ExecutionException, InterruptedException, SkyflowException { + LogUtil.printInfoLog(InfoLogs.PROCESSING_BATCHES.getLog()); + List responseRecords = new ArrayList<>(); + ExecutorService executor = Executors.newFixedThreadPool(cfg.concurrencyLimit); + List batches = + Utils.createBulkDeleteTokensBatches(deleteTokensRequest, cfg.batchSize); + try { + List> futures = + this.deleteTokensBatchFutures(executor, batches, interceptor, cfg.batchSize); + try { + CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).get(); + } catch (Exception e) { + LogUtil.printErrorLog(ErrorLogs.DELETE_REQUEST_REJECTED.getLog()); + } + for (CompletableFuture future : futures) { + BulkDeleteTokensResponse futureResponse = future.get(); + if (futureResponse != null && futureResponse.getRecords() != null) { + responseRecords.addAll(futureResponse.getRecords()); + } + } + } catch (Exception e) { + LogUtil.printErrorLog(ErrorLogs.DELETE_REQUEST_REJECTED.getLog()); + throw new SkyflowException(e.getMessage()); + } finally { + executor.shutdown(); + } + BulkDeleteTokensResponse response = + new BulkDeleteTokensResponse(sortByIndex(responseRecords), originalTokens); + LogUtil.printInfoLog(InfoLogs.DELETE_TOKENS_REQUEST_RESOLVED.getLog()); + return response; + } + + /** + * Batches complete concurrently, so order the unified records by their position in the original + * request before handing them back to the caller. + */ + private static List sortByIndex(List records) { + List sorted = new ArrayList<>(records); + sorted.sort(Comparator.comparingInt(BulkDeleteTokensResponseRecord::getIndex)); + return sorted; + } + + private List> deleteTokensBatchFutures( + ExecutorService executor, + List batches, + RequestInterceptor interceptor, + int batchSize) { + List> futures = new ArrayList<>(); + if (batches == null) return futures; + for (int batchIndex = 0; batchIndex < batches.size(); batchIndex++) { + final int index = batchIndex; + com.skyflow.generated.rest.resources.flowservice.requests.V1FlowDeleteTokenRequest batch = batches.get(index); + RequestContext ctx = new RequestContext("DELETE_TOKENS"); + if (interceptor != null) interceptor.intercept(ctx); + CompletableFuture future = CompletableFuture + .supplyAsync(() -> processDeleteTokensBatch(batch, ctx), executor) + .handle((result, ex) -> { + if (ex != null) { + List batchErrors = + Utils.handleBulkDeleteTokensBatchException(ex, batch, index, batchSize); + return new BulkDeleteTokensResponse(batchErrors); + } + return Utils.formatBulkDeleteTokensResponse( + result.body(), batch, index, batchSize, result.headers()); + }); + futures.add(future); + } + return futures; + } + + private ApiClientHttpResponse processDeleteTokensBatch( + com.skyflow.generated.rest.resources.flowservice.requests.V1FlowDeleteTokenRequest batch, + RequestContext ctx) { + return this.getRecordsApi().withRawResponse().deletetoken(batch, buildRequestOptions(ctx)); + } + + /** + * Resolves a user-tunable batching setting: process environment first, then a {@code .env} file. + * Package-private and swappable purely so tests can drive batching and concurrency without + * mutating the JVM environment — deliberately not public, so it stays out of the frozen + * public API surface. + */ + static Function settingResolver = VaultController::resolveSettingFromEnvironment; + + private static String resolveSettingFromEnvironment(String key) { + String value = System.getenv(key); + if (value == null) { + try { + value = Dotenv.load().get(key); + } catch (DotenvException ignored) { + // no .env available — environment-only + } + } + return value; + } + + private BatchConfig configureDeleteTokensConcurrencyAndBatchSize(int totalRequests) { + int batchSize = Constants.DELETE_TOKENS_BATCH_SIZE; + int concurrencyLimit; + try { + String userProvidedBatchSize = settingResolver.apply("DELETE_TOKENS_BATCH_SIZE"); + String userProvidedConcurrencyLimit = settingResolver.apply("DELETE_TOKENS_CONCURRENCY_LIMIT"); + + if (userProvidedBatchSize != null) { + try { + int parsedBatchSize = Integer.parseInt(userProvidedBatchSize); + if (parsedBatchSize > Constants.MAX_DELETE_TOKENS_BATCH_SIZE) { + LogUtil.printWarningLog(WarningLogs.BATCH_SIZE_EXCEEDS_MAX_LIMIT.getLog()); + } + int maxBatchSize = Math.min(parsedBatchSize, Constants.MAX_DELETE_TOKENS_BATCH_SIZE); + if (maxBatchSize > 0) { + batchSize = maxBatchSize; + } else { + LogUtil.printWarningLog(WarningLogs.INVALID_BATCH_SIZE_PROVIDED.getLog()); + batchSize = Constants.DELETE_TOKENS_BATCH_SIZE; + } + } catch (NumberFormatException e) { + LogUtil.printWarningLog(WarningLogs.INVALID_BATCH_SIZE_PROVIDED.getLog()); + batchSize = Constants.DELETE_TOKENS_BATCH_SIZE; + } + } + + int maxConcurrencyNeeded = (totalRequests + batchSize - 1) / batchSize; + + if (userProvidedConcurrencyLimit != null) { + try { + int parsedConcurrencyLimit = Integer.parseInt(userProvidedConcurrencyLimit); + if (parsedConcurrencyLimit > Constants.MAX_DELETE_TOKENS_CONCURRENCY_LIMIT) { + LogUtil.printWarningLog(WarningLogs.CONCURRENCY_EXCEEDS_MAX_LIMIT.getLog()); + } + int maxConcurrencyLimit = Math.min(parsedConcurrencyLimit, Constants.MAX_DELETE_TOKENS_CONCURRENCY_LIMIT); + if (maxConcurrencyLimit > 0) { + concurrencyLimit = Math.min(maxConcurrencyLimit, maxConcurrencyNeeded); + } else { + LogUtil.printWarningLog(WarningLogs.INVALID_CONCURRENCY_LIMIT_PROVIDED.getLog()); + concurrencyLimit = Math.min(Constants.DELETE_TOKENS_CONCURRENCY_LIMIT, maxConcurrencyNeeded); + } + } catch (NumberFormatException e) { + LogUtil.printWarningLog(WarningLogs.INVALID_CONCURRENCY_LIMIT_PROVIDED.getLog()); + concurrencyLimit = Math.min(Constants.DELETE_TOKENS_CONCURRENCY_LIMIT, maxConcurrencyNeeded); + } + } else { + concurrencyLimit = Math.min(Constants.DELETE_TOKENS_CONCURRENCY_LIMIT, maxConcurrencyNeeded); + } + } catch (Exception e) { + batchSize = Constants.DELETE_TOKENS_BATCH_SIZE; + int maxConcurrencyNeeded = (totalRequests + batchSize - 1) / batchSize; + concurrencyLimit = Math.min(Constants.DELETE_TOKENS_CONCURRENCY_LIMIT, maxConcurrencyNeeded); + } + return new BatchConfig(batchSize, concurrencyLimit); + } + + private BulkTokenizeResponse processBulkTokenizeSync( + List originalRecords, + RequestInterceptor interceptor, + BatchConfig cfg + ) throws ExecutionException, InterruptedException, SkyflowException { + LogUtil.printInfoLog(InfoLogs.PROCESSING_BATCHES.getLog()); + List responseRecords = new ArrayList<>(); + ExecutorService executor = Executors.newFixedThreadPool(cfg.concurrencyLimit); + List> batches = + Utils.createBulkTokenizeBatches(originalRecords, cfg.batchSize); + try { + List> futures = + this.tokenizeBatchFutures(executor, batches, interceptor); + try { + CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); + } catch (Exception e) { + // individual batch errors are already captured + } + for (CompletableFuture future : futures) { + BulkTokenizeResponse futureResponse = future.get(); + if (futureResponse != null && futureResponse.getRecords() != null) { + responseRecords.addAll(futureResponse.getRecords()); + } + } + } catch (Exception e) { + LogUtil.printErrorLog(ErrorLogs.TOKENIZE_REQUEST_REJECTED.getLog()); + throw new SkyflowException(e.getMessage()); + } finally { + executor.shutdown(); + } + BulkTokenizeResponse response = + new BulkTokenizeResponse(sortTokenizeByIndex(responseRecords), originalRecords); + LogUtil.printInfoLog(InfoLogs.TOKENIZE_REQUEST_RESOLVED.getLog()); + return response; + } + + /** Batches complete concurrently; order results by the index the SDK assigned each record. */ + private static List sortTokenizeByIndex(List records) { + List sorted = new ArrayList<>(records); + sorted.sort(Comparator.comparingInt(BulkTokenizeResponseRecord::getIndex)); + return sorted; + } + + private List> tokenizeBatchFutures( + ExecutorService executor, + List> batches, + RequestInterceptor interceptor) { + List> futures = new ArrayList<>(); + if (batches == null) return futures; + // batches are contiguous but not uniformly sized - a batch is cut short when it would + // otherwise repeat a value - so track where each one starts rather than deriving it + int nextStartIndex = 0; + for (List batchRecords : batches) { + final int startIndex = nextStartIndex; + nextStartIndex += batchRecords.size(); + com.skyflow.generated.rest.resources.flowservice.requests.V1FlowTokenizeRequest batch = + Utils.getBulkTokenizeRequestBody(batchRecords, this.getVaultConfig().getVaultId()); + RequestContext ctx = new RequestContext("TOKENIZE"); + if (interceptor != null) interceptor.intercept(ctx); + CompletableFuture future = CompletableFuture + .supplyAsync(() -> processTokenizeBatch(batch, ctx), executor) + .handle((result, ex) -> { + if (ex != null) { + return new BulkTokenizeResponse(Utils.handleBulkTokenizeBatchException( + ex, batchRecords, startIndex)); + } + return Utils.formatBulkTokenizeResponse( + result.body(), batchRecords, startIndex, result.headers()); + }); + futures.add(future); + } + return futures; + } + + private ApiClientHttpResponse processTokenizeBatch( + com.skyflow.generated.rest.resources.flowservice.requests.V1FlowTokenizeRequest batch, + RequestContext ctx) { + return this.getRecordsApi().withRawResponse().tokenize(batch, buildRequestOptions(ctx)); + } + + private BatchConfig configureTokenizeConcurrencyAndBatchSize(int totalRequests) { + int batchSize = Constants.TOKENIZE_BATCH_SIZE; + int concurrencyLimit; + try { + String userProvidedBatchSize = settingResolver.apply("TOKENIZE_BATCH_SIZE"); + String userProvidedConcurrencyLimit = settingResolver.apply("TOKENIZE_CONCURRENCY_LIMIT"); + + if (userProvidedBatchSize != null) { + try { + int parsedBatchSize = Integer.parseInt(userProvidedBatchSize); + if (parsedBatchSize > Constants.MAX_TOKENIZE_BATCH_SIZE) { + LogUtil.printWarningLog(WarningLogs.BATCH_SIZE_EXCEEDS_MAX_LIMIT.getLog()); + } + int maxBatchSize = Math.min(parsedBatchSize, Constants.MAX_TOKENIZE_BATCH_SIZE); + if (maxBatchSize > 0) { + batchSize = maxBatchSize; + } else { + LogUtil.printWarningLog(WarningLogs.INVALID_BATCH_SIZE_PROVIDED.getLog()); + batchSize = Constants.TOKENIZE_BATCH_SIZE; + } + } catch (NumberFormatException e) { + LogUtil.printWarningLog(WarningLogs.INVALID_BATCH_SIZE_PROVIDED.getLog()); + batchSize = Constants.TOKENIZE_BATCH_SIZE; + } + } + + int maxConcurrencyNeeded = (totalRequests + batchSize - 1) / batchSize; + + if (userProvidedConcurrencyLimit != null) { + try { + int parsedConcurrencyLimit = Integer.parseInt(userProvidedConcurrencyLimit); + if (parsedConcurrencyLimit > Constants.MAX_TOKENIZE_CONCURRENCY_LIMIT) { + LogUtil.printWarningLog(WarningLogs.CONCURRENCY_EXCEEDS_MAX_LIMIT.getLog()); + } + int maxConcurrencyLimit = Math.min(parsedConcurrencyLimit, Constants.MAX_TOKENIZE_CONCURRENCY_LIMIT); + if (maxConcurrencyLimit > 0) { + concurrencyLimit = Math.min(maxConcurrencyLimit, maxConcurrencyNeeded); + } else { + LogUtil.printWarningLog(WarningLogs.INVALID_CONCURRENCY_LIMIT_PROVIDED.getLog()); + concurrencyLimit = Math.min(Constants.TOKENIZE_CONCURRENCY_LIMIT, maxConcurrencyNeeded); + } + } catch (NumberFormatException e) { + LogUtil.printWarningLog(WarningLogs.INVALID_CONCURRENCY_LIMIT_PROVIDED.getLog()); + concurrencyLimit = Math.min(Constants.TOKENIZE_CONCURRENCY_LIMIT, maxConcurrencyNeeded); + } + } else { + concurrencyLimit = Math.min(Constants.TOKENIZE_CONCURRENCY_LIMIT, maxConcurrencyNeeded); + } + } catch (Exception e) { + batchSize = Constants.TOKENIZE_BATCH_SIZE; + int maxConcurrencyNeeded = (totalRequests + batchSize - 1) / batchSize; + concurrencyLimit = Math.min(Constants.TOKENIZE_CONCURRENCY_LIMIT, maxConcurrencyNeeded); + } + return new BatchConfig(batchSize, concurrencyLimit); + } + + private BatchConfig configureDetokenizeConcurrencyAndBatchSize(int totalRequests) { + int batchSize = Constants.DETOKENIZE_BATCH_SIZE; + int concurrencyLimit; + try { + String userProvidedBatchSize = settingResolver.apply("DETOKENIZE_BATCH_SIZE"); + String userProvidedConcurrencyLimit = settingResolver.apply("DETOKENIZE_CONCURRENCY_LIMIT"); + + if (userProvidedBatchSize != null) { + try { + int parsedBatchSize = Integer.parseInt(userProvidedBatchSize); + if (parsedBatchSize > Constants.MAX_DETOKENIZE_BATCH_SIZE) { + LogUtil.printWarningLog(WarningLogs.BATCH_SIZE_EXCEEDS_MAX_LIMIT.getLog()); + } + int maxBatchSize = Math.min(parsedBatchSize, Constants.MAX_DETOKENIZE_BATCH_SIZE); + if (maxBatchSize > 0) { + batchSize = maxBatchSize; + } else { + LogUtil.printWarningLog(WarningLogs.INVALID_BATCH_SIZE_PROVIDED.getLog()); + batchSize = Constants.DETOKENIZE_BATCH_SIZE; + } + } catch (NumberFormatException e) { + LogUtil.printWarningLog(WarningLogs.INVALID_BATCH_SIZE_PROVIDED.getLog()); + batchSize = Constants.DETOKENIZE_BATCH_SIZE; + } + } + + int maxConcurrencyNeeded = (totalRequests + batchSize - 1) / batchSize; + + if (userProvidedConcurrencyLimit != null) { + try { + int parsedConcurrencyLimit = Integer.parseInt(userProvidedConcurrencyLimit); + if (parsedConcurrencyLimit > Constants.MAX_DETOKENIZE_CONCURRENCY_LIMIT) { + LogUtil.printWarningLog(WarningLogs.CONCURRENCY_EXCEEDS_MAX_LIMIT.getLog()); + } + int maxConcurrencyLimit = Math.min(parsedConcurrencyLimit, Constants.MAX_DETOKENIZE_CONCURRENCY_LIMIT); + + if (maxConcurrencyLimit > 0) { + concurrencyLimit = Math.min(maxConcurrencyLimit, maxConcurrencyNeeded); + } else { + LogUtil.printWarningLog(WarningLogs.INVALID_CONCURRENCY_LIMIT_PROVIDED.getLog()); + concurrencyLimit = Math.min(Constants.DETOKENIZE_CONCURRENCY_LIMIT, maxConcurrencyNeeded); + } + } catch (NumberFormatException e) { + LogUtil.printWarningLog(WarningLogs.INVALID_CONCURRENCY_LIMIT_PROVIDED.getLog()); + concurrencyLimit = Math.min(Constants.DETOKENIZE_CONCURRENCY_LIMIT, maxConcurrencyNeeded); + } + } else { + concurrencyLimit = Math.min(Constants.DETOKENIZE_CONCURRENCY_LIMIT, maxConcurrencyNeeded); + } + } catch (Exception e) { + batchSize = Constants.DETOKENIZE_BATCH_SIZE; + int maxConcurrencyNeeded = (totalRequests + batchSize - 1) / batchSize; + concurrencyLimit = Math.min(Constants.DETOKENIZE_CONCURRENCY_LIMIT, maxConcurrencyNeeded); + } + return new BatchConfig(batchSize, concurrencyLimit); + } + + private BulkInsertResponse processBulkInsertSync( + V1InsertRequest insertRequest, + List originalPayload, + RequestInterceptor interceptor, + BatchConfig cfg + ) throws ExecutionException, InterruptedException, SkyflowException { + LogUtil.printInfoLog(InfoLogs.PROCESSING_BATCHES.getLog()); + List records = new ArrayList<>(); + List> futures = this.insertBatchFutures(insertRequest, interceptor, cfg); + + try { + CompletableFuture allFutures = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); + try { + allFutures.join(); + } catch (Exception e) { + // individual batch errors are already captured + } + for (CompletableFuture future : futures) { + BulkInsertResponse futureResponse = future.get(); + if (futureResponse != null && futureResponse.getRecords() != null) { + records.addAll(futureResponse.getRecords()); + } + } + } catch (Exception e) { + LogUtil.printErrorLog(ErrorLogs.INSERT_RECORDS_REJECTED.getLog()); + throw new SkyflowException(e.getMessage()); + } + BulkInsertResponse response = new BulkInsertResponse(records, originalPayload); + LogUtil.printInfoLog(InfoLogs.INSERT_REQUEST_RESOLVED.getLog()); + return response; + } + + private BulkDetokenizeResponse processBulkDetokenizeSync( + com.skyflow.generated.rest.resources.flowservice.requests.V1FlowDetokenizeRequest detokenizeRequest, + List originalTokens, + RequestInterceptor interceptor, + BatchConfig cfg + ) throws ExecutionException, InterruptedException, SkyflowException { + LogUtil.printInfoLog(InfoLogs.PROCESSING_BATCHES.getLog()); + List records = new ArrayList<>(); + ExecutorService executor = Executors.newFixedThreadPool(cfg.concurrencyLimit); + List batches = + Utils.createBulkDetokenizeBatches(detokenizeRequest, cfg.batchSize); + try { + List> futures = this.detokenizeBatchFutures(executor, batches, interceptor, cfg.batchSize); + try { + CompletableFuture allFutures = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); + allFutures.join(); + } catch (Exception e) { + // individual batch errors are already captured + } + for (CompletableFuture future : futures) { + BulkDetokenizeResponse futureResponse = future.get(); + if (futureResponse != null && futureResponse.getRecords() != null) { + records.addAll(futureResponse.getRecords()); + } + } + } catch (Exception e) { + LogUtil.printErrorLog(ErrorLogs.DETOKENIZE_REQUEST_REJECTED.getLog()); + throw new SkyflowException(e.getMessage()); + } finally { + executor.shutdown(); + } + BulkDetokenizeResponse response = new BulkDetokenizeResponse(records, originalTokens); + LogUtil.printInfoLog(InfoLogs.DETOKENIZE_REQUEST_RESOLVED.getLog()); + return response; + } + + private List> detokenizeBatchFutures( + ExecutorService executor, + List batches, + RequestInterceptor interceptor, + int batchSize) { + List> futures = new ArrayList<>(); + for (int batchIndex = 0; batchIndex < batches.size(); batchIndex++) { + com.skyflow.generated.rest.resources.flowservice.requests.V1FlowDetokenizeRequest batch = batches.get(batchIndex); + int batchNumber = batchIndex; + RequestContext ctx = new RequestContext("DETOKENIZE"); + if (interceptor != null) interceptor.intercept(ctx); + CompletableFuture future = CompletableFuture + .supplyAsync(() -> processDetokenizeBatch(batch, ctx), executor) + .thenApply(response -> Utils.formatBulkDetokenizeResponse(response.body(), batchNumber, batchSize, response.headers())) + .exceptionally(ex -> new BulkDetokenizeResponse( + Utils.handleBulkDetokenizeBatchException(ex, batch, batchNumber, batchSize))); + futures.add(future); + } + return futures; + } + + private ApiClientHttpResponse processDetokenizeBatch( + com.skyflow.generated.rest.resources.flowservice.requests.V1FlowDetokenizeRequest batch, + RequestContext ctx) { + return this.getRecordsApi().withRawResponse().detokenize(batch, buildRequestOptions(ctx)); + } + + private List> insertBatchFutures( + V1InsertRequest insertRequest, + RequestInterceptor interceptor, + BatchConfig cfg) { + List records = insertRequest.getRecords().get(); + + ExecutorService executor = Executors.newFixedThreadPool(cfg.concurrencyLimit); + List> batches = Utils.createBulkInsertBatches(records, cfg.batchSize); + List> futures = new ArrayList<>(); + + try { + for (int batchIndex = 0; batchIndex < batches.size(); batchIndex++) { + List batch = batches.get(batchIndex); + int batchNumber = batchIndex; + RequestContext ctx = new RequestContext("INSERT"); + if (interceptor != null) interceptor.intercept(ctx); + CompletableFuture future = CompletableFuture + .supplyAsync(() -> insertBatch( + batch, + insertRequest.getTableName().isPresent() ? insertRequest.getTableName().get() : null, + insertRequest.getUpsert().isPresent() ? insertRequest.getUpsert().get() : null, + ctx), executor) + .thenApply(response -> Utils.formatBulkInsertResponse(response.body(), batchNumber, cfg.batchSize, response.headers())) + .exceptionally(ex -> new BulkInsertResponse( + Utils.handleBulkInsertBatchException(ex, batch, batchNumber, cfg.batchSize))); + futures.add(future); + } + } finally { + executor.shutdown(); + } + return futures; + } + + // tableName and upsert live on the envelope when the caller set them at the request level, and + // batching rebuilds the envelope per batch — so both have to be re-applied here or they are + // silently dropped for every batch after the body was built. + private ApiClientHttpResponse insertBatch(List batch, String tableName, + V1Upsert upsert, RequestContext ctx) { + V1InsertRequest.Builder req = V1InsertRequest.builder() + .vaultId(this.getVaultConfig().getVaultId()) + .records(batch); + if (tableName != null && !tableName.isEmpty()) { + req.tableName(tableName); + } + if (upsert != null) { + req.upsert(upsert); + } + V1InsertRequest request = req.build(); + return this.getRecordsApi().withRawResponse().insert(request, buildRequestOptions(ctx)); + } + + private BatchConfig configureInsertConcurrencyAndBatchSize(int totalRequests) { + int batchSize = Constants.INSERT_BATCH_SIZE; + int concurrencyLimit; + try { + String userProvidedBatchSize = settingResolver.apply("INSERT_BATCH_SIZE"); + String userProvidedConcurrencyLimit = settingResolver.apply("INSERT_CONCURRENCY_LIMIT"); + + if (userProvidedBatchSize != null) { + try { + int parsedBatchSize = Integer.parseInt(userProvidedBatchSize); + if (parsedBatchSize > Constants.MAX_INSERT_BATCH_SIZE) { + LogUtil.printWarningLog(WarningLogs.BATCH_SIZE_EXCEEDS_MAX_LIMIT.getLog()); + } + int maxBatchSize = Math.min(parsedBatchSize, Constants.MAX_INSERT_BATCH_SIZE); + if (maxBatchSize > 0) { + batchSize = maxBatchSize; + } else { + LogUtil.printWarningLog(WarningLogs.INVALID_BATCH_SIZE_PROVIDED.getLog()); + batchSize = Constants.INSERT_BATCH_SIZE; + } + } catch (NumberFormatException e) { + LogUtil.printWarningLog(WarningLogs.INVALID_BATCH_SIZE_PROVIDED.getLog()); + batchSize = Constants.INSERT_BATCH_SIZE; + } + } + + // Max no of threads required to run all batches concurrently at once + int maxConcurrencyNeeded = (totalRequests + batchSize - 1) / batchSize; + + if (userProvidedConcurrencyLimit != null) { + try { + int parsedConcurrencyLimit = Integer.parseInt(userProvidedConcurrencyLimit); + int maxConcurrencyLimit = Math.min(parsedConcurrencyLimit, Constants.MAX_INSERT_CONCURRENCY_LIMIT); + if (parsedConcurrencyLimit > Constants.MAX_INSERT_CONCURRENCY_LIMIT) { + LogUtil.printWarningLog(WarningLogs.CONCURRENCY_EXCEEDS_MAX_LIMIT.getLog()); + } + if (maxConcurrencyLimit > 0) { + concurrencyLimit = Math.min(maxConcurrencyLimit, maxConcurrencyNeeded); + } else { + LogUtil.printWarningLog(WarningLogs.INVALID_CONCURRENCY_LIMIT_PROVIDED.getLog()); + concurrencyLimit = Math.min(Constants.INSERT_CONCURRENCY_LIMIT, maxConcurrencyNeeded); + } + } catch (NumberFormatException e) { + LogUtil.printWarningLog(WarningLogs.INVALID_CONCURRENCY_LIMIT_PROVIDED.getLog()); + concurrencyLimit = Math.min(Constants.INSERT_CONCURRENCY_LIMIT, maxConcurrencyNeeded); + } + } else { + concurrencyLimit = Math.min(Constants.INSERT_CONCURRENCY_LIMIT, maxConcurrencyNeeded); + } + } catch (Exception e) { + batchSize = Constants.INSERT_BATCH_SIZE; + int maxConcurrencyNeeded = (totalRequests + batchSize - 1) / batchSize; + concurrencyLimit = Math.min(Constants.INSERT_CONCURRENCY_LIMIT, maxConcurrencyNeeded); + } + return new BatchConfig(batchSize, concurrencyLimit); + } + +} diff --git a/flowvault/src/main/java/com/skyflow/vault/data/BulkDeleteTokensOptions.java b/flowvault/src/main/java/com/skyflow/vault/data/BulkDeleteTokensOptions.java new file mode 100644 index 00000000..f8e212fd --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/data/BulkDeleteTokensOptions.java @@ -0,0 +1,34 @@ +package com.skyflow.vault.data; + +/** + * Per-call options for bulk delete tokens. + * + *

Adds nothing to {@link DeleteTokensOptions} today; it exists so the bulk interfaces have their own + * options type to grow into, matching the {@link BulkDeleteTokensRequest} / {@link DeleteTokensRequest} split. + */ +public final class BulkDeleteTokensOptions extends DeleteTokensOptions { + + private BulkDeleteTokensOptions(BulkDeleteTokensOptionsBuilder builder) { + super(builder); + } + + public static BulkDeleteTokensOptionsBuilder builder() { + return new BulkDeleteTokensOptionsBuilder(); + } + + public static final class BulkDeleteTokensOptionsBuilder extends Builder { + + private BulkDeleteTokensOptionsBuilder() {} + + @Override + public BulkDeleteTokensOptionsBuilder interceptor(RequestInterceptor interceptor) { + super.interceptor(interceptor); + return this; + } + + @Override + public BulkDeleteTokensOptions build() { + return new BulkDeleteTokensOptions(this); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/vault/data/BulkDeleteTokensRequest.java b/flowvault/src/main/java/com/skyflow/vault/data/BulkDeleteTokensRequest.java new file mode 100644 index 00000000..5d123922 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/data/BulkDeleteTokensRequest.java @@ -0,0 +1,30 @@ +package com.skyflow.vault.data; + +import java.util.List; + +public class BulkDeleteTokensRequest extends DeleteTokensRequest { + + private BulkDeleteTokensRequest(List tokens) { + super(tokens); + } + + public static BulkDeleteTokensRequestBuilder builder() { + return new BulkDeleteTokensRequestBuilder(); + } + + public static final class BulkDeleteTokensRequestBuilder extends DeleteTokensRequestBuilder { + + private BulkDeleteTokensRequestBuilder() {} + + @Override + public BulkDeleteTokensRequestBuilder tokens(List tokens) { + this.tokens = tokens; + return this; + } + + @Override + public BulkDeleteTokensRequest build() { + return new BulkDeleteTokensRequest(this.tokens); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/vault/data/BulkDeleteTokensResponse.java b/flowvault/src/main/java/com/skyflow/vault/data/BulkDeleteTokensResponse.java new file mode 100644 index 00000000..d4530145 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/data/BulkDeleteTokensResponse.java @@ -0,0 +1,91 @@ +package com.skyflow.vault.data; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.annotations.Expose; + +import java.util.ArrayList; +import java.util.List; + +public class BulkDeleteTokensResponse { + @Expose(serialize = true) + private DeleteTokensSummary summary; + + @Expose(serialize = true) + private List records; + + private List originalPayload; + private List tokensToRetry; + + public BulkDeleteTokensResponse(List records) { + this.records = records; + } + + public BulkDeleteTokensResponse(List records, List originalPayload) { + this.records = records; + this.originalPayload = originalPayload; + this.summary = buildSummary(this.records, this.originalPayload); + } + + private static DeleteTokensSummary buildSummary(List records, List originalPayload) { + int totalDeleted = 0; + int totalFailed = 0; + if (records != null) { + for (BulkDeleteTokensResponseRecord record : records) { + if (record.getError() == null) { + totalDeleted++; + } else { + totalFailed++; + } + } + } + int totalTokens = originalPayload != null ? originalPayload.size() : totalDeleted + totalFailed; + return new DeleteTokensSummary(totalTokens, totalDeleted, totalFailed); + } + + public DeleteTokensSummary getSummary() { + return summary; + } + + public List getRecords() { + return records; + } + + /** + * The tokens whose delete failed with a retryable status, ready to be resubmitted. + * + *

Retryable means a 5xx other than 529, matching the rule used by bulk insert and bulk + * detokenize. Tokens are read straight off the failed records, so a token is never dropped + * because of an index mismatch. + */ + public List getTokensToRetry() { + if (tokensToRetry == null) { + tokensToRetry = new ArrayList<>(); + if (records != null) { + for (BulkDeleteTokensResponseRecord record : records) { + if (isRetryable(record) && record.getToken() != null) { + tokensToRetry.add(record.getToken()); + } + } + } + } + return tokensToRetry; + } + + private static boolean isRetryable(BulkDeleteTokensResponseRecord record) { + Integer httpCode = record.getHttpCode(); + return record.getError() != null + && httpCode != null + && httpCode >= 500 && httpCode <= 599 + && httpCode != 529; + } + + @Override + public String toString() { + Gson gson = new GsonBuilder() + .excludeFieldsWithoutExposeAnnotation() + .serializeNulls() + .create(); + return gson.toJson(this); + } +} diff --git a/flowvault/src/main/java/com/skyflow/vault/data/BulkDeleteTokensResponseRecord.java b/flowvault/src/main/java/com/skyflow/vault/data/BulkDeleteTokensResponseRecord.java new file mode 100644 index 00000000..879733cd --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/data/BulkDeleteTokensResponseRecord.java @@ -0,0 +1,33 @@ +package com.skyflow.vault.data; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.annotations.Expose; + +/** + * A {@link DeleteTokensRecord} carrying the token's position in the original bulk request. + */ +public class BulkDeleteTokensResponseRecord extends DeleteTokensRecord { + @Expose(serialize = true) + private final int index; + + public BulkDeleteTokensResponseRecord(int index, String token, Integer httpCode, String error) { + this(index, token, httpCode, error, null); + } + + public BulkDeleteTokensResponseRecord(int index, String token, Integer httpCode, + String error, String requestId) { + super(token, httpCode, error, requestId); + this.index = index; + } + + public int getIndex() { + return index; + } + + @Override + public String toString() { + Gson gson = new GsonBuilder().serializeNulls().create(); + return gson.toJson(this); + } +} diff --git a/flowvault/src/main/java/com/skyflow/vault/data/BulkDetokenizeOptions.java b/flowvault/src/main/java/com/skyflow/vault/data/BulkDetokenizeOptions.java new file mode 100644 index 00000000..12d57ca5 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/data/BulkDetokenizeOptions.java @@ -0,0 +1,31 @@ +package com.skyflow.vault.data; + +// Bulk counterpart of DetokenizeOptions. Carries no extra state today; the interceptor +// field is inherited. +public class BulkDetokenizeOptions extends DetokenizeOptions { + + protected BulkDetokenizeOptions(Builder builder) { + super(builder); + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder extends DetokenizeOptions.Builder { + + private Builder() { + } + + @Override + public Builder interceptor(RequestInterceptor interceptor) { + super.interceptor(interceptor); + return this; + } + + @Override + public BulkDetokenizeOptions build() { + return new BulkDetokenizeOptions(this); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/vault/data/BulkDetokenizeRequest.java b/flowvault/src/main/java/com/skyflow/vault/data/BulkDetokenizeRequest.java new file mode 100644 index 00000000..dd2c9366 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/data/BulkDetokenizeRequest.java @@ -0,0 +1,39 @@ +package com.skyflow.vault.data; + +import java.util.List; + +// Bulk counterpart of DetokenizeRequest. Carries no extra state today; all fields +// (tokens, tokenGroupRedactions) are inherited. +public class BulkDetokenizeRequest extends DetokenizeRequest { + + protected BulkDetokenizeRequest(BulkDetokenizeRequestBuilder builder) { + super(builder); + } + + public static BulkDetokenizeRequestBuilder builder() { + return new BulkDetokenizeRequestBuilder(); + } + + public static final class BulkDetokenizeRequestBuilder extends DetokenizeRequestBuilder { + + private BulkDetokenizeRequestBuilder() { + } + + @Override + public BulkDetokenizeRequestBuilder tokens(List tokens) { + super.tokens(tokens); + return this; + } + + @Override + public BulkDetokenizeRequestBuilder tokenGroupRedactions(List tokenGroupRedactions) { + super.tokenGroupRedactions(tokenGroupRedactions); + return this; + } + + @Override + public BulkDetokenizeRequest build() { + return new BulkDetokenizeRequest(this); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/vault/data/BulkDetokenizeResponse.java b/flowvault/src/main/java/com/skyflow/vault/data/BulkDetokenizeResponse.java new file mode 100644 index 00000000..279e1dc6 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/data/BulkDetokenizeResponse.java @@ -0,0 +1,59 @@ +package com.skyflow.vault.data; + +import com.google.gson.Gson; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class BulkDetokenizeResponse { + private DetokenizeSummary summary; + private List records; + + // Internal fields. transient keeps them out of the toString() output. + private transient List originalPayload; + private transient List tokensToRetry; + + public BulkDetokenizeResponse(List records) { + this.records = records; + } + + public BulkDetokenizeResponse( + List records, + List originalPayload + ) { + this.records = records; + this.originalPayload = originalPayload; + int totalFailed = (int) records.stream().filter(record -> record.getError() != null).count(); + this.summary = new DetokenizeSummary(originalPayload.size(), records.size() - totalFailed, totalFailed); + } + + public DetokenizeSummary getSummary() { + return this.summary; + } + + public List getRecords() { + return this.records; + } + + public List getTokensToRetry() { + if (tokensToRetry == null) { + // Per-batch responses are built without the original payload; nothing to retry from. + if (originalPayload == null) { + return new ArrayList<>(); + } + tokensToRetry = records.stream() + .filter(record -> record.getHttpCode() >= 500 && record.getHttpCode() <= 599 + && record.getHttpCode() != 529) + .map(record -> originalPayload.get(record.getIndex())) + .collect(Collectors.toCollection(ArrayList::new)); + } + return tokensToRetry; + } + + @Override + public String toString() { + Gson gson = new Gson().newBuilder().serializeNulls().create(); + return gson.toJson(this); + } +} diff --git a/flowvault/src/main/java/com/skyflow/vault/data/BulkDetokenizeResponseRecord.java b/flowvault/src/main/java/com/skyflow/vault/data/BulkDetokenizeResponseRecord.java new file mode 100644 index 00000000..e38c20d7 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/data/BulkDetokenizeResponseRecord.java @@ -0,0 +1,34 @@ +package com.skyflow.vault.data; + +import com.google.gson.Gson; + +import java.util.Map; + +// Bulk counterpart of DetokenizeResponseRecord. Adds the caller-facing position of the token +// in the submitted payload; all other fields are inherited. +public class BulkDetokenizeResponseRecord extends DetokenizeResponseRecord { + private final int index; + private final String requestId; + + public BulkDetokenizeResponseRecord(int index, String token, Object value, String tokenGroupName, + Map metadata, int httpCode, String error, + String requestId) { + super(token, value, tokenGroupName, metadata, httpCode, error); + this.index = index; + this.requestId = requestId; + } + + public int getIndex() { + return index; + } + + public String getRequestId() { + return requestId; + } + + @Override + public String toString() { + Gson gson = new Gson().newBuilder().serializeNulls().create(); + return gson.toJson(this); + } +} diff --git a/flowvault/src/main/java/com/skyflow/vault/data/BulkInsertOptions.java b/flowvault/src/main/java/com/skyflow/vault/data/BulkInsertOptions.java new file mode 100644 index 00000000..6dfea493 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/data/BulkInsertOptions.java @@ -0,0 +1,31 @@ +package com.skyflow.vault.data; + +// Bulk counterpart of InsertOptions. Carries no extra state today; the interceptor +// field is inherited. +public class BulkInsertOptions extends InsertOptions { + + protected BulkInsertOptions(Builder builder) { + super(builder); + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder extends InsertOptions.Builder { + + private Builder() { + } + + @Override + public Builder interceptor(RequestInterceptor interceptor) { + super.interceptor(interceptor); + return this; + } + + @Override + public BulkInsertOptions build() { + return new BulkInsertOptions(this); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/vault/data/BulkInsertRequest.java b/flowvault/src/main/java/com/skyflow/vault/data/BulkInsertRequest.java new file mode 100644 index 00000000..82ff45ef --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/data/BulkInsertRequest.java @@ -0,0 +1,45 @@ +package com.skyflow.vault.data; + +import java.util.List; + +// Bulk counterpart of InsertRequest. Carries no extra state today; all fields +// (tableName, records, upsert) are inherited. +public class BulkInsertRequest extends InsertRequest { + + protected BulkInsertRequest(BulkInsertRequestBuilder builder) { + super(builder); + } + + public static BulkInsertRequestBuilder builder() { + return new BulkInsertRequestBuilder(); + } + + public static final class BulkInsertRequestBuilder extends InsertRequestBuilder { + + private BulkInsertRequestBuilder() { + } + + @Override + public BulkInsertRequestBuilder tableName(String tableName) { + super.tableName(tableName); + return this; + } + + @Override + public BulkInsertRequestBuilder records(List records) { + super.records(records); + return this; + } + + @Override + public BulkInsertRequestBuilder upsert(UpsertOptions upsert) { + super.upsert(upsert); + return this; + } + + @Override + public BulkInsertRequest build() { + return new BulkInsertRequest(this); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/vault/data/BulkInsertRequestRecord.java b/flowvault/src/main/java/com/skyflow/vault/data/BulkInsertRequestRecord.java new file mode 100644 index 00000000..6cddda6b --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/data/BulkInsertRequestRecord.java @@ -0,0 +1,51 @@ +package com.skyflow.vault.data; + +import java.util.Map; + +// Bulk counterpart of InsertRequestRecord. Carries no extra state today; it exists so +// bulk-only fields can be added without touching the unary record. +public class BulkInsertRequestRecord extends InsertRequestRecord { + + protected BulkInsertRequestRecord(BulkInsertRequestRecordBuilder builder) { + super(builder); + } + + public static BulkInsertRequestRecordBuilder builder() { + return new BulkInsertRequestRecordBuilder(); + } + + public static final class BulkInsertRequestRecordBuilder extends InsertRequestRecordBuilder { + + private BulkInsertRequestRecordBuilder() { + } + + @Override + public BulkInsertRequestRecordBuilder tableName(String tableName) { + super.tableName(tableName); + return this; + } + + @Override + public BulkInsertRequestRecordBuilder data(Map data) { + super.data(data); + return this; + } + + @Override + public BulkInsertRequestRecordBuilder tokens(Map tokens) { + super.tokens(tokens); + return this; + } + + @Override + public BulkInsertRequestRecordBuilder upsert(UpsertOptions upsert) { + super.upsert(upsert); + return this; + } + + @Override + public BulkInsertRequestRecord build() { + return new BulkInsertRequestRecord(this); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/vault/data/BulkInsertResponse.java b/flowvault/src/main/java/com/skyflow/vault/data/BulkInsertResponse.java new file mode 100644 index 00000000..b97d2e97 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/data/BulkInsertResponse.java @@ -0,0 +1,61 @@ +package com.skyflow.vault.data; + +import com.google.gson.Gson; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class BulkInsertResponse { + private BulkSummary summary; + private List records; + + // Internal fields. transient keeps them out of the toString() output. + private transient List originalPayload; + private transient List recordsToRetry; + + public BulkInsertResponse(List records) { + this.records = records; + } + + public BulkInsertResponse( + List records, + List originalPayload + ) { + this.records = records; + this.originalPayload = originalPayload; + int totalFailed = (int) records.stream().filter(record -> record.getError() != null).count(); + this.summary = new BulkSummary(originalPayload.size(), records.size() - totalFailed, totalFailed); + } + + public BulkSummary getSummary() { + return this.summary; + } + + public List getRecords() { + return this.records; + } + + // Records are guaranteed to be BulkInsertRequestRecord: validateBulkInsertRequest rejects + // any other InsertRequestRecord subtype before the request is ever sent. + public List getRecordsToRetry() { + if (recordsToRetry == null) { + // Per-batch responses are built without the original payload; nothing to retry from. + if (originalPayload == null) { + return new ArrayList<>(); + } + recordsToRetry = records.stream() + .filter(record -> record.getHttpCode() >= 500 && record.getHttpCode() <= 599 + && record.getHttpCode() != 529) + .map(record -> (BulkInsertRequestRecord) originalPayload.get(record.getIndex())) + .collect(Collectors.toCollection(ArrayList::new)); + } + return recordsToRetry; + } + + @Override + public String toString() { + Gson gson = new Gson().newBuilder().serializeNulls().create(); + return gson.toJson(this); + } +} diff --git a/flowvault/src/main/java/com/skyflow/vault/data/BulkInsertResponseRecord.java b/flowvault/src/main/java/com/skyflow/vault/data/BulkInsertResponseRecord.java new file mode 100644 index 00000000..1ea6b757 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/data/BulkInsertResponseRecord.java @@ -0,0 +1,34 @@ +package com.skyflow.vault.data; + +import com.google.gson.Gson; + +import java.util.Map; + +// Bulk counterpart of InsertResponseRecord. Adds the caller-facing position of the record +// in the submitted payload; all other fields are inherited. +public class BulkInsertResponseRecord extends InsertResponseRecord { + private final int index; + private final String requestId; + + public BulkInsertResponseRecord(int index, String tableName, String skyflowId, + Map fields, Map hashedData, + int httpCode, String error, String requestId) { + super(tableName, skyflowId, fields, hashedData, httpCode, error); + this.index = index; + this.requestId = requestId; + } + + public int getIndex() { + return index; + } + + public String getRequestId(){ + return requestId; + } + + @Override + public String toString() { + Gson gson = new Gson().newBuilder().serializeNulls().create(); + return gson.toJson(this); + } +} diff --git a/flowvault/src/main/java/com/skyflow/vault/data/BulkSummary.java b/flowvault/src/main/java/com/skyflow/vault/data/BulkSummary.java new file mode 100644 index 00000000..f32a14ee --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/data/BulkSummary.java @@ -0,0 +1,40 @@ +package com.skyflow.vault.data; + +import com.google.gson.Gson; +import com.google.gson.annotations.Expose; + +public class BulkSummary { + @Expose(serialize = true) + private int totalRecords; + @Expose(serialize = true) + private int totalInserted; + @Expose(serialize = true) + private int totalFailed; + + public BulkSummary() { + } + + public BulkSummary(int totalRecords, int totalInserted, int totalFailed) { + this.totalRecords = totalRecords; + this.totalInserted = totalInserted; + this.totalFailed = totalFailed; + } + + public int getTotalRecords() { + return totalRecords; + } + + public int getTotalInserted() { + return totalInserted; + } + + public int getTotalFailed() { + return totalFailed; + } + + @Override + public String toString() { + Gson gson = new Gson(); + return gson.toJson(this); + } +} diff --git a/flowvault/src/main/java/com/skyflow/vault/data/BulkTokenizeOptions.java b/flowvault/src/main/java/com/skyflow/vault/data/BulkTokenizeOptions.java new file mode 100644 index 00000000..36d47507 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/data/BulkTokenizeOptions.java @@ -0,0 +1,34 @@ +package com.skyflow.vault.data; + +/** + * Per-call options for bulk tokenize. + * + *

Adds nothing to {@link TokenizeOptions} today; it exists so the bulk interfaces have their own + * options type to grow into, matching the {@link BulkTokenizeRequest} / {@link TokenizeRequest} split. + */ +public final class BulkTokenizeOptions extends TokenizeOptions { + + private BulkTokenizeOptions(BulkTokenizeOptionsBuilder builder) { + super(builder); + } + + public static BulkTokenizeOptionsBuilder builder() { + return new BulkTokenizeOptionsBuilder(); + } + + public static final class BulkTokenizeOptionsBuilder extends Builder { + + private BulkTokenizeOptionsBuilder() {} + + @Override + public BulkTokenizeOptionsBuilder interceptor(RequestInterceptor interceptor) { + super.interceptor(interceptor); + return this; + } + + @Override + public BulkTokenizeOptions build() { + return new BulkTokenizeOptions(this); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/vault/data/BulkTokenizeRequest.java b/flowvault/src/main/java/com/skyflow/vault/data/BulkTokenizeRequest.java new file mode 100644 index 00000000..852f00d9 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/data/BulkTokenizeRequest.java @@ -0,0 +1,50 @@ +package com.skyflow.vault.data; + +import java.util.ArrayList; +import java.util.List; + +public class BulkTokenizeRequest extends TokenizeRequest { + + private BulkTokenizeRequest(List records) { + super(records); + } + + public static BulkTokenizeRequestBuilder builder() { + return new BulkTokenizeRequestBuilder(); + } + + /** + * Narrows the inherited accessor to the bulk record type. Safe because the builder only ever + * stores {@link BulkTokenizeRequestRecord}s. + */ + @Override + @SuppressWarnings("unchecked") + public List getRecords() { + return (List) super.getRecords(); + } + + public static final class BulkTokenizeRequestBuilder extends TokenizeRequestBuilder { + + private BulkTokenizeRequestBuilder() {} + + @Override + public BulkTokenizeRequestBuilder records(List records) { + this.records = records; + return this; + } + + @Override + public BulkTokenizeRequest build() { + List bulkRecords = null; + if (this.records != null) { + bulkRecords = new ArrayList<>(); + for (TokenizeRequestRecord record : this.records) { + // fail fast and clearly if a plain TokenizeRequestRecord was supplied to the + // bulk builder through the inherited setter + bulkRecords.add((BulkTokenizeRequestRecord) record); + } + } + return new BulkTokenizeRequest(bulkRecords); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/vault/data/BulkTokenizeRequestRecord.java b/flowvault/src/main/java/com/skyflow/vault/data/BulkTokenizeRequestRecord.java new file mode 100644 index 00000000..cdf792d9 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/data/BulkTokenizeRequestRecord.java @@ -0,0 +1,50 @@ +package com.skyflow.vault.data; + +import java.util.List; + +/** + * A record in a bulk tokenize request. + * + *

Carries nothing beyond {@link TokenizeRequestRecord} today. It exists as its own type so the + * bulk request has somewhere to grow, mirroring the {@link BulkInsertRecord} / {@link InsertRecord} + * split. The index that correlates a record with its result is assigned by the SDK from list + * position and appears only on {@link BulkTokenizeResponseRecord} — callers never supply it. + */ +public class BulkTokenizeRequestRecord extends TokenizeRequestRecord { + + private BulkTokenizeRequestRecord(BulkTokenizeRequestRecordBuilder builder) { + super(builder); + } + + public static BulkTokenizeRequestRecordBuilder builder() { + return new BulkTokenizeRequestRecordBuilder(); + } + + public static final class BulkTokenizeRequestRecordBuilder extends TokenizeRequestRecordBuilder { + + private BulkTokenizeRequestRecordBuilder() {} + + @Override + public BulkTokenizeRequestRecordBuilder value(Object value) { + this.value = value; + return this; + } + + @Override + public BulkTokenizeRequestRecordBuilder token(Object token) { + this.token = token; + return this; + } + + @Override + public BulkTokenizeRequestRecordBuilder tokenGroupNames(List tokenGroupNames) { + this.tokenGroupNames = tokenGroupNames; + return this; + } + + @Override + public BulkTokenizeRequestRecord build() { + return new BulkTokenizeRequestRecord(this); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/vault/data/BulkTokenizeResponse.java b/flowvault/src/main/java/com/skyflow/vault/data/BulkTokenizeResponse.java new file mode 100644 index 00000000..115fb9b3 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/data/BulkTokenizeResponse.java @@ -0,0 +1,130 @@ +package com.skyflow.vault.data; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.annotations.Expose; + +import java.util.ArrayList; +import java.util.List; + +public class BulkTokenizeResponse { + @Expose(serialize = true) + private TokenizeSummary summary; + + @Expose(serialize = true) + private List records; + + private List originalPayload; + private List recordsToRetry; + + public BulkTokenizeResponse(List records) { + this.records = records; + } + + public BulkTokenizeResponse(List records, + List originalPayload) { + this.records = records; + this.originalPayload = originalPayload; + this.summary = buildSummary(this.records, this.originalPayload); + } + + /** + * {@code totalTokens} counts the input values submitted. The remaining three classify each + * value by how its token groups fared, so together they sum to the number of values. + */ + private static TokenizeSummary buildSummary(List records, + List originalPayload) { + int totalTokenized = 0; + int totalPartial = 0; + int totalFailed = 0; + if (records != null) { + for (BulkTokenizeResponseRecord record : records) { + int succeeded = 0; + int failed = 0; + if (record.getTokens() != null) { + for (TokenizeResponseToken token : record.getTokens()) { + if (token.getError() == null) { + succeeded++; + } else { + failed++; + } + } + } + if (succeeded > 0 && failed > 0) { + totalPartial++; + } else if (succeeded > 0) { + totalTokenized++; + } else { + // no token groups came back, or every one of them failed + totalFailed++; + } + } + } + int totalTokens = originalPayload != null + ? originalPayload.size() + : (records != null ? records.size() : 0); + return new TokenizeSummary(totalTokens, totalTokenized, totalPartial, totalFailed); + } + + public TokenizeSummary getSummary() { + return summary; + } + + public List getRecords() { + return records; + } + + /** + * The records that failed with a retryable status, ready to be resubmitted as a new bulk request. + * + *

Retryable means a 5xx other than 529, matching the rule used elsewhere in the SDK. The + * caller's original record objects are returned unchanged — they carry no index, exactly as they + * were supplied. Records where nothing failed retryably are omitted. + */ + public List getRecordsToRetry() { + if (recordsToRetry == null) { + recordsToRetry = new ArrayList<>(); + if (records != null && originalPayload != null) { + for (BulkTokenizeResponseRecord record : records) { + // the SDK assigns the index from the record's position in originalPayload, so a + // positional lookup is exact + int index = record.getIndex(); + if (index < 0 || index >= originalPayload.size() || !hasRetryableFailure(record)) { + continue; + } + recordsToRetry.add(originalPayload.get(index)); + } + } + } + return recordsToRetry; + } + + private static boolean hasRetryableFailure(BulkTokenizeResponseRecord record) { + if (record.getTokens() == null) { + return false; + } + for (TokenizeResponseToken token : record.getTokens()) { + if (isRetryable(token)) { + return true; + } + } + return false; + } + + private static boolean isRetryable(TokenizeResponseToken token) { + Integer httpCode = token.getHttpCode(); + return token.getError() != null + && httpCode != null + && httpCode >= 500 && httpCode <= 599 + && httpCode != 529; + } + + @Override + public String toString() { + Gson gson = new GsonBuilder() + .excludeFieldsWithoutExposeAnnotation() + .serializeNulls() + .create(); + return gson.toJson(this); + } +} diff --git a/flowvault/src/main/java/com/skyflow/vault/data/BulkTokenizeResponseRecord.java b/flowvault/src/main/java/com/skyflow/vault/data/BulkTokenizeResponseRecord.java new file mode 100644 index 00000000..bfcae073 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/data/BulkTokenizeResponseRecord.java @@ -0,0 +1,31 @@ +package com.skyflow.vault.data; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.annotations.Expose; + +import java.util.List; + +/** + * A {@link TokenizeResponseRecord} carrying the index of the input value it belongs to. The index + * is the one supplied on the matching {@link BulkTokenizeRequestRecord}, echoed back unchanged. + */ +public class BulkTokenizeResponseRecord extends TokenizeResponseRecord { + @Expose(serialize = true) + private final int index; + + public BulkTokenizeResponseRecord(int index, Object value, List tokens) { + super(value, tokens); + this.index = index; + } + + public int getIndex() { + return index; + } + + @Override + public String toString() { + Gson gson = new GsonBuilder().serializeNulls().create(); + return gson.toJson(this); + } +} diff --git a/flowvault/src/main/java/com/skyflow/vault/data/DeleteTokensOptions.java b/flowvault/src/main/java/com/skyflow/vault/data/DeleteTokensOptions.java new file mode 100644 index 00000000..67a3e849 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/data/DeleteTokensOptions.java @@ -0,0 +1,38 @@ +package com.skyflow.vault.data; + +/** + * Per-call options for delete tokens. + * + *

Subclassed by {@link BulkDeleteTokensOptions} so the bulk interfaces can take their own options + * type while sharing this one's settings, mirroring how {@link BulkDeleteTokensRequest} extends {@link DeleteTokensRequest}. + */ +public class DeleteTokensOptions { + private final RequestInterceptor interceptor; + + protected DeleteTokensOptions(Builder builder) { + this.interceptor = builder.interceptor; + } + + public RequestInterceptor getInterceptor() { + return interceptor; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private RequestInterceptor interceptor; + + protected Builder() {} + + public Builder interceptor(RequestInterceptor interceptor) { + this.interceptor = interceptor; + return this; + } + + public DeleteTokensOptions build() { + return new DeleteTokensOptions(this); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/vault/data/DeleteTokensRecord.java b/flowvault/src/main/java/com/skyflow/vault/data/DeleteTokensRecord.java new file mode 100644 index 00000000..50f05e86 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/data/DeleteTokensRecord.java @@ -0,0 +1,63 @@ +package com.skyflow.vault.data; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.annotations.Expose; + +/** + * A single token's delete outcome. {@code token} and {@code httpCode} are present on both the + * success and the error path; {@code error} is null when the token was deleted successfully. + * + *

{@code requestId} identifies the API call this outcome came from and is set only when the + * outcome is an error, since that is when it is useful for support. Bulk requests are split into + * batches, so every error from the same batch carries the same id and errors from different + * batches carry different ones. + */ +public class DeleteTokensRecord { + @Expose(serialize = true) + private final String token; + + @Expose(serialize = true) + private final Integer httpCode; + + @Expose(serialize = true) + private final String error; + + @Expose(serialize = true) + private final String requestId; + + public DeleteTokensRecord(String token, Integer httpCode, String error) { + this(token, httpCode, error, null); + } + + public DeleteTokensRecord(String token, Integer httpCode, String error, String requestId) { + this.token = token; + this.httpCode = httpCode; + this.error = error; + // a successful delete carries no request id, whatever the caller passed + this.requestId = error != null ? requestId : null; + } + + public String getToken() { + return token; + } + + public Integer getHttpCode() { + return httpCode; + } + + public String getError() { + return error; + } + + /** The API call this outcome came from; null unless this is an error. */ + public String getRequestId() { + return requestId; + } + + @Override + public String toString() { + Gson gson = new GsonBuilder().serializeNulls().create(); + return gson.toJson(this); + } +} diff --git a/flowvault/src/main/java/com/skyflow/vault/data/DeleteTokensRequest.java b/flowvault/src/main/java/com/skyflow/vault/data/DeleteTokensRequest.java new file mode 100644 index 00000000..98ab8922 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/data/DeleteTokensRequest.java @@ -0,0 +1,34 @@ +package com.skyflow.vault.data; + +import java.util.List; + +public class DeleteTokensRequest { + private final List tokens; + + protected DeleteTokensRequest(List tokens) { + this.tokens = tokens; + } + + public static DeleteTokensRequestBuilder builder() { + return new DeleteTokensRequestBuilder(); + } + + public List getTokens() { + return this.tokens; + } + + public static class DeleteTokensRequestBuilder { + protected List tokens; + + protected DeleteTokensRequestBuilder() {} + + public DeleteTokensRequestBuilder tokens(List tokens) { + this.tokens = tokens; + return this; + } + + public DeleteTokensRequest build() { + return new DeleteTokensRequest(this.tokens); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/vault/data/DeleteTokensResponse.java b/flowvault/src/main/java/com/skyflow/vault/data/DeleteTokensResponse.java new file mode 100644 index 00000000..46c3887a --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/data/DeleteTokensResponse.java @@ -0,0 +1,24 @@ +package com.skyflow.vault.data; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import java.util.List; + +public class DeleteTokensResponse { + private final List records; + + public DeleteTokensResponse(List records) { + this.records = records; + } + + public List getRecords() { + return records; + } + + @Override + public String toString() { + Gson gson = new GsonBuilder().serializeNulls().create(); + return gson.toJson(this); + } +} diff --git a/flowvault/src/main/java/com/skyflow/vault/data/DeleteTokensSummary.java b/flowvault/src/main/java/com/skyflow/vault/data/DeleteTokensSummary.java new file mode 100644 index 00000000..92770caf --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/data/DeleteTokensSummary.java @@ -0,0 +1,41 @@ +package com.skyflow.vault.data; + +import com.google.gson.Gson; +import com.google.gson.annotations.Expose; + +public class DeleteTokensSummary { + @Expose(serialize = true) + private int totalTokens; + + @Expose(serialize = true) + private int totalDeleted; + + @Expose(serialize = true) + private int totalFailed; + + public DeleteTokensSummary() {} + + public DeleteTokensSummary(int totalTokens, int totalDeleted, int totalFailed) { + this.totalTokens = totalTokens; + this.totalDeleted = totalDeleted; + this.totalFailed = totalFailed; + } + + public int getTotalTokens() { + return totalTokens; + } + + public int getTotalDeleted() { + return totalDeleted; + } + + public int getTotalFailed() { + return totalFailed; + } + + @Override + public String toString() { + Gson gson = new Gson(); + return gson.toJson(this); + } +} diff --git a/flowvault/src/main/java/com/skyflow/vault/data/DetokenizeOptions.java b/flowvault/src/main/java/com/skyflow/vault/data/DetokenizeOptions.java new file mode 100644 index 00000000..ddc18bdf --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/data/DetokenizeOptions.java @@ -0,0 +1,33 @@ +package com.skyflow.vault.data; + +public class DetokenizeOptions { + private final RequestInterceptor interceptor; + + protected DetokenizeOptions(Builder builder) { + this.interceptor = builder.interceptor; + } + + public RequestInterceptor getInterceptor() { + return interceptor; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private RequestInterceptor interceptor; + + protected Builder() { + } + + public Builder interceptor(RequestInterceptor interceptor) { + this.interceptor = interceptor; + return this; + } + + public DetokenizeOptions build() { + return new DetokenizeOptions(this); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/vault/data/DetokenizeRequest.java b/flowvault/src/main/java/com/skyflow/vault/data/DetokenizeRequest.java new file mode 100644 index 00000000..e833450e --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/data/DetokenizeRequest.java @@ -0,0 +1,41 @@ +package com.skyflow.vault.data; + +import java.util.List; + +public class DetokenizeRequest extends BaseDetokenizeRequest { + private final DetokenizeRequestBuilder builder; + + protected DetokenizeRequest(DetokenizeRequestBuilder builder) { + this.builder = builder; + } + + public static DetokenizeRequestBuilder builder() { + return new DetokenizeRequestBuilder(); + } + + public List getTokens() { + return this.builder.tokens; + } + + public List getTokenGroupRedactions(){ + return this.builder.tokenGroupRedactions; + } + + public static class DetokenizeRequestBuilder { + private List tokens; + private List tokenGroupRedactions; + + public DetokenizeRequestBuilder tokens(List tokens) { + this.tokens = tokens; + return this; + } + public DetokenizeRequestBuilder tokenGroupRedactions(List tokenGroupRedactions){ + this.tokenGroupRedactions = tokenGroupRedactions; + return this; + } + public DetokenizeRequest build() { + return new DetokenizeRequest(this); + } + } + +} diff --git a/flowvault/src/main/java/com/skyflow/vault/data/DetokenizeResponse.java b/flowvault/src/main/java/com/skyflow/vault/data/DetokenizeResponse.java new file mode 100644 index 00000000..b716abca --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/data/DetokenizeResponse.java @@ -0,0 +1,25 @@ +package com.skyflow.vault.data; + +import com.google.gson.Gson; + +import java.util.List; + +// Response shape for the unary detokenize contract. Retained as published API even though the +// module currently exposes only the bulk operations. +public class DetokenizeResponse extends BaseDetokenizeResponse { + private final List records; + + public DetokenizeResponse(List records) { + this.records = records; + } + + public List getRecords() { + return records; + } + + @Override + public String toString() { + Gson gson = new Gson(); + return gson.toJson(this); + } +} diff --git a/flowvault/src/main/java/com/skyflow/vault/data/DetokenizeResponseRecord.java b/flowvault/src/main/java/com/skyflow/vault/data/DetokenizeResponseRecord.java new file mode 100644 index 00000000..e0aa8278 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/data/DetokenizeResponseRecord.java @@ -0,0 +1,36 @@ +package com.skyflow.vault.data; + +import java.util.Map; + +public class DetokenizeResponseRecord extends BaseDetokenizeRecordResponse { + // Passed straight through from V1FlowDetokenizeResponseObject.getValue() (Optional). + private final Object value; + private final String tokenGroupName; + private final Map metadata; + private final int httpCode; + + public DetokenizeResponseRecord(String token, Object value, String tokenGroupName, + Map metadata, int httpCode, String error) { + super(token, error); + this.value = value; + this.tokenGroupName = tokenGroupName; + this.metadata = metadata; + this.httpCode = httpCode; + } + + public Object getValue() { + return value; + } + + public String getTokenGroupName() { + return tokenGroupName; + } + + public Map getMetadata() { + return metadata; + } + + public int getHttpCode() { + return httpCode; + } +} diff --git a/flowvault/src/main/java/com/skyflow/vault/data/DetokenizeSummary.java b/flowvault/src/main/java/com/skyflow/vault/data/DetokenizeSummary.java new file mode 100644 index 00000000..c86c1854 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/data/DetokenizeSummary.java @@ -0,0 +1,40 @@ +package com.skyflow.vault.data; + +import com.google.gson.Gson; +import com.google.gson.annotations.Expose; + +public class DetokenizeSummary { + @Expose(serialize = true) + private int totalTokens; + @Expose(serialize = true) + private int totalDetokenized; + @Expose(serialize = true) + private int totalFailed; + + public DetokenizeSummary() { + } + + public DetokenizeSummary(int totalTokens, int totalDetokenized, int totalFailed) { + this.totalTokens = totalTokens; + this.totalDetokenized = totalDetokenized; + this.totalFailed = totalFailed; + } + + public int getTotalTokens() { + return totalTokens; + } + + public int getTotalDetokenized() { + return totalDetokenized; + } + + public int getTotalFailed() { + return totalFailed; + } + + @Override + public String toString() { + Gson gson = new Gson(); + return gson.toJson(this); + } +} diff --git a/flowvault/src/main/java/com/skyflow/vault/data/ErrorRecord.java b/flowvault/src/main/java/com/skyflow/vault/data/ErrorRecord.java new file mode 100644 index 00000000..1536c0f9 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/data/ErrorRecord.java @@ -0,0 +1,51 @@ +package com.skyflow.vault.data; + +import com.google.gson.Gson; +import com.google.gson.annotations.Expose; + +public class ErrorRecord { + @Expose(serialize = true) + private int index; + @Expose(serialize = true) + private String error; + @Expose(serialize = true) + private int code; + @Expose(serialize = true) + private String requestId; + + public ErrorRecord(int index, String error, int code) { + this.index = index; + this.error = error; + this.code = code; + } + + public ErrorRecord(int index, String error, int code, String requestId) { + this.index = index; + this.error = error; + this.code = code; + this.requestId = requestId; + } + + public String getError() { + return error; + } + + public int getCode() { + return code; + } + + public int getIndex() { + return index; + } + + public String getRequestId() { + return requestId; + } + + + @Override + public String toString() { + Gson gson = new Gson(); + return gson.toJson(this); + } +} diff --git a/flowvault/src/main/java/com/skyflow/vault/data/InsertOptions.java b/flowvault/src/main/java/com/skyflow/vault/data/InsertOptions.java new file mode 100644 index 00000000..a3d5aa79 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/data/InsertOptions.java @@ -0,0 +1,33 @@ +package com.skyflow.vault.data; + +public class InsertOptions { + private final RequestInterceptor interceptor; + + protected InsertOptions(Builder builder) { + this.interceptor = builder.interceptor; + } + + public RequestInterceptor getInterceptor() { + return interceptor; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private RequestInterceptor interceptor; + + protected Builder() { + } + + public Builder interceptor(RequestInterceptor interceptor) { + this.interceptor = interceptor; + return this; + } + + public InsertOptions build() { + return new InsertOptions(this); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/vault/data/InsertRequest.java b/flowvault/src/main/java/com/skyflow/vault/data/InsertRequest.java new file mode 100644 index 00000000..4a2bcb5d --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/data/InsertRequest.java @@ -0,0 +1,55 @@ +package com.skyflow.vault.data; + +import java.util.List; + +public class InsertRequest extends BaseInsertRequest { + private final InsertRequestBuilder builder; + + protected InsertRequest(InsertRequestBuilder builder) { + this.builder = builder; + } + + public static InsertRequestBuilder builder() { + return new InsertRequestBuilder(); + } + + public String getTableName() { + return this.builder.tableName; + } + + public List getRecords() { + return this.builder.records; + } + + public UpsertOptions getUpsert() { + return this.builder.upsert; + } + + public static class InsertRequestBuilder { + private String tableName; + private List records; + private UpsertOptions upsert; + + protected InsertRequestBuilder() { + } + + public InsertRequestBuilder tableName(String tableName) { + this.tableName = tableName; + return this; + } + + public InsertRequestBuilder records(List records) { + this.records = records; + return this; + } + + public InsertRequestBuilder upsert(UpsertOptions upsert) { + this.upsert = upsert; + return this; + } + + public InsertRequest build() { + return new InsertRequest(this); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/vault/data/InsertRequestRecord.java b/flowvault/src/main/java/com/skyflow/vault/data/InsertRequestRecord.java new file mode 100644 index 00000000..75747a84 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/data/InsertRequestRecord.java @@ -0,0 +1,65 @@ +package com.skyflow.vault.data; + +import java.util.Map; + +public class InsertRequestRecord { + private final InsertRequestRecordBuilder builder; + + protected InsertRequestRecord(InsertRequestRecordBuilder builder) { + this.builder = builder; + } + + // Getters + public String getTableName() { + return this.builder.tableName; + } + + public Map getData() { + return this.builder.data; + } + + public Map getTokens() { + return this.builder.tokens; + } + + public UpsertOptions getUpsert() { + return this.builder.upsert; + } + + // Builder Class + public static class InsertRequestRecordBuilder { + private String tableName; + private Map data; + private Map tokens; + private UpsertOptions upsert; + + public InsertRequestRecordBuilder tableName(String tableName) { + this.tableName = tableName; + return this; + } + + public InsertRequestRecordBuilder data(Map data) { + this.data = data; + return this; + } + + public InsertRequestRecordBuilder tokens(Map tokens) { + this.tokens = tokens; + return this; + } + + public InsertRequestRecordBuilder upsert(UpsertOptions upsert) { + this.upsert = upsert; + return this; + } + + public InsertRequestRecord build() { + return new InsertRequestRecord(this); + } + } + + // Static entry point for builder + public static InsertRequestRecordBuilder builder() { + return new InsertRequestRecordBuilder(); + } +} diff --git a/flowvault/src/main/java/com/skyflow/vault/data/InsertResponse.java b/flowvault/src/main/java/com/skyflow/vault/data/InsertResponse.java new file mode 100644 index 00000000..594e5fd1 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/data/InsertResponse.java @@ -0,0 +1,25 @@ +package com.skyflow.vault.data; + +import com.google.gson.Gson; + +import java.util.List; + +// Response shape for the unary insert contract. Retained as published API even though the +// module currently exposes only the bulk operations. +public class InsertResponse extends BaseInsertResponse { + private final List records; + + public InsertResponse(List records) { + this.records = records; + } + + public List getRecords() { + return records; + } + + @Override + public String toString() { + Gson gson = new Gson().newBuilder().serializeNulls().create(); + return gson.toJson(this); + } +} diff --git a/flowvault/src/main/java/com/skyflow/vault/data/InsertResponseRecord.java b/flowvault/src/main/java/com/skyflow/vault/data/InsertResponseRecord.java new file mode 100644 index 00000000..442d3749 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/data/InsertResponseRecord.java @@ -0,0 +1,46 @@ +package com.skyflow.vault.data; + +import java.util.Map; + +public class InsertResponseRecord { + private final String tableName; + private final String skyflowId; + private final Map fields; + private final Map hashedData; + private final int httpCode; + private final String error; + + public InsertResponseRecord(String tableName, String skyflowId, Map fields, + Map hashedData, int httpCode, String error) { + this.tableName = tableName; + this.skyflowId = skyflowId; + this.fields = fields; + this.hashedData = hashedData; + this.httpCode = httpCode; + this.error = error; + } + + public String getTableName() { + return tableName; + } + + public String getSkyflowId() { + return skyflowId; + } + + public Map getFields() { + return fields; + } + + public Map getHashedData() { + return hashedData; + } + + public int getHttpCode() { + return httpCode; + } + + public String getError() { + return error; + } +} diff --git a/flowvault/src/main/java/com/skyflow/vault/data/TokenGroupRedactions.java b/flowvault/src/main/java/com/skyflow/vault/data/TokenGroupRedactions.java new file mode 100644 index 00000000..abeabdbe --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/data/TokenGroupRedactions.java @@ -0,0 +1,39 @@ +package com.skyflow.vault.data; + +public class TokenGroupRedactions extends BaseDetokenizeData { + private final TokenGroupRedactionsBuilder builder; + + private TokenGroupRedactions(TokenGroupRedactionsBuilder builder) { + this.builder = builder; + } + public String getTokenGroupName() { + return this.builder.tokenGroupName; + } + + public String getRedaction() { + return this.builder.redaction; + } + + public static TokenGroupRedactionsBuilder builder() { + return new TokenGroupRedactionsBuilder(); + } + + public static final class TokenGroupRedactionsBuilder { + private String tokenGroupName; + private String redaction; + + public TokenGroupRedactionsBuilder tokenGroupName(String tokenGroupName) { + this.tokenGroupName = tokenGroupName; + return this; + } + + public TokenGroupRedactionsBuilder redaction(String redaction) { + this.redaction = redaction; + return this; + } + + public TokenGroupRedactions build() { + return new TokenGroupRedactions(this); + } + } +} \ No newline at end of file diff --git a/flowvault/src/main/java/com/skyflow/vault/data/TokenizeOptions.java b/flowvault/src/main/java/com/skyflow/vault/data/TokenizeOptions.java new file mode 100644 index 00000000..f955a731 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/data/TokenizeOptions.java @@ -0,0 +1,38 @@ +package com.skyflow.vault.data; + +/** + * Per-call options for tokenize. + * + *

Subclassed by {@link BulkTokenizeOptions} so the bulk interfaces can take their own options + * type while sharing this one's settings, mirroring how {@link BulkTokenizeRequest} extends {@link TokenizeRequest}. + */ +public class TokenizeOptions { + private final RequestInterceptor interceptor; + + protected TokenizeOptions(Builder builder) { + this.interceptor = builder.interceptor; + } + + public RequestInterceptor getInterceptor() { + return interceptor; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private RequestInterceptor interceptor; + + protected Builder() {} + + public Builder interceptor(RequestInterceptor interceptor) { + this.interceptor = interceptor; + return this; + } + + public TokenizeOptions build() { + return new TokenizeOptions(this); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/vault/data/TokenizeRequest.java b/flowvault/src/main/java/com/skyflow/vault/data/TokenizeRequest.java new file mode 100644 index 00000000..e08a8f19 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/data/TokenizeRequest.java @@ -0,0 +1,34 @@ +package com.skyflow.vault.data; + +import java.util.List; + +public class TokenizeRequest { + private final List records; + + protected TokenizeRequest(List records) { + this.records = records; + } + + public static TokenizeRequestBuilder builder() { + return new TokenizeRequestBuilder(); + } + + public List getRecords() { + return this.records; + } + + public static class TokenizeRequestBuilder { + protected List records; + + protected TokenizeRequestBuilder() {} + + public TokenizeRequestBuilder records(List records) { + this.records = records; + return this; + } + + public TokenizeRequest build() { + return new TokenizeRequest(this.records); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/vault/data/TokenizeRequestRecord.java b/flowvault/src/main/java/com/skyflow/vault/data/TokenizeRequestRecord.java new file mode 100644 index 00000000..3e9094e8 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/data/TokenizeRequestRecord.java @@ -0,0 +1,59 @@ +package com.skyflow.vault.data; + +import java.util.List; + +public class TokenizeRequestRecord { + private final Object value; + private final Object token; + private final List tokenGroupNames; + + protected TokenizeRequestRecord(TokenizeRequestRecordBuilder builder) { + this.value = builder.value; + this.token = builder.token; + this.tokenGroupNames = builder.tokenGroupNames; + } + + public static TokenizeRequestRecordBuilder builder() { + return new TokenizeRequestRecordBuilder(); + } + + public Object getValue() { + return this.value; + } + + /** Bring-your-own-token value, when the caller supplies the token instead of generating one. */ + public Object getToken() { + return this.token; + } + + public List getTokenGroupNames() { + return this.tokenGroupNames; + } + + public static class TokenizeRequestRecordBuilder { + protected Object value; + protected Object token; + protected List tokenGroupNames; + + protected TokenizeRequestRecordBuilder() {} + + public TokenizeRequestRecordBuilder value(Object value) { + this.value = value; + return this; + } + + public TokenizeRequestRecordBuilder token(Object token) { + this.token = token; + return this; + } + + public TokenizeRequestRecordBuilder tokenGroupNames(List tokenGroupNames) { + this.tokenGroupNames = tokenGroupNames; + return this; + } + + public TokenizeRequestRecord build() { + return new TokenizeRequestRecord(this); + } + } +} diff --git a/flowvault/src/main/java/com/skyflow/vault/data/TokenizeResponse.java b/flowvault/src/main/java/com/skyflow/vault/data/TokenizeResponse.java new file mode 100644 index 00000000..d27f66d3 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/data/TokenizeResponse.java @@ -0,0 +1,24 @@ +package com.skyflow.vault.data; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import java.util.List; + +public class TokenizeResponse { + private final List response; + + public TokenizeResponse(List response) { + this.response = response; + } + + public List getResponse() { + return response; + } + + @Override + public String toString() { + Gson gson = new GsonBuilder().serializeNulls().create(); + return gson.toJson(this); + } +} diff --git a/flowvault/src/main/java/com/skyflow/vault/data/TokenizeResponseRecord.java b/flowvault/src/main/java/com/skyflow/vault/data/TokenizeResponseRecord.java new file mode 100644 index 00000000..84da4cef --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/data/TokenizeResponseRecord.java @@ -0,0 +1,38 @@ +package com.skyflow.vault.data; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.annotations.Expose; + +import java.util.List; + +/** + * The tokenization outcome for one input value: every requested token group is reported in + * {@code tokens}, whether it succeeded or failed. + */ +public class TokenizeResponseRecord { + @Expose(serialize = true) + private final Object value; + + @Expose(serialize = true) + private final List tokens; + + public TokenizeResponseRecord(Object value, List tokens) { + this.value = value; + this.tokens = tokens; + } + + public Object getValue() { + return value; + } + + public List getTokens() { + return tokens; + } + + @Override + public String toString() { + Gson gson = new GsonBuilder().serializeNulls().create(); + return gson.toJson(this); + } +} diff --git a/flowvault/src/main/java/com/skyflow/vault/data/TokenizeResponseToken.java b/flowvault/src/main/java/com/skyflow/vault/data/TokenizeResponseToken.java new file mode 100644 index 00000000..dff54b77 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/data/TokenizeResponseToken.java @@ -0,0 +1,72 @@ +package com.skyflow.vault.data; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.annotations.Expose; + +/** + * One token-group outcome for a single input value. {@code token} is populated on success and + * {@code error} on failure; {@code httpCode} is present on both paths. + * + *

{@code requestId} identifies the API call this outcome came from and is set only when the + * outcome is an error, since that is when it is useful for support. Bulk requests are split into + * batches, so every error from the same batch carries the same id and errors from different + * batches carry different ones. + */ +public class TokenizeResponseToken { + @Expose(serialize = true) + private final String tokenGroupName; + + @Expose(serialize = true) + private final String token; + + @Expose(serialize = true) + private final Integer httpCode; + + @Expose(serialize = true) + private final String error; + + @Expose(serialize = true) + private final String requestId; + + public TokenizeResponseToken(String tokenGroupName, String token, Integer httpCode, String error) { + this(tokenGroupName, token, httpCode, error, null); + } + + public TokenizeResponseToken(String tokenGroupName, String token, Integer httpCode, + String error, String requestId) { + this.tokenGroupName = tokenGroupName; + this.token = token; + this.httpCode = httpCode; + this.error = error; + // a successful outcome carries no request id, whatever the caller passed + this.requestId = error != null ? requestId : null; + } + + public String getTokenGroupName() { + return tokenGroupName; + } + + public String getToken() { + return token; + } + + public Integer getHttpCode() { + return httpCode; + } + + public String getError() { + return error; + } + + /** The API call this outcome came from; null unless this is an error. */ + public String getRequestId() { + return requestId; + } + + @Override + public String toString() { + Gson gson = new GsonBuilder().serializeNulls().create(); + return gson.toJson(this); + } +} diff --git a/flowvault/src/main/java/com/skyflow/vault/data/TokenizeSummary.java b/flowvault/src/main/java/com/skyflow/vault/data/TokenizeSummary.java new file mode 100644 index 00000000..b9a692c0 --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/data/TokenizeSummary.java @@ -0,0 +1,39 @@ +package com.skyflow.vault.data; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.annotations.Expose; + +public class TokenizeSummary { + @Expose(serialize = true) + private int totalTokens; + + @Expose(serialize = true) + private int totalTokenized; + + @Expose(serialize = true) + private int totalPartial; + + @Expose(serialize = true) + private int totalFailed; + + public TokenizeSummary() {} + + public TokenizeSummary(int totalTokens, int totalTokenized, int totalPartial, int totalFailed) { + this.totalTokens = totalTokens; + this.totalTokenized = totalTokenized; + this.totalPartial = totalPartial; + this.totalFailed = totalFailed; + } + + public int getTotalTokens() { return totalTokens; } + public int getTotalTokenized() { return totalTokenized; } + public int getTotalPartial() { return totalPartial; } + public int getTotalFailed() { return totalFailed; } + + @Override + public String toString() { + Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create(); + return gson.toJson(this); + } +} diff --git a/flowvault/src/main/java/com/skyflow/vault/data/UpsertOptions.java b/flowvault/src/main/java/com/skyflow/vault/data/UpsertOptions.java new file mode 100644 index 00000000..dddaedaf --- /dev/null +++ b/flowvault/src/main/java/com/skyflow/vault/data/UpsertOptions.java @@ -0,0 +1,42 @@ +package com.skyflow.vault.data; + +import java.util.List; + +public class UpsertOptions { + private final UpsertOptionsBuilder builder; + + private UpsertOptions(UpsertOptionsBuilder builder) { + this.builder = builder; + } + + public static UpsertOptionsBuilder builder() { + return new UpsertOptionsBuilder(); + } + + public String getUpdateType() { + return this.builder.updateType; + } + + public List getUniqueColumns() { + return this.builder.uniqueColumns; + } + + public static final class UpsertOptionsBuilder { + private String updateType; + private List uniqueColumns; + + public UpsertOptionsBuilder updateType(String updateType) { + this.updateType = updateType; + return this; + } + + public UpsertOptionsBuilder uniqueColumns(List uniqueColumns) { + this.uniqueColumns = uniqueColumns; + return this; + } + + public UpsertOptions build() { + return new UpsertOptions(this); + } + } +} diff --git a/flowvault/src/main/resources/sdk.properties b/flowvault/src/main/resources/sdk.properties new file mode 100644 index 00000000..a33c65cd --- /dev/null +++ b/flowvault/src/main/resources/sdk.properties @@ -0,0 +1 @@ +sdk.version=${sdk.version} diff --git a/flowvault/src/test/java/com/skyflow/AuthInterceptorTests.java b/flowvault/src/test/java/com/skyflow/AuthInterceptorTests.java new file mode 100644 index 00000000..a0f91dbb --- /dev/null +++ b/flowvault/src/test/java/com/skyflow/AuthInterceptorTests.java @@ -0,0 +1,115 @@ +package com.skyflow; + +import com.skyflow.config.Credentials; +import com.skyflow.config.VaultConfig; +import com.skyflow.enums.Env; +import com.skyflow.errors.SkyflowException; +import com.skyflow.generated.rest.core.RetryInterceptor; +import com.skyflow.utils.FakeChain; +import com.skyflow.utils.SkyflowRetryInterceptor; +import okhttp3.Interceptor; +import org.junit.Assert; +import org.junit.Test; + +import java.io.IOException; +import java.util.List; + +/** + * The auth interceptor installed by {@link VaultClient#updateExecutorInHTTP()}. + * + *

It is a lambda, so the only way to reach it is to pull it back off the built OkHttpClient and + * drive it with a fake chain. The property that matters is that it reads {@code this.token} on every + * request rather than capturing it once — that is why retry is registered outside auth, so a replayed + * attempt picks up a refreshed token instead of resending an expired one. + */ +public class AuthInterceptorTests { + + private static final String API_KEY = "sky-ab123-abcd1234cdef1234abcd4321cdef4321"; + + private static VaultConfig config() { + VaultConfig config = new VaultConfig(); + config.setVaultId("vault1"); + config.setClusterId("cluster1"); + config.setEnv(Env.DEV); + return config; + } + + private static Interceptor authInterceptorOf(VaultClient client) { + client.updateExecutorInHTTP(); + List interceptors = client.sharedHttpClient.interceptors(); + for (Interceptor interceptor : interceptors) { + if (!(interceptor instanceof SkyflowRetryInterceptor) && !(interceptor instanceof RetryInterceptor)) { + return interceptor; + } + } + throw new AssertionError("no auth interceptor installed"); + } + + @Test + public void testAuthInterceptor_addsBearerAuthorizationHeader() throws SkyflowException, IOException { + Credentials credentials = new Credentials(); + credentials.setApiKey(API_KEY); + VaultConfig config = config(); + config.setCredentials(credentials); + VaultClient client = new VaultClient(config, null); + client.setBearerToken(); + + FakeChain chain = new FakeChain(200); + authInterceptorOf(client).intercept(chain); + + Assert.assertEquals("Bearer " + API_KEY, chain.lastProceeded().header("Authorization")); + } + + @Test + public void testAuthInterceptor_readsTheTokenFreshOnEveryRequest() throws SkyflowException, IOException { + VaultClient client = new VaultClient(config(), null); + Interceptor auth = authInterceptorOf(client); + + client.token = "first-token"; + FakeChain first = new FakeChain(200); + auth.intercept(first); + + client.token = "second-token"; + FakeChain second = new FakeChain(200); + auth.intercept(second); + + Assert.assertEquals("Bearer first-token", first.lastProceeded().header("Authorization")); + Assert.assertEquals("A captured token would resend the stale value after a refresh", + "Bearer second-token", second.lastProceeded().header("Authorization")); + } + + @Test + public void testAuthInterceptor_leavesTheRestOfTheRequestAlone() throws SkyflowException, IOException { + VaultClient client = new VaultClient(config(), null); + client.token = "t"; + FakeChain chain = new FakeChain(200); + + authInterceptorOf(client).intercept(chain); + + Assert.assertEquals(chain.request().url(), chain.lastProceeded().url()); + Assert.assertEquals(chain.request().method(), chain.lastProceeded().method()); + } + + @Test + public void testAuthInterceptor_returnsTheChainResponseUntouched() throws SkyflowException, IOException { + VaultClient client = new VaultClient(config(), null); + client.token = "t"; + FakeChain chain = new FakeChain(503); + + Assert.assertEquals(503, authInterceptorOf(client).intercept(chain).code()); + Assert.assertEquals("auth must not retry - that is the outer interceptor's job", 1, chain.calls()); + } + + @Test + public void testGetQueryApi_availableAfterSetBearerToken() throws SkyflowException { + Credentials credentials = new Credentials(); + credentials.setApiKey(API_KEY); + VaultConfig config = config(); + config.setCredentials(credentials); + + VaultClient client = new VaultClient(config, null); + client.setBearerToken(); + + Assert.assertNotNull(client.getQueryApi()); + } +} diff --git a/flowvault/src/test/java/com/skyflow/HttpConfigTests.java b/flowvault/src/test/java/com/skyflow/HttpConfigTests.java new file mode 100644 index 00000000..3e0e4f7f --- /dev/null +++ b/flowvault/src/test/java/com/skyflow/HttpConfigTests.java @@ -0,0 +1,685 @@ +package com.skyflow; + +import com.skyflow.config.VaultConfig; +import com.skyflow.enums.Env; +import com.skyflow.errors.SkyflowException; +import com.skyflow.utils.SkyflowRetryInterceptor; +import com.skyflow.vault.controller.VaultController; +import okhttp3.Interceptor; +import okhttp3.OkHttpClient; +import org.junit.Assert; +import org.junit.Test; + +import java.util.List; + +/** + * Covers the HTTP timeout / retry configuration and its three-level resolution: + * vault-level override -> client-wide default -> SDK default. + */ +public class HttpConfigTests { + + // OkHttp's own defaults, applied when we leave a per-attempt timeout unset. + private static final int OKHTTP_DEFAULT_TIMEOUT_MILLIS = 10_000; + private static final int SDK_DEFAULT_CALL_TIMEOUT_MILLIS = 60_000; + + private static VaultConfig buildConfig() { + VaultConfig config = new VaultConfig(); + config.setVaultId("vault1"); + config.setClusterId("cluster1"); + config.setEnv(Env.DEV); + return config; + } + + /** Builds the shared OkHttp client without needing credentials or a live token. */ + private static OkHttpClient httpClientOf(VaultClient client) { + client.updateExecutorInHTTP(); + return client.sharedHttpClient; + } + + private static int maxRetriesOf(SkyflowRetryInterceptor interceptor) { + return interceptor.getMaxRetries(); + } + + private static SkyflowRetryInterceptor retryInterceptorOf(OkHttpClient http) { + for (Interceptor interceptor : http.interceptors()) { + if (interceptor instanceof SkyflowRetryInterceptor) { + return (SkyflowRetryInterceptor) interceptor; + } + } + throw new AssertionError("No SkyflowRetryInterceptor installed on the HTTP client"); + } + + // ── SDK defaults (neither level configured) ─────────────────────────────── + + @Test + public void testDefaults_callTimeoutIs60Seconds() throws SkyflowException { + OkHttpClient http = httpClientOf(new VaultClient(buildConfig(), null)); + + Assert.assertEquals(SDK_DEFAULT_CALL_TIMEOUT_MILLIS, http.callTimeoutMillis()); + } + + @Test + public void testDefaults_retriesAreOff() throws SkyflowException { + OkHttpClient http = httpClientOf(new VaultClient(buildConfig(), null)); + + Assert.assertEquals(0, maxRetriesOf(retryInterceptorOf(http))); + } + + @Test + public void testDefaults_perAttemptTimeoutsLeftAtOkHttpDefaults() throws SkyflowException { + OkHttpClient http = httpClientOf(new VaultClient(buildConfig(), null)); + + Assert.assertEquals(OKHTTP_DEFAULT_TIMEOUT_MILLIS, http.connectTimeoutMillis()); + Assert.assertEquals(OKHTTP_DEFAULT_TIMEOUT_MILLIS, http.readTimeoutMillis()); + Assert.assertEquals(OKHTTP_DEFAULT_TIMEOUT_MILLIS, http.writeTimeoutMillis()); + } + + // ── Client-wide values only ─────────────────────────────────────────────── + + @Test + public void testClientWideConfig_appliesWhenVaultLevelUnset() throws SkyflowException { + VaultClient client = new VaultClient(buildConfig(), null); + client.setCommonHttpConfig(30, 5, 6, 7, 2, null, null); + + OkHttpClient http = httpClientOf(client); + + Assert.assertEquals(30_000, http.callTimeoutMillis()); + Assert.assertEquals(5_000, http.connectTimeoutMillis()); + Assert.assertEquals(6_000, http.readTimeoutMillis()); + Assert.assertEquals(7_000, http.writeTimeoutMillis()); + Assert.assertEquals(2, maxRetriesOf(retryInterceptorOf(http))); + } + + // ── Vault-level values only ─────────────────────────────────────────────── + + @Test + public void testVaultLevelConfig_appliesWhenClientWideUnset() throws SkyflowException { + VaultConfig config = buildConfig(); + config.setTimeout(45); + config.setConnectTimeout(11); + config.setReadTimeout(12); + config.setWriteTimeout(13); + config.setMaxRetries(4); + + OkHttpClient http = httpClientOf(new VaultClient(config, null)); + + Assert.assertEquals(45_000, http.callTimeoutMillis()); + Assert.assertEquals(11_000, http.connectTimeoutMillis()); + Assert.assertEquals(12_000, http.readTimeoutMillis()); + Assert.assertEquals(13_000, http.writeTimeoutMillis()); + Assert.assertEquals(4, maxRetriesOf(retryInterceptorOf(http))); + } + + // ── Precedence: both levels set ─────────────────────────────────────────── + + @Test + public void testPrecedence_vaultLevelBeatsClientWideForEverySetting() throws SkyflowException { + VaultConfig config = buildConfig(); + config.setTimeout(45); + config.setConnectTimeout(11); + config.setReadTimeout(12); + config.setWriteTimeout(13); + config.setMaxRetries(4); + + VaultClient client = new VaultClient(config, null); + client.setCommonHttpConfig(30, 5, 6, 7, 2, null, null); + + OkHttpClient http = httpClientOf(client); + + Assert.assertEquals(45_000, http.callTimeoutMillis()); + Assert.assertEquals(11_000, http.connectTimeoutMillis()); + Assert.assertEquals(12_000, http.readTimeoutMillis()); + Assert.assertEquals(13_000, http.writeTimeoutMillis()); + Assert.assertEquals(4, maxRetriesOf(retryInterceptorOf(http))); + } + + @Test + public void testPrecedence_settingsResolveIndependently() throws SkyflowException { + // Vault overrides only the read timeout; everything else falls through to client-wide. + VaultConfig config = buildConfig(); + config.setReadTimeout(12); + + VaultClient client = new VaultClient(config, null); + client.setCommonHttpConfig(30, 5, 6, 7, 2, null, null); + + OkHttpClient http = httpClientOf(client); + + Assert.assertEquals(12_000, http.readTimeoutMillis()); + Assert.assertEquals(30_000, http.callTimeoutMillis()); + Assert.assertEquals(5_000, http.connectTimeoutMillis()); + Assert.assertEquals(7_000, http.writeTimeoutMillis()); + Assert.assertEquals(2, maxRetriesOf(retryInterceptorOf(http))); + } + + @Test + public void testPrecedence_sdkDefaultUsedWhenBothLevelsNull() throws SkyflowException { + VaultClient client = new VaultClient(buildConfig(), null); + client.setCommonHttpConfig(null, null, null, null, null, null, null); + + OkHttpClient http = httpClientOf(client); + + Assert.assertEquals(SDK_DEFAULT_CALL_TIMEOUT_MILLIS, http.callTimeoutMillis()); + Assert.assertEquals(0, maxRetriesOf(retryInterceptorOf(http))); + Assert.assertEquals(OKHTTP_DEFAULT_TIMEOUT_MILLIS, http.connectTimeoutMillis()); + } + + // ── Cache invalidation ──────────────────────────────────────────────────── + + @Test + public void testSetCommonHttpConfig_rebuildsHttpClientWithNewValues() throws SkyflowException { + VaultClient client = new VaultClient(buildConfig(), null); + OkHttpClient first = httpClientOf(client); + Assert.assertEquals(SDK_DEFAULT_CALL_TIMEOUT_MILLIS, first.callTimeoutMillis()); + + client.setCommonHttpConfig(30, null, null, null, 2, null, null); + OkHttpClient second = httpClientOf(client); + + Assert.assertNotSame(first, second); + Assert.assertEquals(30_000, second.callTimeoutMillis()); + Assert.assertEquals(2, maxRetriesOf(retryInterceptorOf(second))); + } + + @Test + public void testUpdateExecutorInHTTP_reusesCachedClientWhenConfigUnchanged() throws SkyflowException { + VaultClient client = new VaultClient(buildConfig(), null); + + OkHttpClient first = httpClientOf(client); + OkHttpClient second = httpClientOf(client); + + Assert.assertSame(first, second); + } + + // ── Interceptor wiring ──────────────────────────────────────────────────── + + @Test + public void testInterceptors_retryIsOuterSoEachAttemptRereadsTheToken() throws SkyflowException { + OkHttpClient http = httpClientOf(new VaultClient(buildConfig(), null)); + + List interceptors = http.interceptors(); + Assert.assertEquals(2, interceptors.size()); + Assert.assertTrue("Retry must be registered first so it wraps the auth interceptor", + interceptors.get(0) instanceof SkyflowRetryInterceptor); + Assert.assertFalse(interceptors.get(1) instanceof SkyflowRetryInterceptor); + } + + @Test + public void testConnectionPool_isConfigured() throws SkyflowException { + OkHttpClient http = httpClientOf(new VaultClient(buildConfig(), null)); + + Assert.assertNotNull(http.connectionPool()); + } + + // ── Boxed-Integer semantics ─────────────────────────────────────────────── + + @Test + public void testExplicitZeroTimeout_isAValueNotAnInheritSignal() throws SkyflowException { + // Only null means "inherit". An explicit 0 wins over the client-wide value, and OkHttp + // reads 0 as "no timeout" — so this disables the overall ceiling rather than restoring 60s. + VaultConfig config = buildConfig(); + config.setTimeout(0); + + VaultClient client = new VaultClient(config, null); + client.setCommonHttpConfig(30, null, null, null, null, null, null); + + Assert.assertEquals(0, httpClientOf(client).callTimeoutMillis()); + } + + @Test + public void testExplicitZeroMaxRetries_overridesClientWideRetries() throws SkyflowException { + VaultConfig config = buildConfig(); + config.setMaxRetries(0); + + VaultClient client = new VaultClient(config, null); + client.setCommonHttpConfig(null, null, null, null, 5, null, null); + + Assert.assertEquals(0, maxRetriesOf(retryInterceptorOf(httpClientOf(client)))); + } + + // ── Retry delays ────────────────────────────────────────────────────────── + + @Test + public void testDefaults_retryDelaysAre500And2000Millis() throws SkyflowException { + SkyflowRetryInterceptor retry = retryInterceptorOf(httpClientOf(new VaultClient(buildConfig(), null))); + + Assert.assertEquals(500L, retry.getInitialRetryDelayMillis()); + Assert.assertEquals(2000L, retry.getMaxRetryDelayMillis()); + } + + @Test + public void testRetryDelays_clientWideValuesApply() throws SkyflowException { + VaultClient client = new VaultClient(buildConfig(), null); + client.setCommonHttpConfig(null, null, null, null, 3, 100L, 900L); + + SkyflowRetryInterceptor retry = retryInterceptorOf(httpClientOf(client)); + + Assert.assertEquals(100L, retry.getInitialRetryDelayMillis()); + Assert.assertEquals(900L, retry.getMaxRetryDelayMillis()); + } + + @Test + public void testRetryDelays_vaultLevelBeatsClientWide() throws SkyflowException { + VaultConfig config = buildConfig(); + config.setInitialRetryDelayMillis(250L); + config.setMaxRetryDelayMillis(4000L); + + VaultClient client = new VaultClient(config, null); + client.setCommonHttpConfig(null, null, null, null, 3, 100L, 900L); + + SkyflowRetryInterceptor retry = retryInterceptorOf(httpClientOf(client)); + + Assert.assertEquals(250L, retry.getInitialRetryDelayMillis()); + Assert.assertEquals(4000L, retry.getMaxRetryDelayMillis()); + } + + @Test + public void testRetryDelays_resolveIndependentlyOfEachOther() throws SkyflowException { + VaultConfig config = buildConfig(); + config.setMaxRetryDelayMillis(4000L); + + VaultClient client = new VaultClient(config, null); + client.setCommonHttpConfig(null, null, null, null, 3, 100L, 900L); + + SkyflowRetryInterceptor retry = retryInterceptorOf(httpClientOf(client)); + + Assert.assertEquals(100L, retry.getInitialRetryDelayMillis()); // client-wide + Assert.assertEquals(4000L, retry.getMaxRetryDelayMillis()); // vault + } + + @Test + public void testRetryDelays_endToEndThroughTheBuilder() throws SkyflowException { + Skyflow client = Skyflow.builder() + .maxRetries(3) + .initialRetryDelayMillis(100) + .maxRetryDelayMillis(900) + .addVaultConfig(buildConfig()) + .build(); + + SkyflowRetryInterceptor retry = retryInterceptorOf(httpClientOf(client.vault())); + + Assert.assertEquals(3, retry.getMaxRetries()); + Assert.assertEquals(100L, retry.getInitialRetryDelayMillis()); + Assert.assertEquals(900L, retry.getMaxRetryDelayMillis()); + } + + @Test + public void testRetryDelays_vaultConfigBeatsBuilderEndToEnd() throws SkyflowException { + VaultConfig config = buildConfig(); + config.setInitialRetryDelayMillis(250L); + + Skyflow client = Skyflow.builder() + .initialRetryDelayMillis(100) + .addVaultConfig(config) + .build(); + + Assert.assertEquals(250L, + retryInterceptorOf(httpClientOf(client.vault())).getInitialRetryDelayMillis()); + } + + @Test + public void testRetryDelays_survivedUpdateVaultConfig() throws SkyflowException { + Skyflow.SkyflowClientBuilder builder = Skyflow.builder().addVaultConfig(buildConfig()); + + VaultConfig update = buildConfig(); + update.setInitialRetryDelayMillis(250L); + update.setMaxRetryDelayMillis(4000L); + + SkyflowRetryInterceptor retry = + retryInterceptorOf(httpClientOf(builder.updateVaultConfig(update).build().vault())); + + Assert.assertEquals(250L, retry.getInitialRetryDelayMillis()); + Assert.assertEquals(4000L, retry.getMaxRetryDelayMillis()); + } + + @Test + public void testRetryDelays_builderMethodsAreFluent() { + Skyflow.SkyflowClientBuilder builder = Skyflow.builder(); + + Assert.assertSame(builder, builder.initialRetryDelayMillis(100)); + Assert.assertSame(builder, builder.maxRetryDelayMillis(900)); + } + + // ── VaultConfig accessors ───────────────────────────────────────────────── + + @Test + public void testVaultConfig_httpSettingsDefaultToNull() { + VaultConfig config = new VaultConfig(); + + Assert.assertNull(config.getTimeout()); + Assert.assertNull(config.getConnectTimeout()); + Assert.assertNull(config.getReadTimeout()); + Assert.assertNull(config.getWriteTimeout()); + Assert.assertNull(config.getMaxRetries()); + } + + @Test + public void testVaultConfig_settersRoundTrip() { + VaultConfig config = new VaultConfig(); + config.setTimeout(45); + config.setConnectTimeout(11); + config.setReadTimeout(12); + config.setWriteTimeout(13); + config.setMaxRetries(4); + + Assert.assertEquals(Integer.valueOf(45), config.getTimeout()); + Assert.assertEquals(Integer.valueOf(11), config.getConnectTimeout()); + Assert.assertEquals(Integer.valueOf(12), config.getReadTimeout()); + Assert.assertEquals(Integer.valueOf(13), config.getWriteTimeout()); + Assert.assertEquals(Integer.valueOf(4), config.getMaxRetries()); + } + + // ── Skyflow builder wiring ──────────────────────────────────────────────── + + @Test + public void testBuilder_httpConfigSetBeforeAddVaultConfigReachesController() throws SkyflowException { + Skyflow client = Skyflow.builder() + .timeout(30) + .connectTimeout(5) + .readTimeout(6) + .writeTimeout(7) + .maxRetries(2) + .addVaultConfig(buildConfig()) + .build(); + + OkHttpClient http = httpClientOf(client.vault()); + + Assert.assertEquals(30_000, http.callTimeoutMillis()); + Assert.assertEquals(5_000, http.connectTimeoutMillis()); + Assert.assertEquals(6_000, http.readTimeoutMillis()); + Assert.assertEquals(7_000, http.writeTimeoutMillis()); + Assert.assertEquals(2, maxRetriesOf(retryInterceptorOf(http))); + } + + @Test + public void testBuilder_httpConfigSetAfterAddVaultConfigStillReachesController() throws SkyflowException { + // Order independence: propagateHttpConfig() must reach controllers already built. + Skyflow client = Skyflow.builder() + .addVaultConfig(buildConfig()) + .timeout(30) + .maxRetries(2) + .build(); + + OkHttpClient http = httpClientOf(client.vault()); + + Assert.assertEquals(30_000, http.callTimeoutMillis()); + Assert.assertEquals(2, maxRetriesOf(retryInterceptorOf(http))); + } + + @Test + public void testBuilder_vaultConfigOverridesBuilderValueEndToEnd() throws SkyflowException { + VaultConfig config = buildConfig(); + config.setTimeout(45); + + Skyflow client = Skyflow.builder() + .timeout(30) + .addVaultConfig(config) + .build(); + + Assert.assertEquals(45_000, httpClientOf(client.vault()).callTimeoutMillis()); + } + + @Test + public void testBuilder_httpConfigSurvivesUpdateVaultConfig() throws SkyflowException { + Skyflow.SkyflowClientBuilder builder = Skyflow.builder() + .timeout(30) + .maxRetries(2) + .addVaultConfig(buildConfig()); + + VaultConfig updated = buildConfig(); + updated.setClusterId("cluster2"); + Skyflow client = builder.updateVaultConfig(updated).build(); + + OkHttpClient http = httpClientOf(client.vault()); + + Assert.assertEquals(30_000, http.callTimeoutMillis()); + Assert.assertEquals(2, maxRetriesOf(retryInterceptorOf(http))); + } + + @Test + public void testBuilder_httpConfigAppliesToEveryVault() throws SkyflowException { + VaultConfig second = new VaultConfig(); + second.setVaultId("vault2"); + second.setClusterId("cluster2"); + second.setEnv(Env.DEV); + + Skyflow.SkyflowClientBuilder builder = Skyflow.builder() + .addVaultConfig(buildConfig()) + .addVaultConfig(second) + .timeout(30); + + // vault() resolves the first entry, so drop vault1 to reach the second controller. + Assert.assertEquals(30_000, httpClientOf(builder.build().vault()).callTimeoutMillis()); + Assert.assertEquals(30_000, + httpClientOf(builder.removeVaultConfig("vault1").build().vault()).callTimeoutMillis()); + } + + @Test + public void testBuilder_httpConfigMethodsAreFluent() { + Skyflow.SkyflowClientBuilder builder = Skyflow.builder(); + + Assert.assertSame(builder, builder.timeout(30)); + Assert.assertSame(builder, builder.connectTimeout(5)); + Assert.assertSame(builder, builder.readTimeout(6)); + Assert.assertSame(builder, builder.writeTimeout(7)); + Assert.assertSame(builder, builder.maxRetries(2)); + } + + // ── Precedence through the public API: both levels set ──────────────────── + // Each test sets the same setting on Skyflow.builder() AND on VaultConfig, then asserts the + // VaultConfig value is what reaches the wire client. + + @Test + public void testPrecedence_endToEnd_vaultTimeoutBeatsClientLevelTimeout() throws SkyflowException { + VaultConfig config = buildConfig(); + config.setTimeout(45); + + Skyflow client = Skyflow.builder().timeout(30).addVaultConfig(config).build(); + + Assert.assertEquals(45_000, httpClientOf(client.vault()).callTimeoutMillis()); + } + + @Test + public void testPrecedence_endToEnd_vaultConnectTimeoutBeatsClientLevel() throws SkyflowException { + VaultConfig config = buildConfig(); + config.setConnectTimeout(11); + + Skyflow client = Skyflow.builder().connectTimeout(5).addVaultConfig(config).build(); + + Assert.assertEquals(11_000, httpClientOf(client.vault()).connectTimeoutMillis()); + } + + @Test + public void testPrecedence_endToEnd_vaultReadTimeoutBeatsClientLevel() throws SkyflowException { + VaultConfig config = buildConfig(); + config.setReadTimeout(12); + + Skyflow client = Skyflow.builder().readTimeout(6).addVaultConfig(config).build(); + + Assert.assertEquals(12_000, httpClientOf(client.vault()).readTimeoutMillis()); + } + + @Test + public void testPrecedence_endToEnd_vaultWriteTimeoutBeatsClientLevel() throws SkyflowException { + VaultConfig config = buildConfig(); + config.setWriteTimeout(13); + + Skyflow client = Skyflow.builder().writeTimeout(7).addVaultConfig(config).build(); + + Assert.assertEquals(13_000, httpClientOf(client.vault()).writeTimeoutMillis()); + } + + @Test + public void testPrecedence_endToEnd_vaultMaxRetriesBeatsClientLevel() throws SkyflowException { + VaultConfig config = buildConfig(); + config.setMaxRetries(4); + + Skyflow client = Skyflow.builder().maxRetries(2).addVaultConfig(config).build(); + + Assert.assertEquals(4, maxRetriesOf(retryInterceptorOf(httpClientOf(client.vault())))); + } + + @Test + public void testPrecedence_endToEnd_allFiveSetAtBothLevels() throws SkyflowException { + VaultConfig config = buildConfig(); + config.setTimeout(45); + config.setConnectTimeout(11); + config.setReadTimeout(12); + config.setWriteTimeout(13); + config.setMaxRetries(4); + + Skyflow client = Skyflow.builder() + .timeout(30) + .connectTimeout(5) + .readTimeout(6) + .writeTimeout(7) + .maxRetries(2) + .addVaultConfig(config) + .build(); + + OkHttpClient http = httpClientOf(client.vault()); + + Assert.assertEquals(45_000, http.callTimeoutMillis()); + Assert.assertEquals(11_000, http.connectTimeoutMillis()); + Assert.assertEquals(12_000, http.readTimeoutMillis()); + Assert.assertEquals(13_000, http.writeTimeoutMillis()); + Assert.assertEquals(4, maxRetriesOf(retryInterceptorOf(http))); + } + + @Test + public void testPrecedence_endToEnd_partialOverrideTakesEachLevelPerSetting() throws SkyflowException { + // Vault overrides timeout and maxRetries only; the rest come from the client level. + VaultConfig config = buildConfig(); + config.setTimeout(45); + config.setMaxRetries(4); + + Skyflow client = Skyflow.builder() + .timeout(30) + .connectTimeout(5) + .readTimeout(6) + .writeTimeout(7) + .maxRetries(2) + .addVaultConfig(config) + .build(); + + OkHttpClient http = httpClientOf(client.vault()); + + Assert.assertEquals(45_000, http.callTimeoutMillis()); // vault + Assert.assertEquals(4, maxRetriesOf(retryInterceptorOf(http))); // vault + Assert.assertEquals(5_000, http.connectTimeoutMillis()); // client + Assert.assertEquals(6_000, http.readTimeoutMillis()); // client + Assert.assertEquals(7_000, http.writeTimeoutMillis()); // client + } + + @Test + public void testPrecedence_endToEnd_holdsWhenClientLevelIsSetAfterAddVaultConfig() throws SkyflowException { + // Builder call order must not change who wins. + VaultConfig config = buildConfig(); + config.setTimeout(45); + + Skyflow client = Skyflow.builder().addVaultConfig(config).timeout(30).build(); + + Assert.assertEquals(45_000, httpClientOf(client.vault()).callTimeoutMillis()); + } + + @Test + public void testPrecedence_endToEnd_appliesPerVaultNotGlobally() throws SkyflowException { + // vault1 overrides the timeout; vault2 leaves it unset and inherits the client-level value. + VaultConfig overriding = buildConfig(); + overriding.setTimeout(45); + + VaultConfig inheriting = new VaultConfig(); + inheriting.setVaultId("vault2"); + inheriting.setClusterId("cluster2"); + inheriting.setEnv(Env.DEV); + + Skyflow.SkyflowClientBuilder builder = Skyflow.builder() + .timeout(30) + .addVaultConfig(overriding) + .addVaultConfig(inheriting); + + // vault() resolves the first entry, so drop vault1 to reach the second controller. + Assert.assertEquals(45_000, httpClientOf(builder.build().vault()).callTimeoutMillis()); + Assert.assertEquals(30_000, + httpClientOf(builder.removeVaultConfig("vault1").build().vault()).callTimeoutMillis()); + } + + @Test + public void testPrecedence_endToEnd_vaultStillWinsAfterUpdateVaultConfig() throws SkyflowException { + VaultConfig config = buildConfig(); + config.setTimeout(45); + Skyflow.SkyflowClientBuilder builder = Skyflow.builder().timeout(30).addVaultConfig(config); + + VaultConfig update = buildConfig(); + update.setTimeout(90); + + Assert.assertEquals(90_000, + httpClientOf(builder.updateVaultConfig(update).build().vault()).callTimeoutMillis()); + } + + @Test + public void testPrecedence_endToEnd_updateWithoutHttpSettingsKeepsTheExistingOnes() throws SkyflowException { + // A null on the incoming config means "leave as is", matching how the base class merges. + VaultConfig config = buildConfig(); + config.setTimeout(45); + config.setMaxRetries(4); + Skyflow.SkyflowClientBuilder builder = Skyflow.builder().timeout(30).addVaultConfig(config); + + VaultConfig update = buildConfig(); + update.setClusterId("cluster2"); + + OkHttpClient http = httpClientOf(builder.updateVaultConfig(update).build().vault()); + + Assert.assertEquals(45_000, http.callTimeoutMillis()); + Assert.assertEquals(4, maxRetriesOf(retryInterceptorOf(http))); + } + + @Test + public void testPrecedence_endToEnd_updateVaultConfigCarriesAllFiveHttpSettings() throws SkyflowException { + // Regression: BaseSkyflow.mergeVaultConfig() carries only env/clusterId/credentials, so + // without SkyflowClientBuilder.carryHttpOverrides() every value below is silently dropped + // and the vault keeps running on its original settings. + VaultConfig config = buildConfig(); + config.setTimeout(45); + config.setConnectTimeout(11); + config.setReadTimeout(12); + config.setWriteTimeout(13); + config.setMaxRetries(4); + Skyflow.SkyflowClientBuilder builder = Skyflow.builder().addVaultConfig(config); + + VaultConfig update = buildConfig(); + update.setTimeout(90); + update.setConnectTimeout(21); + update.setReadTimeout(22); + update.setWriteTimeout(23); + update.setMaxRetries(8); + + OkHttpClient http = httpClientOf(builder.updateVaultConfig(update).build().vault()); + + Assert.assertEquals(90_000, http.callTimeoutMillis()); + Assert.assertEquals(21_000, http.connectTimeoutMillis()); + Assert.assertEquals(22_000, http.readTimeoutMillis()); + Assert.assertEquals(23_000, http.writeTimeoutMillis()); + Assert.assertEquals(8, maxRetriesOf(retryInterceptorOf(http))); + } + + @Test + public void testPrecedence_endToEnd_updateCanIntroduceAVaultOverride() throws SkyflowException { + // Vault starts with no override (inherits 30), then an update introduces one. + Skyflow.SkyflowClientBuilder builder = Skyflow.builder().timeout(30).addVaultConfig(buildConfig()); + Assert.assertEquals(30_000, httpClientOf(builder.build().vault()).callTimeoutMillis()); + + VaultConfig update = buildConfig(); + update.setTimeout(45); + + Assert.assertEquals(45_000, + httpClientOf(builder.updateVaultConfig(update).build().vault()).callTimeoutMillis()); + } + + @Test + public void testController_isAVaultClientSoItInheritsTheHttpConfig() throws SkyflowException { + Skyflow client = Skyflow.builder().timeout(30).addVaultConfig(buildConfig()).build(); + + VaultController controller = client.vault(); + + Assert.assertEquals(30_000, httpClientOf(controller).callTimeoutMillis()); + } +} diff --git a/flowvault/src/test/java/com/skyflow/SkyflowTests.java b/flowvault/src/test/java/com/skyflow/SkyflowTests.java new file mode 100644 index 00000000..062989cc --- /dev/null +++ b/flowvault/src/test/java/com/skyflow/SkyflowTests.java @@ -0,0 +1,379 @@ +package com.skyflow; + +import com.skyflow.config.Credentials; +import com.skyflow.config.VaultConfig; +import com.skyflow.enums.Env; +import com.skyflow.enums.LogLevel; +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.controller.VaultController; +import org.junit.Assert; +import org.junit.Test; + +public class SkyflowTests { + private static final String EXCEPTION_NOT_THROWN = "Should have thrown an exception"; + private static final String INVALID_EXCEPTION_THROWN = "Should not have thrown any exception"; + + private static VaultConfig buildConfig(String vaultId, String clusterId) { + VaultConfig config = new VaultConfig(); + config.setVaultId(vaultId); + config.setClusterId(clusterId); + config.setEnv(Env.DEV); + return config; + } + + // ── addVaultConfig ──────────────────────────────────────────────────────── + + @Test + public void testAddVaultConfig_success() throws SkyflowException { + Skyflow client = Skyflow.builder().addVaultConfig(buildConfig("vault1", "cluster1")).build(); + Assert.assertEquals("vault1", client.getVaultConfig("vault1").getVaultId()); + } + + @Test + public void testAddVaultConfig_duplicateVaultIdThrows() throws SkyflowException { + Skyflow.SkyflowClientBuilder builder = Skyflow.builder().addVaultConfig(buildConfig("vault1", "cluster1")); + try { + builder.addVaultConfig(buildConfig("vault1", "cluster2")); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testAddVaultConfig_invalidConfigThrows() { + VaultConfig config = new VaultConfig(); + // no vaultId set + try { + Skyflow.builder().addVaultConfig(config); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + // ── updateVaultConfig: flowvault-specific fields ───────────────────────── + // BaseSkyflow.mergeVaultConfig() only carries env/clusterId/credentials, so vaultUrl needs + // SkyflowClientBuilder.carryVaultOverrides() to survive an update. + + @Test + public void testUpdateVaultConfig_changesVaultURL() throws SkyflowException { + VaultConfig config = buildConfig("vault1", "cluster1"); + config.setVaultUrl("https://first.example.com"); + Skyflow.SkyflowClientBuilder builder = Skyflow.builder().addVaultConfig(config); + Assert.assertEquals("https://first.example.com", builder.build().vault().currentVaultURL); + + VaultConfig update = buildConfig("vault1", "cluster1"); + update.setVaultUrl("https://second.example.com"); + + Assert.assertEquals("https://second.example.com", + builder.updateVaultConfig(update).build().vault().currentVaultURL); + } + + @Test + public void testUpdateVaultConfig_storesTheNewVaultURLOnTheConfig() throws SkyflowException { + VaultConfig config = buildConfig("vault1", "cluster1"); + config.setVaultUrl("https://first.example.com"); + Skyflow.SkyflowClientBuilder builder = Skyflow.builder().addVaultConfig(config); + + VaultConfig update = buildConfig("vault1", "cluster1"); + update.setVaultUrl("https://second.example.com"); + + Assert.assertEquals("https://second.example.com", + builder.updateVaultConfig(update).build().getVaultConfig("vault1").getVaultUrl()); + } + + @Test + public void testUpdateVaultConfig_omittingVaultURLKeepsTheExistingOne() throws SkyflowException { + VaultConfig config = buildConfig("vault1", "cluster1"); + config.setVaultUrl("https://first.example.com"); + Skyflow.SkyflowClientBuilder builder = Skyflow.builder().addVaultConfig(config); + + // No vaultUrl on the update: null means "leave as is", as elsewhere in the merge. + Skyflow client = builder.updateVaultConfig(buildConfig("vault1", "cluster2")).build(); + + Assert.assertEquals("https://first.example.com", client.vault().currentVaultURL); + } + + @Test + public void testUpdateVaultConfig_canIntroduceAVaultURLWhereClusterIdWasUsed() throws SkyflowException { + Skyflow.SkyflowClientBuilder builder = Skyflow.builder().addVaultConfig(buildConfig("vault1", "cluster1")); + Assert.assertEquals("https://cluster1.skyvault.skyflowapis.dev", builder.build().vault().currentVaultURL); + + VaultConfig update = buildConfig("vault1", "cluster1"); + update.setVaultUrl("https://explicit.example.com"); + + Assert.assertEquals("https://explicit.example.com", + builder.updateVaultConfig(update).build().vault().currentVaultURL); + } + + @Test + public void testUpdateVaultConfig_clusterIdChangeStillRebuildsTheURL() throws SkyflowException { + Skyflow.SkyflowClientBuilder builder = Skyflow.builder().addVaultConfig(buildConfig("vault1", "cluster1")); + + Skyflow client = builder.updateVaultConfig(buildConfig("vault1", "cluster2")).build(); + + Assert.assertEquals("https://cluster2.skyvault.skyflowapis.dev", client.vault().currentVaultURL); + } + + // ── updateVaultConfig ───────────────────────────────────────────────────── + + @Test + public void testUpdateVaultConfig_success() throws SkyflowException { + Skyflow client = Skyflow.builder().addVaultConfig(buildConfig("vault1", "cluster1")).build(); + + VaultConfig update = new VaultConfig(); + update.setVaultId("vault1"); + update.setClusterId("cluster2"); + update.setEnv(Env.PROD); + client.updateVaultConfig(update); + + Assert.assertEquals("cluster2", client.getVaultConfig("vault1").getClusterId()); + Assert.assertEquals(Env.PROD, client.getVaultConfig("vault1").getEnv()); + } + + @Test + public void testUpdateVaultConfig_nonExistentVaultIdThrows() { + try { + Skyflow.builder().updateVaultConfig(buildConfig("vault-unknown", "cluster1")); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + // ── removeVaultConfig ───────────────────────────────────────────────────── + + @Test + public void testRemoveVaultConfig_success() throws SkyflowException { + Skyflow client = Skyflow.builder().addVaultConfig(buildConfig("vault1", "cluster1")).build(); + client.removeVaultConfig("vault1"); + Assert.assertNull(client.getVaultConfig("vault1")); + } + + @Test + public void testRemoveVaultConfig_nonExistentVaultIdThrows() { + try { + Skyflow.builder().removeVaultConfig("vault-unknown"); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + // ── Client management lifecycles ───────────────────────────────────────── + // Whole add/update/remove sequences, asserting both the stored config and the controller + // behind vault() stay in step at every stage. + + private static final String NOT_IN_CONFIG_LIST = "VaultId is missing from the config"; + + @Test + public void testLifecycle_addThenRemove() throws SkyflowException { + Skyflow.SkyflowClientBuilder builder = Skyflow.builder().addVaultConfig(buildConfig("vault1", "cluster1")); + + Skyflow afterAdd = builder.build(); + Assert.assertEquals("cluster1", afterAdd.getVaultConfig("vault1").getClusterId()); + Assert.assertNotNull(afterAdd.vault()); + + Skyflow afterRemove = builder.removeVaultConfig("vault1").build(); + + Assert.assertNull(afterRemove.getVaultConfig("vault1")); + try { + afterRemove.vault(); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertTrue(e.getMessage().contains(NOT_IN_CONFIG_LIST)); + } + } + + @Test + public void testLifecycle_addThenRemoveThenReAddSameVaultId() throws SkyflowException { + // Removing must clear the id, otherwise re-adding would trip the duplicate check. + Skyflow.SkyflowClientBuilder builder = Skyflow.builder() + .addVaultConfig(buildConfig("vault1", "cluster1")) + .removeVaultConfig("vault1") + .addVaultConfig(buildConfig("vault1", "cluster2")); + + Skyflow client = builder.build(); + + Assert.assertEquals("cluster2", client.getVaultConfig("vault1").getClusterId()); + Assert.assertEquals("https://cluster2.skyvault.skyflowapis.dev", client.vault().currentVaultURL); + } + + @Test + public void testLifecycle_addRemoveThenUpdateFails() throws SkyflowException { + Skyflow.SkyflowClientBuilder builder = Skyflow.builder() + .addVaultConfig(buildConfig("vault1", "cluster1")) + .removeVaultConfig("vault1"); + + try { + builder.updateVaultConfig(buildConfig("vault1", "cluster2")); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertTrue(e.getMessage().contains(NOT_IN_CONFIG_LIST)); + } + } + + @Test + public void testLifecycle_addRemoveThenFailedUpdateLeavesNoConfigBehind() throws SkyflowException { + // A rejected update must not resurrect the removed vault. + Skyflow.SkyflowClientBuilder builder = Skyflow.builder() + .addVaultConfig(buildConfig("vault1", "cluster1")) + .removeVaultConfig("vault1"); + try { + builder.updateVaultConfig(buildConfig("vault1", "cluster2")); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException expected) { + // asserted in testLifecycle_addRemoveThenUpdateFails + } + + Skyflow client = builder.build(); + + Assert.assertNull(client.getVaultConfig("vault1")); + try { + client.vault(); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertTrue(e.getMessage().contains(NOT_IN_CONFIG_LIST)); + } + } + + @Test + public void testLifecycle_addUpdateThenRemove() throws SkyflowException { + Skyflow.SkyflowClientBuilder builder = Skyflow.builder().addVaultConfig(buildConfig("vault1", "cluster1")); + + VaultConfig update = buildConfig("vault1", "cluster2"); + update.setEnv(Env.PROD); + Skyflow afterUpdate = builder.updateVaultConfig(update).build(); + + Assert.assertEquals("cluster2", afterUpdate.getVaultConfig("vault1").getClusterId()); + Assert.assertEquals(Env.PROD, afterUpdate.getVaultConfig("vault1").getEnv()); + Assert.assertEquals("https://cluster2.skyvault.skyflowapis.com", afterUpdate.vault().currentVaultURL); + + Skyflow afterRemove = builder.removeVaultConfig("vault1").build(); + + Assert.assertNull(afterRemove.getVaultConfig("vault1")); + try { + afterRemove.vault(); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertTrue(e.getMessage().contains(NOT_IN_CONFIG_LIST)); + } + } + + @Test + public void testLifecycle_addUpdateRemoveThenRemoveAgainFails() throws SkyflowException { + Skyflow.SkyflowClientBuilder builder = Skyflow.builder() + .addVaultConfig(buildConfig("vault1", "cluster1")) + .updateVaultConfig(buildConfig("vault1", "cluster2")) + .removeVaultConfig("vault1"); + + try { + builder.removeVaultConfig("vault1"); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertTrue(e.getMessage().contains(NOT_IN_CONFIG_LIST)); + } + } + + @Test + public void testLifecycle_removingOneVaultLeavesTheOtherIntact() throws SkyflowException { + Skyflow client = Skyflow.builder() + .addVaultConfig(buildConfig("vault1", "cluster1")) + .addVaultConfig(buildConfig("vault2", "cluster2")) + .removeVaultConfig("vault1") + .build(); + + Assert.assertNull(client.getVaultConfig("vault1")); + Assert.assertEquals("cluster2", client.getVaultConfig("vault2").getClusterId()); + Assert.assertEquals("https://cluster2.skyvault.skyflowapis.dev", client.vault().currentVaultURL); + } + + @Test + public void testLifecycle_onTheBuiltClientRatherThanTheBuilder() throws SkyflowException { + // Skyflow exposes the same add/update/remove surface as the builder; exercise that path too. + Skyflow client = Skyflow.builder().addVaultConfig(buildConfig("vault1", "cluster1")).build(); + + client.updateVaultConfig(buildConfig("vault1", "cluster2")); + Assert.assertEquals("cluster2", client.getVaultConfig("vault1").getClusterId()); + + client.removeVaultConfig("vault1"); + Assert.assertNull(client.getVaultConfig("vault1")); + + try { + client.updateVaultConfig(buildConfig("vault1", "cluster3")); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertTrue(e.getMessage().contains(NOT_IN_CONFIG_LIST)); + } + } + + // ── addSkyflowCredentials ───────────────────────────────────────────────── + + @Test + public void testAddSkyflowCredentials_success() throws SkyflowException { + Credentials credentials = new Credentials(); + credentials.setToken("some-token"); + Skyflow client = Skyflow.builder() + .addVaultConfig(buildConfig("vault1", "cluster1")) + .addSkyflowCredentials(credentials) + .build(); + Assert.assertNotNull(client); + } + + @Test + public void testAddSkyflowCredentials_invalidCredentialsThrows() { + Credentials credentials = new Credentials(); + credentials.setApiKey("not-a-valid-api-key"); + try { + Skyflow.builder().addSkyflowCredentials(credentials); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + // ── setLogLevel / getLogLevel ───────────────────────────────────────────── + + @Test + public void testSetLogLevel_updatesLogLevel() throws SkyflowException { + Skyflow client = Skyflow.builder() + .addVaultConfig(buildConfig("vault1", "cluster1")) + .setLogLevel(LogLevel.DEBUG) + .build(); + Assert.assertEquals(LogLevel.DEBUG, client.getLogLevel()); + } + + @Test + public void testGetLogLevel_defaultsToError() throws SkyflowException { + Skyflow client = Skyflow.builder().addVaultConfig(buildConfig("vault1", "cluster1")).build(); + Assert.assertEquals(LogLevel.ERROR, client.getLogLevel()); + } + + // ── vault() ─────────────────────────────────────────────────────────────── + + @Test + public void testVault_returnsVaultControllerWhenConfigured() throws SkyflowException { + Skyflow client = Skyflow.builder().addVaultConfig(buildConfig("vault1", "cluster1")).build(); + VaultController controller = client.vault(); + Assert.assertNotNull(controller); + } + + @Test + public void testVault_throwsWhenNoConfigExists() { + try { + Skyflow.builder().build().vault(); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + // ── getVaultConfig ──────────────────────────────────────────────────────── + + @Test + public void testGetVaultConfig_returnsNullForUnknownVaultId() throws SkyflowException { + Skyflow client = Skyflow.builder().addVaultConfig(buildConfig("vault1", "cluster1")).build(); + Assert.assertNull(client.getVaultConfig("vault-unknown")); + } +} diff --git a/flowvault/src/test/java/com/skyflow/UpdatePropagationTests.java b/flowvault/src/test/java/com/skyflow/UpdatePropagationTests.java new file mode 100644 index 00000000..db0d28db --- /dev/null +++ b/flowvault/src/test/java/com/skyflow/UpdatePropagationTests.java @@ -0,0 +1,268 @@ +package com.skyflow; + +import com.skyflow.config.Credentials; +import com.skyflow.config.VaultConfig; +import com.skyflow.enums.Env; +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.controller.VaultController; +import org.junit.Assert; +import org.junit.Test; + +/** + * Verifies that an update reaches every place the value is actually consumed — the stored config, + * the controller holding it, and everything derived from it (vault URL, HTTP client, bearer token) + * — rather than only the copy in vaultConfigMap. + */ +public class UpdatePropagationTests { + + private static VaultConfig buildConfig(String vaultId, String clusterId) { + VaultConfig config = new VaultConfig(); + config.setVaultId(vaultId); + config.setClusterId(clusterId); + config.setEnv(Env.DEV); + return config; + } + + private static Credentials tokenCredentials(String token) { + Credentials credentials = new Credentials(); + credentials.setToken(token); + return credentials; + } + + // ── updateVaultConfig reaches the controller, not just the stored config ── + + @Test + public void testUpdateVaultConfig_storedConfigAndControllerConfigAgree() throws SkyflowException { + Skyflow.SkyflowClientBuilder builder = Skyflow.builder().addVaultConfig(buildConfig("vault1", "cluster1")); + + VaultConfig update = buildConfig("vault1", "cluster2"); + update.setEnv(Env.PROD); + Skyflow client = builder.updateVaultConfig(update).build(); + + // The copy the user can read back... + Assert.assertEquals("cluster2", client.getVaultConfig("vault1").getClusterId()); + Assert.assertEquals(Env.PROD, client.getVaultConfig("vault1").getEnv()); + // ...and the copy the controller actually builds requests from. + Assert.assertEquals("cluster2", client.vault().getVaultConfig().getClusterId()); + Assert.assertEquals(Env.PROD, client.vault().getVaultConfig().getEnv()); + } + + @Test + public void testUpdateVaultConfig_reconfiguresTheControllerInPlace() throws SkyflowException { + Skyflow.SkyflowClientBuilder builder = Skyflow.builder().addVaultConfig(buildConfig("vault1", "cluster1")); + VaultController before = builder.build().vault(); + + VaultController after = builder.updateVaultConfig(buildConfig("vault1", "cluster2")).build().vault(); + + Assert.assertSame(before, after); + } + + @Test + public void testUpdateVaultConfig_handleHeldAcrossTheUpdateSeesTheNewConfig() throws SkyflowException { + // Replacing the controller instead of reconfiguring it would leave this reference talking + // to the old vault, with no error to say so. + Skyflow.SkyflowClientBuilder builder = Skyflow.builder().addVaultConfig(buildConfig("vault1", "cluster1")); + VaultController held = builder.build().vault(); + Assert.assertEquals("https://cluster1.skyvault.skyflowapis.dev", held.currentVaultURL); + + VaultConfig update = buildConfig("vault1", "cluster2"); + update.setEnv(Env.PROD); + builder.updateVaultConfig(update); + + Assert.assertEquals("cluster2", held.getVaultConfig().getClusterId()); + Assert.assertEquals(Env.PROD, held.getVaultConfig().getEnv()); + Assert.assertEquals("https://cluster2.skyvault.skyflowapis.com", held.currentVaultURL); + } + + @Test + public void testUpdateVaultConfig_handleHeldAcrossTheUpdateSeesNewCredentials() throws SkyflowException { + VaultConfig config = buildConfig("vault1", "cluster1"); + config.setCredentials(tokenCredentials("first-token")); + Skyflow.SkyflowClientBuilder builder = Skyflow.builder().addVaultConfig(config); + VaultController held = builder.build().vault(); + held.setBearerToken(); + Assert.assertEquals("first-token", held.token); + + VaultConfig update = buildConfig("vault1", "cluster1"); + update.setCredentials(tokenCredentials("second-token")); + builder.updateVaultConfig(update); + held.setBearerToken(); + + Assert.assertEquals("second-token", held.token); + } + + @Test + public void testUpdateVaultConfig_handleHeldAcrossTheUpdateSeesNewHttpSettings() throws SkyflowException { + Skyflow.SkyflowClientBuilder builder = Skyflow.builder().addVaultConfig(buildConfig("vault1", "cluster1")); + VaultController held = builder.build().vault(); + held.updateExecutorInHTTP(); + Assert.assertEquals(60_000, held.sharedHttpClient.callTimeoutMillis()); + + VaultConfig update = buildConfig("vault1", "cluster1"); + update.setTimeout(45); + builder.updateVaultConfig(update); + held.updateExecutorInHTTP(); + + Assert.assertEquals(45_000, held.sharedHttpClient.callTimeoutMillis()); + } + + @Test + public void testUpdateVaultConfig_derivedVaultURLIsRebuilt() throws SkyflowException { + Skyflow.SkyflowClientBuilder builder = Skyflow.builder().addVaultConfig(buildConfig("vault1", "cluster1")); + Assert.assertEquals("https://cluster1.skyvault.skyflowapis.dev", builder.build().vault().currentVaultURL); + + VaultConfig update = buildConfig("vault1", "cluster2"); + update.setEnv(Env.PROD); + + Assert.assertEquals("https://cluster2.skyvault.skyflowapis.com", + builder.updateVaultConfig(update).build().vault().currentVaultURL); + } + + @Test + public void testUpdateVaultConfig_newVaultLevelCredentialsReachTheToken() throws SkyflowException { + VaultConfig config = buildConfig("vault1", "cluster1"); + config.setCredentials(tokenCredentials("first-token")); + Skyflow.SkyflowClientBuilder builder = Skyflow.builder().addVaultConfig(config); + + VaultController before = builder.build().vault(); + before.setBearerToken(); + Assert.assertEquals("first-token", before.token); + + VaultConfig update = buildConfig("vault1", "cluster1"); + update.setCredentials(tokenCredentials("second-token")); + VaultController after = builder.updateVaultConfig(update).build().vault(); + after.setBearerToken(); + + Assert.assertEquals("second-token", after.token); + } + + @Test + public void testUpdateVaultConfig_omittingCredentialsKeepsTheExistingOnes() throws SkyflowException { + VaultConfig config = buildConfig("vault1", "cluster1"); + config.setCredentials(tokenCredentials("first-token")); + Skyflow.SkyflowClientBuilder builder = Skyflow.builder().addVaultConfig(config); + + // No credentials on the update: null means "leave as is". + VaultController after = builder.updateVaultConfig(buildConfig("vault1", "cluster2")).build().vault(); + after.setBearerToken(); + + Assert.assertEquals("first-token", after.token); + } + + @Test + public void testUpdateVaultConfig_stillHasTheClientWideCredentialsAfterwards() throws SkyflowException { + Skyflow.SkyflowClientBuilder builder = Skyflow.builder() + .addSkyflowCredentials(tokenCredentials("common-token")) + .addVaultConfig(buildConfig("vault1", "cluster1")); + + // Reconfiguring must not clear the client-wide credentials the controller already holds. + VaultController after = builder.updateVaultConfig(buildConfig("vault1", "cluster2")).build().vault(); + after.setBearerToken(); + + Assert.assertEquals("common-token", after.token); + } + + @Test + public void testUpdateVaultConfig_vaultLevelCredentialsStillBeatClientWide() throws SkyflowException { + VaultConfig config = buildConfig("vault1", "cluster1"); + config.setCredentials(tokenCredentials("vault-token")); + Skyflow.SkyflowClientBuilder builder = Skyflow.builder() + .addSkyflowCredentials(tokenCredentials("common-token")) + .addVaultConfig(config); + + VaultController after = builder.updateVaultConfig(buildConfig("vault1", "cluster2")).build().vault(); + after.setBearerToken(); + + Assert.assertEquals("vault-token", after.token); + } + + // ── Credentials updates reach every controller ─────────────────────────── + + @Test + public void testAddSkyflowCredentials_afterVaultExists_reachesTheController() throws SkyflowException { + Skyflow.SkyflowClientBuilder builder = Skyflow.builder().addVaultConfig(buildConfig("vault1", "cluster1")); + + VaultController controller = builder.addSkyflowCredentials(tokenCredentials("common-token")).build().vault(); + controller.setBearerToken(); + + Assert.assertEquals("common-token", controller.token); + } + + @Test + public void testAddSkyflowCredentials_beforeVaultExists_reachesTheNewController() throws SkyflowException { + Skyflow client = Skyflow.builder() + .addSkyflowCredentials(tokenCredentials("common-token")) + .addVaultConfig(buildConfig("vault1", "cluster1")) + .build(); + + VaultController controller = client.vault(); + controller.setBearerToken(); + + Assert.assertEquals("common-token", controller.token); + } + + @Test + public void testAddSkyflowCredentials_reachesEveryVaultNotJustTheFirst() throws SkyflowException { + Skyflow.SkyflowClientBuilder builder = Skyflow.builder() + .addVaultConfig(buildConfig("vault1", "cluster1")) + .addVaultConfig(buildConfig("vault2", "cluster2")) + .addSkyflowCredentials(tokenCredentials("common-token")); + + VaultController first = builder.build().vault(); + first.setBearerToken(); + Assert.assertEquals("common-token", first.token); + + // vault() resolves the first entry, so drop vault1 to reach the second controller. + VaultController second = builder.removeVaultConfig("vault1").build().vault(); + second.setBearerToken(); + Assert.assertEquals("common-token", second.token); + } + + @Test + public void testUpdateSkyflowCredentials_invalidatesTheCachedToken() throws SkyflowException { + Skyflow client = Skyflow.builder() + .addSkyflowCredentials(tokenCredentials("first-token")) + .addVaultConfig(buildConfig("vault1", "cluster1")) + .build(); + + VaultController controller = client.vault(); + controller.setBearerToken(); + Assert.assertEquals("first-token", controller.token); + + client.updateSkyflowCredentials(tokenCredentials("second-token")); + controller.setBearerToken(); + + Assert.assertEquals("A cached token must not survive a credentials change", + "second-token", controller.token); + } + + @Test + public void testUpdateSkyflowCredentials_doesNotOverrideVaultLevelCredentials() throws SkyflowException { + VaultConfig config = buildConfig("vault1", "cluster1"); + config.setCredentials(tokenCredentials("vault-token")); + Skyflow client = Skyflow.builder().addVaultConfig(config).build(); + + client.updateSkyflowCredentials(tokenCredentials("common-token")); + VaultController controller = client.vault(); + controller.setBearerToken(); + + Assert.assertEquals("vault-token", controller.token); + } + + @Test + public void testUpdateSkyflowCredentials_appliesToTheSameControllerInstance() throws SkyflowException { + // Unlike updateVaultConfig, a credentials change must not swap the controller out — it + // reconfigures the existing one, so a handle the caller already holds stays valid. + Skyflow client = Skyflow.builder() + .addSkyflowCredentials(tokenCredentials("first-token")) + .addVaultConfig(buildConfig("vault1", "cluster1")) + .build(); + VaultController held = client.vault(); + + client.updateSkyflowCredentials(tokenCredentials("second-token")); + + Assert.assertSame(held, client.vault()); + held.setBearerToken(); + Assert.assertEquals("second-token", held.token); + } +} diff --git a/flowvault/src/test/java/com/skyflow/VaultClientTests.java b/flowvault/src/test/java/com/skyflow/VaultClientTests.java new file mode 100644 index 00000000..68f026fb --- /dev/null +++ b/flowvault/src/test/java/com/skyflow/VaultClientTests.java @@ -0,0 +1,130 @@ +package com.skyflow; + +import com.skyflow.config.Credentials; +import com.skyflow.config.VaultConfig; +import com.skyflow.enums.Env; +import com.skyflow.errors.SkyflowException; +import org.junit.Assert; +import org.junit.Test; + +public class VaultClientTests { + + private static VaultConfig buildConfig(String vaultId, String clusterId, Credentials credentials) { + VaultConfig config = new VaultConfig(); + config.setVaultId(vaultId); + config.setClusterId(clusterId); + config.setEnv(Env.DEV); + if (credentials != null) { + config.setCredentials(credentials); + } + return config; + } + + // ── updateVaultUrl priority order ──────────────────────────────────────── + + @Test + public void testUpdateVaultURL_usesExplicitVaultURLOverClusterId() throws SkyflowException { + VaultConfig config = buildConfig("vault1", "cluster1", null); + config.setVaultUrl("https://custom.example.com"); + + VaultClient client = new VaultClient(config, null); + + Assert.assertEquals("https://custom.example.com", client.currentVaultURL); + } + + @Test + public void testUpdateVaultURL_constructsFromClusterIdWhenNoVaultURL() throws SkyflowException { + VaultConfig config = buildConfig("vault1", "cluster1", null); + + VaultClient client = new VaultClient(config, null); + + Assert.assertEquals("https://cluster1.skyvault.skyflowapis.dev", client.currentVaultURL); + } + + // ── setBearerToken ──────────────────────────────────────────────────────── + + @Test + public void testSetBearerToken_usesApiKeyDirectly() throws SkyflowException { + Credentials creds = new Credentials(); + creds.setApiKey("sky-ab123-abcd1234cdef1234abcd4321cdef4321"); + VaultConfig config = buildConfig("vault1", "cluster1", creds); + + VaultClient client = new VaultClient(config, null); + client.setBearerToken(); + + Assert.assertEquals("sky-ab123-abcd1234cdef1234abcd4321cdef4321", client.token); + } + + @Test + public void testSetBearerToken_reusesNonExpiredToken() throws SkyflowException { + Credentials creds = new Credentials(); + creds.setToken("x.eyJleHAiOjk5OTk5OTk5OTl9.y"); + VaultConfig config = buildConfig("vault1", "cluster1", creds); + + VaultClient client = new VaultClient(config, null); + client.setBearerToken(); + String firstToken = client.token; + client.setBearerToken(); + + Assert.assertEquals(firstToken, client.token); + } + + @Test + public void testSetBearerToken_invalidCredentialsThrows() { + Credentials creds = new Credentials(); + creds.setApiKey("not-a-valid-api-key"); + VaultConfig config = buildConfig("vault1", "cluster1", creds); + + try { + VaultClient client = new VaultClient(config, null); + client.setBearerToken(); + Assert.fail("Should have thrown an exception"); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + // ── getRecordsApi ───────────────────────────────────────────────────────── + + @Test + public void testGetRecordsApi_availableAfterSetBearerToken() throws SkyflowException { + Credentials creds = new Credentials(); + creds.setApiKey("sky-ab123-abcd1234cdef1234abcd4321cdef4321"); + VaultConfig config = buildConfig("vault1", "cluster1", creds); + + VaultClient client = new VaultClient(config, null); + client.setBearerToken(); + + Assert.assertNotNull(client.getRecordsApi()); + } + + // ── setCommonCredentials ────────────────────────────────────────────────── + + @Test + public void testSetCommonCredentials_vaultSpecificCredentialsTakePriority() throws SkyflowException { + Credentials vaultCreds = new Credentials(); + vaultCreds.setToken("vault-specific-token"); + VaultConfig config = buildConfig("vault1", "cluster1", vaultCreds); + + VaultClient client = new VaultClient(config, null); + Credentials commonCreds = new Credentials(); + commonCreds.setToken("common-token"); + client.setCommonCredentials(commonCreds); + client.setBearerToken(); + + Assert.assertEquals("vault-specific-token", client.token); + } + + @Test + public void testSetCommonCredentials_usedWhenNoVaultSpecificCredentials() throws SkyflowException { + VaultConfig config = buildConfig("vault1", "cluster1", null); + + VaultClient client = new VaultClient(config, null); + Credentials commonCreds = new Credentials(); + commonCreds.setToken("common-token"); + client.setCommonCredentials(commonCreds); + client.setBearerToken(); + + Assert.assertEquals("common-token", client.token); + } +} diff --git a/flowvault/src/test/java/com/skyflow/config/VaultConfigTests.java b/flowvault/src/test/java/com/skyflow/config/VaultConfigTests.java new file mode 100644 index 00000000..065848a1 --- /dev/null +++ b/flowvault/src/test/java/com/skyflow/config/VaultConfigTests.java @@ -0,0 +1,94 @@ +package com.skyflow.config; + +import com.skyflow.enums.Env; +import org.junit.Assert; +import org.junit.Test; + +public class VaultConfigTests { + + @Test + public void testDefaultEnvIsProd() { + VaultConfig config = new VaultConfig(); + Assert.assertEquals(Env.PROD, config.getEnv()); + } + + @Test + public void testSettingNullEnvFallsBackToProd() { + VaultConfig config = new VaultConfig(); + config.setEnv(null); + Assert.assertEquals(Env.PROD, config.getEnv()); + } + + @Test + public void testVaultIdGetterSetter() { + VaultConfig config = new VaultConfig(); + config.setVaultId("vault123"); + Assert.assertEquals("vault123", config.getVaultId()); + } + + @Test + public void testClusterIdGetterSetter() { + VaultConfig config = new VaultConfig(); + config.setClusterId("cluster123"); + Assert.assertEquals("cluster123", config.getClusterId()); + } + + @Test + public void testEnvGetterSetter() { + VaultConfig config = new VaultConfig(); + config.setEnv(Env.SANDBOX); + Assert.assertEquals(Env.SANDBOX, config.getEnv()); + } + + @Test + public void testCredentialsGetterSetter() { + VaultConfig config = new VaultConfig(); + Credentials credentials = new Credentials(); + credentials.setToken("token1"); + config.setCredentials(credentials); + Assert.assertEquals(credentials, config.getCredentials()); + } + + @Test + public void testVaultURLDefaultsToNull() { + VaultConfig config = new VaultConfig(); + Assert.assertNull(config.getVaultUrl()); + } + + @Test + public void testVaultURLGetterSetter() { + VaultConfig config = new VaultConfig(); + config.setVaultUrl("https://myvault.example.com"); + Assert.assertEquals("https://myvault.example.com", config.getVaultUrl()); + } + + @Test + public void testClone_copiesFieldsAndDeepCopiesCredentials() throws CloneNotSupportedException { + VaultConfig config = new VaultConfig(); + config.setVaultId("vault123"); + config.setClusterId("cluster123"); + config.setEnv(Env.DEV); + Credentials credentials = new Credentials(); + credentials.setToken("token1"); + config.setCredentials(credentials); + + VaultConfig cloned = (VaultConfig) config.clone(); + + Assert.assertEquals(config.getVaultId(), cloned.getVaultId()); + Assert.assertEquals(config.getClusterId(), cloned.getClusterId()); + Assert.assertEquals(config.getEnv(), cloned.getEnv()); + Assert.assertNotSame(config.getCredentials(), cloned.getCredentials()); + Assert.assertEquals(config.getCredentials().getToken(), cloned.getCredentials().getToken()); + } + + @Test + public void testClone_withNullCredentials() throws CloneNotSupportedException { + VaultConfig config = new VaultConfig(); + config.setVaultId("vault123"); + config.setClusterId("cluster123"); + + VaultConfig cloned = (VaultConfig) config.clone(); + + Assert.assertNull(cloned.getCredentials()); + } +} diff --git a/flowvault/src/test/java/com/skyflow/utils/FakeChain.java b/flowvault/src/test/java/com/skyflow/utils/FakeChain.java new file mode 100644 index 00000000..97012f13 --- /dev/null +++ b/flowvault/src/test/java/com/skyflow/utils/FakeChain.java @@ -0,0 +1,136 @@ +package com.skyflow.utils; + +import okhttp3.Call; +import okhttp3.Connection; +import okhttp3.Interceptor; +import okhttp3.MediaType; +import okhttp3.Protocol; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; +import okio.Buffer; +import okio.BufferedSource; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * Minimal Interceptor.Chain that replays a scripted list of status codes, so the retry loop can + * be driven without a network or a mock-server dependency. Records how many times it was called + * and which responses were closed. + */ +public final class FakeChain implements Interceptor.Chain { + + private final Request request = + new Request.Builder().url("https://cluster1.example.com/v1/flows").build(); + private final int[] statusCodes; + private final List bodies = new ArrayList<>(); + private int calls; + private Request lastProceeded; + + public FakeChain(int... statusCodes) { + this.statusCodes = statusCodes; + } + + public int calls() { + return calls; + } + + /** Responses the interceptor superseded and should have closed. */ + public List bodies() { + return bodies; + } + + /** The request as it reached the next interceptor, i.e. after any rewriting. */ + public Request lastProceeded() { + return lastProceeded; + } + + @Override + public Request request() { + return request; + } + + @Override + public Response proceed(Request request) throws IOException { + this.lastProceeded = request; + // Past the end of the script, keep returning the last code. + int code = statusCodes[Math.min(calls, statusCodes.length - 1)]; + calls++; + TrackingBody body = new TrackingBody(); + bodies.add(body); + return new Response.Builder() + .request(request) + .protocol(Protocol.HTTP_1_1) + .code(code) + .message("status " + code) + .body(body) + .build(); + } + + @Override + public Connection connection() { + return null; + } + + @Override + public Call call() { + throw new UnsupportedOperationException(); + } + + @Override + public int connectTimeoutMillis() { + return 0; + } + + @Override + public Interceptor.Chain withConnectTimeout(int timeout, TimeUnit unit) { + return this; + } + + @Override + public int readTimeoutMillis() { + return 0; + } + + @Override + public Interceptor.Chain withReadTimeout(int timeout, TimeUnit unit) { + return this; + } + + @Override + public int writeTimeoutMillis() { + return 0; + } + + @Override + public Interceptor.Chain withWriteTimeout(int timeout, TimeUnit unit) { + return this; + } + + public static final class TrackingBody extends ResponseBody { + boolean closed; + + @Override + public MediaType contentType() { + return null; + } + + @Override + public long contentLength() { + return 0; + } + + @Override + public BufferedSource source() { + return new Buffer(); + } + + @Override + public void close() { + closed = true; + } + } +} diff --git a/flowvault/src/test/java/com/skyflow/utils/FlatTokenizeResponseTests.java b/flowvault/src/test/java/com/skyflow/utils/FlatTokenizeResponseTests.java new file mode 100644 index 00000000..43d51408 --- /dev/null +++ b/flowvault/src/test/java/com/skyflow/utils/FlatTokenizeResponseTests.java @@ -0,0 +1,383 @@ +package com.skyflow.utils; + +import com.skyflow.generated.rest.core.ApiClientApiException; +import com.skyflow.generated.rest.core.ObjectMappers; +import com.skyflow.generated.rest.types.V1FlowTokenizeResponse; +import com.skyflow.vault.data.BulkTokenizeRequestRecord; +import com.skyflow.vault.data.BulkTokenizeResponse; +import com.skyflow.vault.data.BulkTokenizeResponseRecord; +import com.skyflow.vault.data.TokenizeResponseToken; +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * The API returns one flat row per (value, token group) instead of the nested {@code tokens} array + * the generated wire type models, and a record rejected outright yields a single row regardless of + * how many groups it asked for. These cover folding those rows back onto the records that produced + * them. + */ +public class FlatTokenizeResponseTests { + + private static V1FlowTokenizeResponse parse(String json) { + try { + return ObjectMappers.JSON_MAPPER.readValue(json, V1FlowTokenizeResponse.class); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + + private static BulkTokenizeRequestRecord record(Object value, String... groups) { + return BulkTokenizeRequestRecord.builder() + .value(value).tokenGroupNames(Arrays.asList(groups)).build(); + } + + private static BulkTokenizeRequestRecord byotRecord(Object value, String token, String... groups) { + return BulkTokenizeRequestRecord.builder() + .value(value).token(token).tokenGroupNames(Arrays.asList(groups)).build(); + } + + // ── the exact payload observed against the live API ──────────────────────── + + private static final String LIVE_RESPONSE = "{\n" + + " \"response\": [\n" + + " { \"token\": \"\", \"value\": \"byot-input-value\", \"tokenGroupName\": null,\n" + + " \"error\": \"Invalid request. BYOT token should contain one token group.\", \"httpCode\": 400 },\n" + + " { \"token\": \"cc1179a3-e2be-404e-9a31-4f97f27bf406\", \"value\": {\"age\": 28, \"email\": \"ka@yahoo.com\"},\n" + + " \"tokenGroupName\": \"deterministic_string_tg\", \"error\": null, \"httpCode\": 200 },\n" + + " { \"token\": \"\", \"value\": {\"age\": 28, \"email\": \"ka@yahoo.com\"}, \"tokenGroupName\": null,\n" + + " \"error\": \"Tokenize failed. Token group emailTokenGroup is invalid. Specify a valid token group.\",\n" + + " \"httpCode\": 400 }\n" + + " ]\n" + + "}"; + + @Test + public void testLiveResponse_threeRowsFoldOntoTwoRecords() { + Map objectValue = new LinkedHashMap<>(); + objectValue.put("email", "ka@yahoo.com"); + objectValue.put("age", 28); + List sent = Arrays.asList( + byotRecord("byot-input-value", "550e8400-e29b-41d4-a716-446655440000", + "deterministic_string_tg", "non_deterministic"), + record(objectValue, "deterministic_string_tg", "emailTokenGroup")); + + BulkTokenizeResponse result = + Utils.formatBulkTokenizeResponse(parse(LIVE_RESPONSE), sent, 0, new HashMap<>()); + + // two inputs in, two records out - not three + Assert.assertEquals(2, result.getRecords().size()); + + BulkTokenizeResponseRecord byot = result.getRecords().get(0); + Assert.assertEquals(0, byot.getIndex()); + Assert.assertEquals("byot-input-value", byot.getValue()); + Assert.assertEquals(1, byot.getTokens().size()); + Assert.assertEquals("Invalid request. BYOT token should contain one token group.", + byot.getTokens().get(0).getError()); + Assert.assertEquals(Integer.valueOf(400), byot.getTokens().get(0).getHttpCode()); + // the API sends "" for a token that does not apply + Assert.assertNull(byot.getTokens().get(0).getToken()); + + BulkTokenizeResponseRecord object = result.getRecords().get(1); + Assert.assertEquals(1, object.getIndex()); + Assert.assertEquals(2, object.getTokens().size()); + Assert.assertEquals("deterministic_string_tg", object.getTokens().get(0).getTokenGroupName()); + Assert.assertEquals("cc1179a3-e2be-404e-9a31-4f97f27bf406", object.getTokens().get(0).getToken()); + Assert.assertNull(object.getTokens().get(0).getError()); + Assert.assertEquals("Tokenize failed. Token group emailTokenGroup is invalid. Specify a valid token group.", + object.getTokens().get(1).getError()); + } + + @Test + public void testLiveResponse_summaryClassifiesByRecordNotByRow() { + Map objectValue = new LinkedHashMap<>(); + objectValue.put("email", "ka@yahoo.com"); + objectValue.put("age", 28); + List sent = Arrays.asList( + byotRecord("byot-input-value", "550e8400", "deterministic_string_tg", "non_deterministic"), + record(objectValue, "deterministic_string_tg", "emailTokenGroup")); + + BulkTokenizeResponse formatted = + Utils.formatBulkTokenizeResponse(parse(LIVE_RESPONSE), sent, 0, new HashMap<>()); + BulkTokenizeResponse withPayload = + new BulkTokenizeResponse(formatted.getRecords(), sent); + + // 2 values in: one wholly failed, one partially tokenized - and the counts sum to 2 + Assert.assertEquals(2, withPayload.getSummary().getTotalTokens()); + Assert.assertEquals(0, withPayload.getSummary().getTotalTokenized()); + Assert.assertEquals(1, withPayload.getSummary().getTotalPartial()); + Assert.assertEquals(1, withPayload.getSummary().getTotalFailed()); + } + + // ── duplicate token groups within one record ─────────────────────────────── + + @Test + public void testDuplicateTokenGroupsInOneRecord_bothRowsKeptUnderOneRecord() { + String json = "{\"response\": [" + + "{\"value\": \"v1\", \"tokenGroupName\": \"g1\", \"token\": \"tok-a\", \"httpCode\": 200}," + + "{\"value\": \"v1\", \"tokenGroupName\": \"g1\", \"token\": \"tok-b\", \"httpCode\": 200}" + + "]}"; + List sent = Collections.singletonList(record("v1", "g1", "g1")); + + BulkTokenizeResponse result = Utils.formatBulkTokenizeResponse(parse(json), sent, 0, new HashMap<>()); + + // one record asked for the same group twice; both results stay on it rather than + // collapsing or spilling into a phantom second record + Assert.assertEquals(1, result.getRecords().size()); + List tokens = result.getRecords().get(0).getTokens(); + Assert.assertEquals(2, tokens.size()); + Assert.assertEquals("tok-a", tokens.get(0).getToken()); + Assert.assertEquals("tok-b", tokens.get(1).getToken()); + } + + // ── boundaries ───────────────────────────────────────────────────────────── + + @Test + public void testRecordRejectedOutright_nextRecordStillGetsItsOwnRows() { + // record 0 asked for 2 groups but was rejected wholesale, yielding 1 row + String json = "{\"response\": [" + + "{\"value\": \"v0\", \"tokenGroupName\": null, \"token\": \"\", \"error\": \"rejected\", \"httpCode\": 400}," + + "{\"value\": \"v1\", \"tokenGroupName\": \"g1\", \"token\": \"tok-1\", \"httpCode\": 200}," + + "{\"value\": \"v1\", \"tokenGroupName\": \"g2\", \"token\": \"tok-2\", \"httpCode\": 200}" + + "]}"; + List sent = Arrays.asList( + record("v0", "g1", "g2"), record("v1", "g1", "g2")); + + BulkTokenizeResponse result = Utils.formatBulkTokenizeResponse(parse(json), sent, 0, new HashMap<>()); + + Assert.assertEquals(2, result.getRecords().size()); + Assert.assertEquals(1, result.getRecords().get(0).getTokens().size()); + Assert.assertEquals("rejected", result.getRecords().get(0).getTokens().get(0).getError()); + Assert.assertEquals(2, result.getRecords().get(1).getTokens().size()); + Assert.assertEquals("tok-1", result.getRecords().get(1).getTokens().get(0).getToken()); + } + + @Test + public void testIndexIsOffsetByBatchStart() { + String json = "{\"response\": [" + + "{\"value\": \"v0\", \"tokenGroupName\": \"g1\", \"token\": \"tok-0\", \"httpCode\": 200}," + + "{\"value\": \"v1\", \"tokenGroupName\": \"g1\", \"token\": \"tok-1\", \"httpCode\": 200}" + + "]}"; + List sent = Arrays.asList(record("v0", "g1"), record("v1", "g1")); + + BulkTokenizeResponse result = Utils.formatBulkTokenizeResponse(parse(json), sent, 25, new HashMap<>()); + + Assert.assertEquals(25, result.getRecords().get(0).getIndex()); + Assert.assertEquals(26, result.getRecords().get(1).getIndex()); + } + + @Test + public void testRecordTheResponseNeverMentions_stillReportedWithNoTokens() { + String json = "{\"response\": [" + + "{\"value\": \"v0\", \"tokenGroupName\": \"g1\", \"token\": \"tok-0\", \"httpCode\": 200}" + + "]}"; + List sent = Arrays.asList(record("v0", "g1"), record("v1", "g1")); + + BulkTokenizeResponse result = Utils.formatBulkTokenizeResponse(parse(json), sent, 0, new HashMap<>()); + + // the caller must still see a record per input, so indexes stay aligned with their list + Assert.assertEquals(2, result.getRecords().size()); + Assert.assertEquals(1, result.getRecords().get(1).getIndex()); + Assert.assertTrue(result.getRecords().get(1).getTokens().isEmpty()); + } + + @Test + public void testMoreRowsThanTheRequestExplains_rowsAreKeptNotDropped() { + String json = "{\"response\": [" + + "{\"value\": \"v0\", \"tokenGroupName\": \"g1\", \"token\": \"tok-0\", \"httpCode\": 200}," + + "{\"value\": \"stray\", \"tokenGroupName\": \"g9\", \"token\": \"tok-9\", \"httpCode\": 200}" + + "]}"; + List sent = Collections.singletonList(record("v0", "g1")); + + BulkTokenizeResponse result = Utils.formatBulkTokenizeResponse(parse(json), sent, 0, new HashMap<>()); + + Assert.assertEquals(2, result.getRecords().size()); + Assert.assertEquals("tok-9", result.getRecords().get(1).getTokens().get(0).getToken()); + } + + @Test + public void testGroupedResponseShape_stillFoldsOneRowPerRecord() { + // if the API ever returns the nested shape the wire type models, nothing changes + String json = "{\"response\": [" + + "{\"value\": \"v0\", \"tokens\": [" + + " {\"tokenGroupName\": \"g1\", \"token\": \"tok-a\", \"httpCode\": 200}," + + " {\"tokenGroupName\": \"g2\", \"token\": \"tok-b\", \"httpCode\": 200}]}," + + "{\"value\": \"v1\", \"tokens\": [" + + " {\"tokenGroupName\": \"g1\", \"token\": \"tok-c\", \"httpCode\": 200}]}" + + "]}"; + List sent = Arrays.asList( + record("v0", "g1", "g2"), record("v1", "g1")); + + BulkTokenizeResponse result = Utils.formatBulkTokenizeResponse(parse(json), sent, 0, new HashMap<>()); + + Assert.assertEquals(2, result.getRecords().size()); + Assert.assertEquals(2, result.getRecords().get(0).getTokens().size()); + Assert.assertEquals("tok-c", result.getRecords().get(1).getTokens().get(0).getToken()); + } + + // ── rejected requests still describe each record in the body ─────────────── + + /** Builds the wrapper the SDK sees: CompletableFuture wraps the cause. */ + private static Throwable rejected(int status, Object body) { + return new RuntimeException(new ApiClientApiException("Error with status code " + status, status, body)); + } + + private static Map row(String value, String group, String token, String error, int httpCode) { + Map r = new LinkedHashMap<>(); + r.put("token", token); + r.put("value", value); + r.put("tokenGroupName", group); + r.put("error", error); + r.put("httpCode", httpCode); + return r; + } + + private static Map body(Map... rows) { + Map b = new LinkedHashMap<>(); + b.put("response", Arrays.asList(rows)); + return b; + } + + @Test + public void testRejectedRequest_reportsTheApiMessageNotJustTheStatus() { + // BYOT naming two groups: the API rejects the whole record and returns ONE row for it + List sent = Collections.singletonList( + byotRecord("grace@example.com", "tok-1", "deterministic_string_tg", "non_deterministic")); + Throwable ex = rejected(400, body(row("grace@example.com", null, "", + "Invalid request. BYOT token should contain one token group.", 400))); + + List records = + Utils.handleBulkTokenizeBatchException(ex, sent, 0); + + Assert.assertEquals(1, records.size()); + // one entry, matching the API - not one fabricated per requested token group + Assert.assertEquals(1, records.get(0).getTokens().size()); + Assert.assertEquals("Invalid request. BYOT token should contain one token group.", + records.get(0).getTokens().get(0).getError()); + Assert.assertNull(records.get(0).getTokens().get(0).getTokenGroupName()); + Assert.assertEquals(Integer.valueOf(400), records.get(0).getTokens().get(0).getHttpCode()); + } + + @Test + public void testRejectedRequest_keepsPerRecordMessagesAndIndexes() { + List sent = Arrays.asList( + record("dave@example.com", "bad_group"), record("erin@example.com", "bad_group")); + String message = "Tokenize failed. Token group bad_group is invalid. Specify a valid token group."; + Throwable ex = rejected(400, body( + row("dave@example.com", null, "", message, 400), + row("erin@example.com", null, "", message, 400))); + + List records = + Utils.handleBulkTokenizeBatchException(ex, sent, 20); + + Assert.assertEquals(2, records.size()); + Assert.assertEquals(20, records.get(0).getIndex()); + Assert.assertEquals(21, records.get(1).getIndex()); + Assert.assertEquals(message, records.get(0).getTokens().get(0).getError()); + Assert.assertEquals(message, records.get(1).getTokens().get(0).getError()); + } + + @Test + public void testRejectedRequest_withNoUsableBodyFallsBackToTheStatusCode() { + // a transport-level failure carries no response array to read + List sent = Collections.singletonList(record("v1", "g1", "g2")); + Throwable ex = new RuntimeException("connection reset"); + + List records = + Utils.handleBulkTokenizeBatchException(ex, sent, 0); + + Assert.assertEquals(1, records.size()); + // no rows to go on, so every requested group is reported as failed + Assert.assertEquals(2, records.get(0).getTokens().size()); + Assert.assertEquals("connection reset", records.get(0).getTokens().get(0).getError()); + Assert.assertEquals(Integer.valueOf(500), records.get(0).getTokens().get(0).getHttpCode()); + } + + @Test + public void testRejectedRequest_withUnfamiliarBodyFallsBackToTheStatusCode() { + List sent = Collections.singletonList(record("v1", "g1")); + Map opaque = new LinkedHashMap<>(); + opaque.put("error", "gateway timeout"); + + List records = + Utils.handleBulkTokenizeBatchException(rejected(504, opaque), sent, 0); + + Assert.assertEquals(1, records.size()); + Assert.assertEquals("gateway timeout", records.get(0).getTokens().get(0).getError()); + Assert.assertEquals(Integer.valueOf(504), records.get(0).getTokens().get(0).getHttpCode()); + } + + @Test + public void testRejectedRequest_retryableStatusStillSurfacesForRetry() { + List sent = Collections.singletonList(record("v1", "g1")); + Throwable ex = rejected(503, body(row("v1", "g1", "", "service unavailable", 503))); + + List records = + Utils.handleBulkTokenizeBatchException(ex, sent, 0); + BulkTokenizeResponse response = new BulkTokenizeResponse(records, sent); + + Assert.assertEquals(1, response.getRecordsToRetry().size()); + Assert.assertSame(sent.get(0), response.getRecordsToRetry().get(0)); + } + + // ── duplicate-value batching ─────────────────────────────────────────────── + + @Test + public void testBatching_cutsBatchShortWhenAValueRepeats() { + List records = Arrays.asList( + record("alice", "g1"), record("bob", "g1"), record("alice", "g1"), record("carol", "g1")); + + List> batches = Utils.createBulkTokenizeBatches(records, 10); + + // the repeat of "alice" must not share a request with the first one + Assert.assertEquals(2, batches.size()); + Assert.assertEquals(2, batches.get(0).size()); + Assert.assertEquals("alice", batches.get(0).get(0).getValue()); + Assert.assertEquals("bob", batches.get(0).get(1).getValue()); + Assert.assertEquals("alice", batches.get(1).get(0).getValue()); + Assert.assertEquals("carol", batches.get(1).get(1).getValue()); + } + + @Test + public void testBatching_stillHonoursBatchSize() { + List records = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + records.add(record("v" + i, "g1")); + } + + List> batches = Utils.createBulkTokenizeBatches(records, 2); + + Assert.assertEquals(3, batches.size()); + Assert.assertEquals(2, batches.get(0).size()); + Assert.assertEquals(2, batches.get(1).size()); + Assert.assertEquals(1, batches.get(2).size()); + } + + @Test + public void testBatching_batchesStayContiguousSoIndexesFollowFromTheStart() { + List records = Arrays.asList( + record("dup", "g1"), record("dup", "g1"), record("dup", "g1")); + + List> batches = Utils.createBulkTokenizeBatches(records, 10); + + // three copies of one value cannot share a request, so each gets its own + Assert.assertEquals(3, batches.size()); + int seen = 0; + for (List batch : batches) { + seen += batch.size(); + } + Assert.assertEquals(records.size(), seen); + } + + @Test + public void testBatching_emptyAndNullInputs() { + Assert.assertTrue(Utils.createBulkTokenizeBatches(null, 10).isEmpty()); + Assert.assertTrue(Utils.createBulkTokenizeBatches(Collections.emptyList(), 10).isEmpty()); + } +} diff --git a/flowvault/src/test/java/com/skyflow/utils/RequestFidelityTests.java b/flowvault/src/test/java/com/skyflow/utils/RequestFidelityTests.java new file mode 100644 index 00000000..13e2506e --- /dev/null +++ b/flowvault/src/test/java/com/skyflow/utils/RequestFidelityTests.java @@ -0,0 +1,908 @@ +package com.skyflow.utils; + +import com.skyflow.config.VaultConfig; +import com.skyflow.generated.rest.resources.flowservice.requests.V1FlowDeleteTokenRequest; +import com.skyflow.generated.rest.resources.flowservice.requests.V1FlowDetokenizeRequest; +import com.skyflow.generated.rest.resources.flowservice.requests.V1FlowTokenizeRequest; +import com.skyflow.generated.rest.resources.flowservice.requests.V1InsertRequest; +import com.skyflow.generated.rest.types.FlowEnumUpdateType; +import com.skyflow.generated.rest.types.V1DeleteTokenResponseObject; +import com.skyflow.generated.rest.types.V1FlowDeleteTokenResponse; +import com.skyflow.generated.rest.types.V1FlowDetokenizeResponse; +import com.skyflow.generated.rest.types.V1FlowDetokenizeResponseObject; +import com.skyflow.generated.rest.types.V1FlowTokenizeRequestObject; +import com.skyflow.generated.rest.types.V1InsertRecordData; +import com.skyflow.generated.rest.types.V1InsertResponse; +import com.skyflow.generated.rest.types.V1RecordResponseObject; +import com.skyflow.generated.rest.types.V1TokenGroupRedactions; +import com.skyflow.vault.data.BulkDeleteTokensRequest; +import com.skyflow.vault.data.BulkDeleteTokensResponse; +import com.skyflow.vault.data.BulkDetokenizeRequest; +import com.skyflow.vault.data.BulkDetokenizeResponse; +import com.skyflow.vault.data.BulkInsertRequest; +import com.skyflow.vault.data.BulkInsertRequestRecord; +import com.skyflow.vault.data.BulkInsertResponse; +import com.skyflow.vault.data.BulkTokenizeRequestRecord; +import com.skyflow.vault.data.BulkTokenizeRequest; +import com.skyflow.vault.data.InsertRequestRecord; +import com.skyflow.vault.data.TokenGroupRedactions; +import com.skyflow.vault.data.UpsertOptions; +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Request-fidelity tests: every value an SDK user sets on a flowvault request object must reach + * the outgoing generated REST request object faithfully — same value, same field, same order — + * including across batching. + * + * These tests deliberately assert on the real mapping code in {@link Utils} rather than on any + * hand-rolled copy of it, and use {@code assertSame} where the SDK should be passing the user's + * own object through untouched (maps, lists, arbitrary tokenize values). + */ +public class RequestFidelityTests { + + private static final String VAULT_ID = "vault123"; + + // Values that are easy to mangle: non-ASCII, embedded spaces, punctuation. + private static final String NON_ASCII_NAME = "日本語 テスト Ω"; + private static final String SPACED_TABLE = "my table name"; + private static final String NON_ASCII_TABLE = "表_日本語"; + + private static VaultConfig vaultConfig() { + VaultConfig config = new VaultConfig(); + config.setVaultId(VAULT_ID); + return config; + } + + private static ArrayList recordList(InsertRequestRecord... records) { + return new ArrayList<>(Arrays.asList(records)); + } + + // ───────────────────────────────────────────────────────────────────────── + // Bulk insert — field fidelity + // ───────────────────────────────────────────────────────────────────────── + + @Test + public void testBulkInsert_everyRecordFieldReachesWire() { + Map data = new LinkedHashMap<>(); + data.put("name", "john"); + Map tokens = new LinkedHashMap<>(); + tokens.put("name", "tok-abc"); + UpsertOptions upsert = UpsertOptions.builder() + .updateType("UPDATE") + .uniqueColumns(Arrays.asList("email", "phone")) + .build(); + + BulkInsertRequestRecord record = BulkInsertRequestRecord.builder() + .tableName("cards") + .data(data) + .tokens(tokens) + .upsert(upsert) + .build(); + BulkInsertRequest request = BulkInsertRequest.builder() + .tableName("cards") + .records(recordList(record)) + .build(); + + V1InsertRequest body = Utils.getBulkInsertRequestBody(request, vaultConfig()); + + Assert.assertEquals(VAULT_ID, body.getVaultId().get()); + Assert.assertEquals("cards", body.getTableName().get()); + V1InsertRecordData wire = body.getRecords().get().get(0); + Assert.assertEquals("cards", wire.getTableName().get()); + // The user's own map instances must be handed to the wire object untouched. + Assert.assertSame(data, wire.getData().get()); + Assert.assertSame(tokens, wire.getTokens().get()); + Assert.assertEquals(FlowEnumUpdateType.UPDATE, wire.getUpsert().get().getUpdateType().get()); + Assert.assertEquals(Arrays.asList("email", "phone"), wire.getUpsert().get().getUniqueColumns().get()); + } + + @Test + public void testBulkInsert_nonAsciiAndSpacedValues_userValueReachesWire() { + Map data = new LinkedHashMap<>(); + data.put("name", NON_ASCII_NAME); + data.put("street address", "12 東京都 千代田区"); + Map tokens = new LinkedHashMap<>(); + tokens.put("name", "tök-ábc 123"); + + BulkInsertRequestRecord record = BulkInsertRequestRecord.builder() + .tableName(NON_ASCII_TABLE) + .data(data) + .tokens(tokens) + .build(); + BulkInsertRequest request = BulkInsertRequest.builder() + .tableName(SPACED_TABLE) + .records(recordList(record)) + .build(); + + V1InsertRequest body = Utils.getBulkInsertRequestBody(request, vaultConfig()); + + Assert.assertEquals(SPACED_TABLE, body.getTableName().get()); + V1InsertRecordData wire = body.getRecords().get().get(0); + Assert.assertEquals(NON_ASCII_TABLE, wire.getTableName().get()); + Assert.assertEquals(NON_ASCII_NAME, wire.getData().get().get("name")); + Assert.assertEquals("12 東京都 千代田区", wire.getData().get().get("street address")); + Assert.assertEquals("tök-ábc 123", wire.getTokens().get().get("name")); + } + + @Test + public void testBulkInsert_nonStringDataValues_userValueReachesWire() { + Map nested = new LinkedHashMap<>(); + nested.put("city", "Paris"); + nested.put("zip", 75001); + List list = Arrays.asList(1, "two", true); + + Map data = new LinkedHashMap<>(); + data.put("age", 42); + data.put("balance", 1234.56d); + data.put("longValue", 9007199254740993L); + data.put("active", Boolean.TRUE); + data.put("address", nested); + data.put("tags", list); + + BulkInsertRequestRecord record = BulkInsertRequestRecord.builder() + .tableName("cards") + .data(data) + .build(); + BulkInsertRequest request = BulkInsertRequest.builder().records(recordList(record)).build(); + + V1InsertRequest body = Utils.getBulkInsertRequestBody(request, vaultConfig()); + Map wireData = body.getRecords().get().get(0).getData().get(); + + Assert.assertSame(data, wireData); + // Object identity/type of every value survives — no toString()-ing, no boxing changes. + Assert.assertEquals(Integer.valueOf(42), wireData.get("age")); + Assert.assertEquals(Double.valueOf(1234.56d), wireData.get("balance")); + Assert.assertEquals(Long.valueOf(9007199254740993L), wireData.get("longValue")); + Assert.assertSame(Boolean.TRUE, wireData.get("active")); + Assert.assertSame(nested, wireData.get("address")); + Assert.assertSame(list, wireData.get("tags")); + Assert.assertEquals(Integer.valueOf(75001), ((Map) wireData.get("address")).get("zip")); + } + + @Test + public void testBulkInsert_recordOrderPreserved() { + ArrayList records = new ArrayList<>(); + for (int i = 0; i < 7; i++) { + Map data = new HashMap<>(); + data.put("pos", i); + records.add(BulkInsertRequestRecord.builder().tableName("cards").data(data).build()); + } + BulkInsertRequest request = BulkInsertRequest.builder().records(records).build(); + + V1InsertRequest body = Utils.getBulkInsertRequestBody(request, vaultConfig()); + + List wireRecords = body.getRecords().get(); + Assert.assertEquals(7, wireRecords.size()); + for (int i = 0; i < 7; i++) { + Assert.assertEquals(Integer.valueOf(i), wireRecords.get(i).getData().get().get("pos")); + } + } + + @Test + public void testBulkInsert_recordOrderPreservedAcrossBatches() { + int total = 7; + int batchSize = 3; + ArrayList records = new ArrayList<>(); + for (int i = 0; i < total; i++) { + Map data = new HashMap<>(); + data.put("pos", i); + records.add(BulkInsertRequestRecord.builder().tableName("cards").data(data).build()); + } + BulkInsertRequest request = BulkInsertRequest.builder().tableName("cards").records(records).build(); + + V1InsertRequest body = Utils.getBulkInsertRequestBody(request, vaultConfig()); + List> batches = Utils.createBulkInsertBatches(body.getRecords().get(), batchSize); + + Assert.assertEquals(3, batches.size()); + int expected = 0; + for (List batch : batches) { + for (V1InsertRecordData wire : batch) { + Assert.assertEquals(Integer.valueOf(expected), wire.getData().get().get("pos")); + // Non-batched per-record fields survive batching on every batch. + Assert.assertEquals("cards", wire.getTableName().get()); + expected++; + } + } + Assert.assertEquals(total, expected); + } + + @Test + public void testBulkInsert_responseIndexMapsToOriginalInputPosition_acrossBatches() { + int total = 7; + int batchSize = 3; + ArrayList records = new ArrayList<>(); + for (int i = 0; i < total; i++) { + Map data = new HashMap<>(); + data.put("pos", i); + records.add(BulkInsertRequestRecord.builder().tableName("cards").data(data).build()); + } + BulkInsertRequest request = BulkInsertRequest.builder().tableName("cards").records(records).build(); + + V1InsertRequest body = Utils.getBulkInsertRequestBody(request, vaultConfig()); + List> batches = Utils.createBulkInsertBatches(body.getRecords().get(), batchSize); + + // Simulate the server echoing one response record per request record in each batch, then + // assert the SDK-assigned index equals the record's position in the ORIGINAL user list. + List indices = new ArrayList<>(); + List skyflowIds = new ArrayList<>(); + for (int batchNumber = 0; batchNumber < batches.size(); batchNumber++) { + List responseRecords = new ArrayList<>(); + for (V1InsertRecordData wire : batches.get(batchNumber)) { + responseRecords.add(V1RecordResponseObject.builder() + .skyflowId("sky-" + wire.getData().get().get("pos")) + .build()); + } + V1InsertResponse response = V1InsertResponse.builder().records(responseRecords).build(); + BulkInsertResponse formatted = Utils.formatBulkInsertResponse(response, batchNumber, batchSize, new HashMap<>()); + formatted.getRecords().forEach(r -> { + indices.add(r.getIndex()); + skyflowIds.add(r.getSkyflowId()); + }); + } + + Assert.assertEquals(total, indices.size()); + for (int i = 0; i < total; i++) { + Assert.assertEquals(Integer.valueOf(i), indices.get(i)); + Assert.assertEquals("sky-" + i, skyflowIds.get(i)); + } + } + + // ── insert: table-name precedence / fallback regressions ───────────────── + + @Test + public void testBulkInsert_blankRecordTableNameStaysAtRequestLevel() { + // Regression: a blank record-level table name counts as ABSENT (Utils.hasText). The name + // must go out on the envelope ONLY — the vault rejects a body carrying it at both levels. + Map data = new HashMap<>(); + data.put("name", "john"); + BulkInsertRequestRecord record = BulkInsertRequestRecord.builder() + .tableName(" ") + .data(data) + .build(); + BulkInsertRequest request = BulkInsertRequest.builder() + .tableName("cards") + .records(recordList(record)) + .build(); + + V1InsertRequest body = Utils.getBulkInsertRequestBody(request, vaultConfig()); + + Assert.assertFalse(body.getRecords().get().get(0).getTableName().isPresent()); + Assert.assertEquals("cards", body.getTableName().get()); + } + + @Test + public void testBulkInsert_nullRecordTableNameStaysAtRequestLevel() { + Map data = new HashMap<>(); + data.put("name", "john"); + BulkInsertRequestRecord record = BulkInsertRequestRecord.builder().data(data).build(); + BulkInsertRequest request = BulkInsertRequest.builder() + .tableName("cards") + .records(recordList(record)) + .build(); + + V1InsertRequest body = Utils.getBulkInsertRequestBody(request, vaultConfig()); + + Assert.assertFalse(body.getRecords().get().get(0).getTableName().isPresent()); + Assert.assertEquals("cards", body.getTableName().get()); + } + + @Test + public void testBulkInsert_emptyStringRecordTableNameStaysAtRequestLevel() { + Map data = new HashMap<>(); + data.put("name", "john"); + BulkInsertRequestRecord record = BulkInsertRequestRecord.builder().tableName("").data(data).build(); + BulkInsertRequest request = BulkInsertRequest.builder() + .tableName("cards") + .records(recordList(record)) + .build(); + + V1InsertRequest body = Utils.getBulkInsertRequestBody(request, vaultConfig()); + + Assert.assertFalse(body.getRecords().get().get(0).getTableName().isPresent()); + Assert.assertEquals("cards", body.getTableName().get()); + } + + @Test + public void testBulkInsert_recordTableNameOverridesRequestLevel() { + Map data = new HashMap<>(); + data.put("name", "john"); + BulkInsertRequestRecord record = BulkInsertRequestRecord.builder() + .tableName("record_table") + .data(data) + .build(); + BulkInsertRequest request = BulkInsertRequest.builder() + .tableName("request_table") + .records(recordList(record)) + .build(); + + V1InsertRequest body = Utils.getBulkInsertRequestBody(request, vaultConfig()); + + Assert.assertEquals("record_table", body.getRecords().get().get(0).getTableName().get()); + // The request-level name still goes out on the envelope, untouched. + Assert.assertEquals("request_table", body.getTableName().get()); + } + + @Test + public void testBulkInsert_perRecordTableNamesResolveIndependently() { + Map data = new HashMap<>(); + data.put("name", "john"); + BulkInsertRequestRecord withOwn = BulkInsertRequestRecord.builder().tableName("own").data(data).build(); + BulkInsertRequestRecord blank = BulkInsertRequestRecord.builder().tableName(" ").data(data).build(); + BulkInsertRequestRecord missing = BulkInsertRequestRecord.builder().data(data).build(); + BulkInsertRequest request = BulkInsertRequest.builder() + .tableName("fallback") + .records(recordList(withOwn, blank, missing)) + .build(); + + V1InsertRequest body = Utils.getBulkInsertRequestBody(request, vaultConfig()); + + // Only a record that names its own table carries one on the wire; the others rely on the + // envelope. Nothing is copied down, so the name is never duplicated across both levels. + Assert.assertEquals("own", body.getRecords().get().get(0).getTableName().get()); + Assert.assertFalse(body.getRecords().get().get(1).getTableName().isPresent()); + Assert.assertFalse(body.getRecords().get().get(2).getTableName().isPresent()); + Assert.assertEquals("fallback", body.getTableName().get()); + } + + @Test + public void testBulkInsert_blankRequestLevelTableNameIsOmittedFromEnvelope() { + Map data = new HashMap<>(); + data.put("name", "john"); + BulkInsertRequestRecord record = BulkInsertRequestRecord.builder().tableName("cards").data(data).build(); + BulkInsertRequest request = BulkInsertRequest.builder() + .tableName(" ") + .records(recordList(record)) + .build(); + + V1InsertRequest body = Utils.getBulkInsertRequestBody(request, vaultConfig()); + + Assert.assertFalse(body.getTableName().isPresent()); + Assert.assertEquals("cards", body.getRecords().get().get(0).getTableName().get()); + } + + // ── insert: upsert mapping ─────────────────────────────────────────────── + + @Test + public void testUpsert_updateTypeUpdateAndReplace_userValueReachesWire() { + Assert.assertEquals(FlowEnumUpdateType.UPDATE, upsertWire("UPDATE").getUpdateType().get()); + Assert.assertEquals(FlowEnumUpdateType.REPLACE, upsertWire("REPLACE").getUpdateType().get()); + } + + @Test + public void testUpsert_updateTypeIsMatchedCaseInsensitively() { + Assert.assertEquals(FlowEnumUpdateType.UPDATE, upsertWire("update").getUpdateType().get()); + Assert.assertEquals(FlowEnumUpdateType.UPDATE, upsertWire("UpDaTe").getUpdateType().get()); + Assert.assertEquals(FlowEnumUpdateType.REPLACE, upsertWire("replace").getUpdateType().get()); + Assert.assertEquals(FlowEnumUpdateType.REPLACE, upsertWire("Replace").getUpdateType().get()); + } + + @Test + public void testUpsert_unrecognizedUpdateTypeIsRejectedBeforeMapping() { + // Validations.validateUpsertOptions now rejects anything that is not UPDATE/REPLACE, so + // the mapper can no longer be reached with a value it would silently drop. A null + // updateType stays legal and simply omits the field. + com.skyflow.generated.rest.types.V1Upsert nullType = upsertWire(null); + Assert.assertFalse(nullType.getUpdateType().isPresent()); + Assert.assertEquals(Collections.singletonList("email"), nullType.getUniqueColumns().get()); + } + + @Test + public void testUpsert_uniqueColumnsValuesAndOrderReachWire() { + List uniqueColumns = Arrays.asList("email", "phone number", NON_ASCII_NAME); + Map data = new HashMap<>(); + data.put("name", "john"); + BulkInsertRequestRecord record = BulkInsertRequestRecord.builder() + .tableName("cards") + .data(data) + .upsert(UpsertOptions.builder().updateType("UPDATE").uniqueColumns(uniqueColumns).build()) + .build(); + BulkInsertRequest request = BulkInsertRequest.builder().records(recordList(record)).build(); + + V1InsertRequest body = Utils.getBulkInsertRequestBody(request, vaultConfig()); + + List wireColumns = body.getRecords().get().get(0).getUpsert().get().getUniqueColumns().get(); + Assert.assertSame(uniqueColumns, wireColumns); + Assert.assertEquals(Arrays.asList("email", "phone number", NON_ASCII_NAME), wireColumns); + } + + @Test + public void testUpsert_recordLevelOverridesRequestLevel() { + Map data = new HashMap<>(); + data.put("name", "john"); + BulkInsertRequestRecord record = BulkInsertRequestRecord.builder() + .tableName("cards") + .data(data) + .upsert(UpsertOptions.builder() + .updateType("REPLACE") + .uniqueColumns(Collections.singletonList("record_col")) + .build()) + .build(); + BulkInsertRequest request = BulkInsertRequest.builder() + .records(recordList(record)) + .upsert(UpsertOptions.builder() + .updateType("UPDATE") + .uniqueColumns(Collections.singletonList("request_col")) + .build()) + .build(); + + V1InsertRequest body = Utils.getBulkInsertRequestBody(request, vaultConfig()); + + com.skyflow.generated.rest.types.V1Upsert wire = body.getRecords().get().get(0).getUpsert().get(); + Assert.assertEquals(FlowEnumUpdateType.REPLACE, wire.getUpdateType().get()); + Assert.assertEquals(Collections.singletonList("record_col"), wire.getUniqueColumns().get()); + } + + @Test + public void testUpsert_requestLevelStaysOnEnvelopeAndIsNotCopiedOntoRecords() { + Map data = new HashMap<>(); + data.put("name", "john"); + BulkInsertRequest request = BulkInsertRequest.builder() + .tableName("cards") + .records(recordList( + BulkInsertRequestRecord.builder().data(data).build(), + BulkInsertRequestRecord.builder().data(data).build(), + BulkInsertRequestRecord.builder().data(data).build())) + .upsert(UpsertOptions.builder() + .updateType("UPDATE") + .uniqueColumns(Collections.singletonList("email")) + .build()) + .build(); + + V1InsertRequest body = Utils.getBulkInsertRequestBody(request, vaultConfig()); + + // upsert must travel at the same single level as the table name — here, the envelope. + for (V1InsertRecordData wire : body.getRecords().get()) { + Assert.assertFalse(wire.getUpsert().isPresent()); + } + Assert.assertTrue(body.getUpsert().isPresent()); + Assert.assertEquals(FlowEnumUpdateType.UPDATE, body.getUpsert().get().getUpdateType().get()); + Assert.assertEquals(Collections.singletonList("email"), body.getUpsert().get().getUniqueColumns().get()); + } + + @Test + public void testUpsert_requestLevelUpsertReachesEnvelope() { + // Regression: the request-level upsert used to be projected onto every record and never set + // on the V1InsertRequest envelope, so VaultController#insertBatchFutures — which reads + // insertRequest.getUpsert() to re-apply it per batch — always read empty. + Map data = new HashMap<>(); + data.put("name", "john"); + BulkInsertRequest request = BulkInsertRequest.builder() + .tableName("cards") + .records(recordList(BulkInsertRequestRecord.builder().data(data).build())) + .upsert(UpsertOptions.builder() + .updateType("UPDATE") + .uniqueColumns(Collections.singletonList("email")) + .build()) + .build(); + + V1InsertRequest body = Utils.getBulkInsertRequestBody(request, vaultConfig()); + + Assert.assertTrue(body.getUpsert().isPresent()); + Assert.assertFalse(body.getRecords().get().get(0).getUpsert().isPresent()); + } + + @Test + public void testUpsert_emptyUniqueColumnsMeansNoUpsertOnWire() { + Map data = new HashMap<>(); + data.put("name", "john"); + BulkInsertRequestRecord record = BulkInsertRequestRecord.builder() + .tableName("cards") + .data(data) + .upsert(UpsertOptions.builder() + .updateType("UPDATE") + .uniqueColumns(new ArrayList<>()) + .build()) + .build(); + BulkInsertRequest request = BulkInsertRequest.builder().records(recordList(record)).build(); + + V1InsertRequest body = Utils.getBulkInsertRequestBody(request, vaultConfig()); + + Assert.assertFalse(body.getRecords().get().get(0).getUpsert().isPresent()); + } + + private static com.skyflow.generated.rest.types.V1Upsert upsertWire(String updateType) { + Map data = new HashMap<>(); + data.put("name", "john"); + BulkInsertRequestRecord record = BulkInsertRequestRecord.builder() + .tableName("cards") + .data(data) + .upsert(UpsertOptions.builder() + .updateType(updateType) + .uniqueColumns(Collections.singletonList("email")) + .build()) + .build(); + BulkInsertRequest request = BulkInsertRequest.builder().records(recordList(record)).build(); + return Utils.getBulkInsertRequestBody(request, vaultConfig()) + .getRecords().get().get(0).getUpsert().get(); + } + + // ── insert: tokens map ─────────────────────────────────────────────────── + + @Test + public void testBulkInsert_emptyTokensMapIsOmittedFromWire_knownGap() { + // KNOWN GAP: an explicitly-set-but-empty tokens map is dropped rather than sent as {}. + // Pinning current behavior — a caller cannot distinguish "no tokens" from "empty tokens". + Map data = new HashMap<>(); + data.put("name", "john"); + BulkInsertRequestRecord record = BulkInsertRequestRecord.builder() + .tableName("cards") + .data(data) + .tokens(new HashMap<>()) + .build(); + BulkInsertRequest request = BulkInsertRequest.builder().records(recordList(record)).build(); + + V1InsertRequest body = Utils.getBulkInsertRequestBody(request, vaultConfig()); + + Assert.assertFalse(body.getRecords().get().get(0).getTokens().isPresent()); + } + + @Test + public void testBulkInsert_nullTokensMapIsOmittedFromWire() { + Map data = new HashMap<>(); + data.put("name", "john"); + BulkInsertRequestRecord record = BulkInsertRequestRecord.builder() + .tableName("cards") + .data(data) + .build(); + BulkInsertRequest request = BulkInsertRequest.builder().records(recordList(record)).build(); + + V1InsertRequest body = Utils.getBulkInsertRequestBody(request, vaultConfig()); + + Assert.assertFalse(body.getRecords().get().get(0).getTokens().isPresent()); + } + + @Test + public void testBulkInsert_multiValueTokensMapReachesWireVerbatim() { + Map data = new HashMap<>(); + data.put("name", "john"); + Map tokens = new LinkedHashMap<>(); + tokens.put("name", "tok-1"); + tokens.put("ssn", "tok-2"); + tokens.put("nested", Collections.singletonMap("group", "tok-3")); + BulkInsertRequestRecord record = BulkInsertRequestRecord.builder() + .tableName("cards") + .data(data) + .tokens(tokens) + .build(); + BulkInsertRequest request = BulkInsertRequest.builder().records(recordList(record)).build(); + + V1InsertRequest body = Utils.getBulkInsertRequestBody(request, vaultConfig()); + + Map wireTokens = body.getRecords().get().get(0).getTokens().get(); + Assert.assertSame(tokens, wireTokens); + Assert.assertEquals("tok-1", wireTokens.get("name")); + Assert.assertEquals("tok-2", wireTokens.get("ssn")); + Assert.assertEquals(Collections.singletonMap("group", "tok-3"), wireTokens.get("nested")); + } + + // ───────────────────────────────────────────────────────────────────────── + // Bulk detokenize + // ───────────────────────────────────────────────────────────────────────── + + @Test + public void testBulkDetokenize_everyFieldReachesWire() { + List tokens = Arrays.asList("token-1", "token 2", "トークン-3"); + TokenGroupRedactions groupA = TokenGroupRedactions.builder() + .tokenGroupName("group one") + .redaction("MASKED") + .build(); + TokenGroupRedactions groupB = TokenGroupRedactions.builder() + .tokenGroupName(NON_ASCII_NAME) + .redaction("PLAIN_TEXT") + .build(); + BulkDetokenizeRequest request = BulkDetokenizeRequest.builder() + .tokens(tokens) + .tokenGroupRedactions(Arrays.asList(groupA, groupB)) + .build(); + + V1FlowDetokenizeRequest body = Utils.getBulkDetokenizeRequestBody(request, VAULT_ID); + + Assert.assertEquals(VAULT_ID, body.getVaultId().get()); + Assert.assertSame(tokens, body.getTokens().get()); + Assert.assertEquals(Arrays.asList("token-1", "token 2", "トークン-3"), body.getTokens().get()); + + List wireGroups = body.getTokenGroupRedactions().get(); + Assert.assertEquals(2, wireGroups.size()); + Assert.assertEquals("group one", wireGroups.get(0).getTokenGroupName().get()); + Assert.assertEquals("MASKED", wireGroups.get(0).getRedaction().get()); + Assert.assertEquals(NON_ASCII_NAME, wireGroups.get(1).getTokenGroupName().get()); + Assert.assertEquals("PLAIN_TEXT", wireGroups.get(1).getRedaction().get()); + } + + @Test + public void testBulkDetokenize_emptyTokenGroupRedactionsListIsOmitted() { + BulkDetokenizeRequest request = BulkDetokenizeRequest.builder() + .tokens(Collections.singletonList("token-1")) + .tokenGroupRedactions(new ArrayList<>()) + .build(); + + V1FlowDetokenizeRequest body = Utils.getBulkDetokenizeRequestBody(request, VAULT_ID); + + Assert.assertFalse(body.getTokenGroupRedactions().isPresent()); + } + + @Test + public void testBulkDetokenize_tokenOrderPreservedAcrossBatches() { + int total = 7; + int batchSize = 3; + List tokens = new ArrayList<>(); + for (int i = 0; i < total; i++) { + tokens.add("token-" + i); + } + BulkDetokenizeRequest request = BulkDetokenizeRequest.builder().tokens(tokens).build(); + + V1FlowDetokenizeRequest body = Utils.getBulkDetokenizeRequestBody(request, VAULT_ID); + List batches = Utils.createBulkDetokenizeBatches(body, batchSize); + + Assert.assertEquals(3, batches.size()); + List flattened = new ArrayList<>(); + for (V1FlowDetokenizeRequest batch : batches) { + flattened.addAll(batch.getTokens().get()); + } + Assert.assertEquals(tokens, flattened); + } + + @Test + public void testBulkDetokenize_vaultIdAndRedactionsPresentOnEveryBatch() { + int total = 7; + int batchSize = 3; + List tokens = new ArrayList<>(); + for (int i = 0; i < total; i++) { + tokens.add("token-" + i); + } + TokenGroupRedactions group = TokenGroupRedactions.builder() + .tokenGroupName("group one") + .redaction("MASKED") + .build(); + BulkDetokenizeRequest request = BulkDetokenizeRequest.builder() + .tokens(tokens) + .tokenGroupRedactions(Collections.singletonList(group)) + .build(); + + V1FlowDetokenizeRequest body = Utils.getBulkDetokenizeRequestBody(request, VAULT_ID); + List batches = Utils.createBulkDetokenizeBatches(body, batchSize); + + Assert.assertEquals(3, batches.size()); + for (V1FlowDetokenizeRequest batch : batches) { + Assert.assertEquals(VAULT_ID, batch.getVaultId().get()); + Assert.assertTrue(batch.getTokenGroupRedactions().isPresent()); + Assert.assertEquals(1, batch.getTokenGroupRedactions().get().size()); + Assert.assertEquals("group one", batch.getTokenGroupRedactions().get().get(0).getTokenGroupName().get()); + Assert.assertEquals("MASKED", batch.getTokenGroupRedactions().get().get(0).getRedaction().get()); + } + } + + @Test + public void testBulkDetokenize_responseIndexMapsToOriginalInputPosition_acrossBatches() { + int total = 7; + int batchSize = 3; + List tokens = new ArrayList<>(); + for (int i = 0; i < total; i++) { + tokens.add("token-" + i); + } + BulkDetokenizeRequest request = BulkDetokenizeRequest.builder().tokens(tokens).build(); + V1FlowDetokenizeRequest body = Utils.getBulkDetokenizeRequestBody(request, VAULT_ID); + List batches = Utils.createBulkDetokenizeBatches(body, batchSize); + + List indices = new ArrayList<>(); + List echoedTokens = new ArrayList<>(); + for (int batchNumber = 0; batchNumber < batches.size(); batchNumber++) { + List responseRecords = new ArrayList<>(); + for (String token : batches.get(batchNumber).getTokens().get()) { + responseRecords.add(V1FlowDetokenizeResponseObject.builder().token(token).build()); + } + V1FlowDetokenizeResponse response = V1FlowDetokenizeResponse.builder().response(responseRecords).build(); + BulkDetokenizeResponse formatted = + Utils.formatBulkDetokenizeResponse(response, batchNumber, batchSize, new HashMap<>()); + formatted.getRecords().forEach(r -> { + indices.add(r.getIndex()); + echoedTokens.add(r.getToken()); + }); + } + + Assert.assertEquals(total, indices.size()); + for (int i = 0; i < total; i++) { + Assert.assertEquals(Integer.valueOf(i), indices.get(i)); + Assert.assertEquals(tokens.get(i), echoedTokens.get(i)); + } + } + + // ───────────────────────────────────────────────────────────────────────── + // Bulk tokenize + // ───────────────────────────────────────────────────────────────────────── + + @Test + public void testBulkTokenize_everyFieldReachesWire() { + List groupNames = Arrays.asList("group one", NON_ASCII_NAME); + BulkTokenizeRequestRecord record = BulkTokenizeRequestRecord.builder() + .value(NON_ASCII_NAME) + .tokenGroupNames(groupNames) + .build(); + List records = Collections.singletonList(record); + + V1FlowTokenizeRequest body = Utils.getBulkTokenizeRequestBody(records, VAULT_ID); + + Assert.assertEquals(VAULT_ID, body.getVaultId().get()); + Assert.assertEquals(1, body.getData().get().size()); + V1FlowTokenizeRequestObject wire = body.getData().get().get(0); + Assert.assertEquals(NON_ASCII_NAME, wire.getValue().get()); + Assert.assertSame(groupNames, wire.getTokenGroupNames().get()); + Assert.assertEquals(Arrays.asList("group one", NON_ASCII_NAME), wire.getTokenGroupNames().get()); + } + + @Test + public void testBulkTokenize_byotTokenReachesWire() { + BulkTokenizeRequestRecord record = BulkTokenizeRequestRecord.builder() + .value("v1") + .token("my-own-token") + .tokenGroupNames(Collections.singletonList("g1")) + .build(); + + V1FlowTokenizeRequest body = Utils.getBulkTokenizeRequestBody( + Collections.singletonList(record), VAULT_ID); + + Assert.assertEquals("my-own-token", body.getData().get().get(0).getToken().get()); + } + + @Test + public void testBulkTokenize_absentByotTokenIsOmittedFromWire() { + BulkTokenizeRequestRecord record = BulkTokenizeRequestRecord.builder() + .value("v1") + .tokenGroupNames(Collections.singletonList("g1")) + .build(); + + V1FlowTokenizeRequest body = Utils.getBulkTokenizeRequestBody( + Collections.singletonList(record), VAULT_ID); + + // omitted rather than sent as null, so a non-BYOT request is byte-identical to before + Assert.assertFalse(body.getData().get().get(0).getToken().isPresent()); + } + + @Test + public void testBulkTokenize_nonStringValues_objectIdentitySurvives() { + Map nested = new LinkedHashMap<>(); + nested.put("city", "Paris"); + nested.put("zip", 75001); + List listValue = Arrays.asList(1, 2, 3); + + List records = Arrays.asList( + BulkTokenizeRequestRecord.builder().value(42).build(), + BulkTokenizeRequestRecord.builder().value(3.14d).build(), + BulkTokenizeRequestRecord.builder().value(Boolean.FALSE).build(), + BulkTokenizeRequestRecord.builder().value(nested).build(), + BulkTokenizeRequestRecord.builder().value(listValue).build()); + + V1FlowTokenizeRequest body = Utils.getBulkTokenizeRequestBody(records, VAULT_ID); + List wire = body.getData().get(); + + Assert.assertEquals(Integer.valueOf(42), wire.get(0).getValue().get()); + Assert.assertEquals(Double.valueOf(3.14d), wire.get(1).getValue().get()); + Assert.assertSame(Boolean.FALSE, wire.get(2).getValue().get()); + Assert.assertSame(nested, wire.get(3).getValue().get()); + Assert.assertSame(listValue, wire.get(4).getValue().get()); + } + + @Test + public void testBulkTokenize_nullTokenGroupNamesIsOmitted() { + List records = + Collections.singletonList(BulkTokenizeRequestRecord.builder().value("v1").build()); + + V1FlowTokenizeRequest body = Utils.getBulkTokenizeRequestBody(records, VAULT_ID); + + Assert.assertFalse(body.getData().get().get(0).getTokenGroupNames().isPresent()); + } + + @Test + public void testBulkTokenize_recordOrderPreservedAcrossBatches() { + int total = 7; + int batchSize = 3; + List records = new ArrayList<>(); + for (int i = 0; i < total; i++) { + records.add(BulkTokenizeRequestRecord.builder().value("value-" + i).build()); + } + + // batching now happens on the SDK records, before the wire object is built, so the + // response formatter can recover each value's index from its batch position + List> batches = + Utils.createBulkTokenizeBatches(records, batchSize); + + Assert.assertEquals(3, batches.size()); + List flattened = new ArrayList<>(); + for (List batch : batches) { + V1FlowTokenizeRequest body = Utils.getBulkTokenizeRequestBody(batch, VAULT_ID); + // vaultId is a non-batched field and must be re-applied on every batch. + Assert.assertEquals(VAULT_ID, body.getVaultId().get()); + for (V1FlowTokenizeRequestObject obj : body.getData().get()) { + flattened.add(obj.getValue().get()); + } + } + Assert.assertEquals(total, flattened.size()); + for (int i = 0; i < total; i++) { + Assert.assertEquals("value-" + i, flattened.get(i)); + } + } + + // ───────────────────────────────────────────────────────────────────────── + // Bulk delete tokens + // ───────────────────────────────────────────────────────────────────────── + + @Test + public void testBulkDeleteTokens_everyFieldReachesWire() { + List tokens = Arrays.asList("token-1", "token 2", "トークン-3"); + BulkDeleteTokensRequest request = BulkDeleteTokensRequest.builder().tokens(tokens).build(); + + V1FlowDeleteTokenRequest body = Utils.getBulkDeleteTokensRequestBody(request, VAULT_ID); + + Assert.assertEquals(VAULT_ID, body.getVaultId().get()); + Assert.assertSame(tokens, body.getTokens().get()); + Assert.assertEquals(Arrays.asList("token-1", "token 2", "トークン-3"), body.getTokens().get()); + } + + @Test + public void testBulkDeleteTokens_tokenOrderPreservedAndVaultIdOnEveryBatch() { + int total = 7; + int batchSize = 3; + List tokens = new ArrayList<>(); + for (int i = 0; i < total; i++) { + tokens.add("token-" + i); + } + BulkDeleteTokensRequest request = BulkDeleteTokensRequest.builder().tokens(tokens).build(); + + V1FlowDeleteTokenRequest body = Utils.getBulkDeleteTokensRequestBody(request, VAULT_ID); + List batches = Utils.createBulkDeleteTokensBatches(body, batchSize); + + Assert.assertEquals(3, batches.size()); + List flattened = new ArrayList<>(); + for (V1FlowDeleteTokenRequest batch : batches) { + Assert.assertEquals(VAULT_ID, batch.getVaultId().get()); + flattened.addAll(batch.getTokens().get()); + } + Assert.assertEquals(tokens, flattened); + } + + @Test + public void testBulkDeleteTokens_responseIndexMapsToOriginalInputPosition_acrossBatches() { + int total = 7; + int batchSize = 3; + List tokens = new ArrayList<>(); + for (int i = 0; i < total; i++) { + tokens.add("token-" + i); + } + BulkDeleteTokensRequest request = BulkDeleteTokensRequest.builder().tokens(tokens).build(); + V1FlowDeleteTokenRequest body = Utils.getBulkDeleteTokensRequestBody(request, VAULT_ID); + List batches = Utils.createBulkDeleteTokensBatches(body, batchSize); + + List indices = new ArrayList<>(); + List echoed = new ArrayList<>(); + for (int batchNumber = 0; batchNumber < batches.size(); batchNumber++) { + List responseRecords = new ArrayList<>(); + for (String token : batches.get(batchNumber).getTokens().get()) { + responseRecords.add(V1DeleteTokenResponseObject.builder().value(token).build()); + } + V1FlowDeleteTokenResponse response = + V1FlowDeleteTokenResponse.builder().tokens(responseRecords).build(); + // successes and errors now share one records list, keyed by index + BulkDeleteTokensResponse formatted = Utils.formatBulkDeleteTokensResponse( + response, batches.get(batchNumber), batchNumber, batchSize, new HashMap<>()); + formatted.getRecords().forEach(r -> { + indices.add(r.getIndex()); + echoed.add(r.getToken()); + }); + } + + Assert.assertEquals(total, indices.size()); + for (int i = 0; i < total; i++) { + Assert.assertEquals(Integer.valueOf(i), indices.get(i)); + Assert.assertEquals(tokens.get(i), echoed.get(i)); + } + } +} diff --git a/flowvault/src/test/java/com/skyflow/utils/RequestIdTests.java b/flowvault/src/test/java/com/skyflow/utils/RequestIdTests.java new file mode 100644 index 00000000..e5e56857 --- /dev/null +++ b/flowvault/src/test/java/com/skyflow/utils/RequestIdTests.java @@ -0,0 +1,415 @@ +package com.skyflow.utils; + +import com.skyflow.generated.rest.core.ApiClientApiException; +import com.skyflow.generated.rest.core.ObjectMappers; +import com.skyflow.generated.rest.resources.flowservice.requests.V1FlowDeleteTokenRequest; +import com.skyflow.generated.rest.types.V1DeleteTokenResponseObject; +import com.skyflow.generated.rest.types.V1FlowDeleteTokenResponse; +import com.skyflow.generated.rest.types.V1FlowTokenizeResponse; +import com.skyflow.utils.BaseConstants; +import com.skyflow.vault.data.BulkDeleteTokensResponse; +import com.skyflow.vault.data.BulkDeleteTokensResponseRecord; +import com.skyflow.vault.data.BulkTokenizeRequestRecord; +import com.skyflow.vault.data.BulkTokenizeResponse; +import com.skyflow.vault.data.BulkTokenizeResponseRecord; +import com.skyflow.vault.data.TokenizeResponseToken; +import okhttp3.Protocol; +import okhttp3.Request; +import okhttp3.Response; +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * The API does not report a request id per record; it comes back once per call in the + * {@code x-request-id} response header. Bulk operations split the caller's list across several + * calls, so the SDK stamps each error with the id of the call it came from - and only errors, since + * that is when it is useful for support. + * + *

These cover the two properties that matter: every error from one batch shares a single id, and + * errors from different batches carry different ones. + */ +public class RequestIdTests { + + private static final String REQ_ID_A = "req-aaaa-1111"; + private static final String REQ_ID_B = "req-bbbb-2222"; + + private static Map> headers(String requestId) { + Map> headers = new HashMap<>(); + if (requestId != null) { + headers.put(BaseConstants.REQUEST_ID_HEADER_KEY, Collections.singletonList(requestId)); + } + return headers; + } + + /** + * Builds the exception the SDK actually sees on a non-2xx, carrying a real okhttp response so + * the header is read the same way it is in production rather than injected. + */ + private static ApiClientApiException apiException(String message, int status, Object body, + String requestId) { + Response.Builder raw = new Response.Builder() + .request(new Request.Builder().url("https://vault.example.test/v1/tokenize").build()) + .protocol(Protocol.HTTP_1_1) + .code(status) + .message(message); + if (requestId != null) { + raw.header(BaseConstants.REQUEST_ID_HEADER_KEY, requestId); + } + return new ApiClientApiException(message, status, body, raw.build()); + } + + private static V1FlowTokenizeResponse tokenizeWire(String json) { + try { + return ObjectMappers.JSON_MAPPER.readValue(json, V1FlowTokenizeResponse.class); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + + private static BulkTokenizeRequestRecord tokenizeRecord(Object value, String... groups) { + return BulkTokenizeRequestRecord.builder() + .value(value).tokenGroupNames(Arrays.asList(groups)).build(); + } + + // ── tokenize: success carries no id, errors carry the batch's ────────────── + + @Test + public void testTokenize_requestIdOnErrorsOnly() { + String json = "{\"response\": [" + + "{\"value\":\"ok\",\"tokenGroupName\":\"g1\",\"token\":\"tok-1\",\"httpCode\":200,\"error\":null}," + + "{\"value\":\"bad\",\"tokenGroupName\":null,\"token\":\"\",\"httpCode\":400," + + " \"error\":\"Token group g9 is invalid.\"}" + + "]}"; + List sent = Arrays.asList( + tokenizeRecord("ok", "g1"), tokenizeRecord("bad", "g9")); + + BulkTokenizeResponse result = Utils.formatBulkTokenizeResponse( + tokenizeWire(json), sent, 0, headers(REQ_ID_A)); + + TokenizeResponseToken success = result.getRecords().get(0).getTokens().get(0); + TokenizeResponseToken failure = result.getRecords().get(1).getTokens().get(0); + Assert.assertNull("a successful token must not carry a request id", success.getRequestId()); + Assert.assertEquals(REQ_ID_A, failure.getRequestId()); + } + + @Test + public void testTokenize_everyErrorInOneBatchSharesOneRequestId() { + String json = "{\"response\": [" + + "{\"value\":\"v0\",\"tokenGroupName\":null,\"token\":\"\",\"httpCode\":400,\"error\":\"bad group\"}," + + "{\"value\":\"v1\",\"tokenGroupName\":null,\"token\":\"\",\"httpCode\":400,\"error\":\"bad group\"}," + + "{\"value\":\"v2\",\"tokenGroupName\":null,\"token\":\"\",\"httpCode\":400,\"error\":\"bad group\"}" + + "]}"; + List sent = Arrays.asList( + tokenizeRecord("v0", "g1"), tokenizeRecord("v1", "g1"), tokenizeRecord("v2", "g1")); + + BulkTokenizeResponse result = Utils.formatBulkTokenizeResponse( + tokenizeWire(json), sent, 0, headers(REQ_ID_A)); + + Set ids = tokenizeRequestIds(result.getRecords()); + Assert.assertEquals(Collections.singleton(REQ_ID_A), ids); + } + + @Test + public void testTokenize_twoBatchesReportTwoDistinctRequestIds() { + String batchJson = "{\"response\": [" + + "{\"value\":\"%s\",\"tokenGroupName\":null,\"token\":\"\",\"httpCode\":400,\"error\":\"bad group\"}," + + "{\"value\":\"%s\",\"tokenGroupName\":null,\"token\":\"\",\"httpCode\":400,\"error\":\"bad group\"}" + + "]}"; + + // batch 0 occupies indexes 0-1 and answers with REQ_ID_A + BulkTokenizeResponse first = Utils.formatBulkTokenizeResponse( + tokenizeWire(String.format(batchJson, "v0", "v1")), + Arrays.asList(tokenizeRecord("v0", "g1"), tokenizeRecord("v1", "g1")), + 0, headers(REQ_ID_A)); + // batch 1 occupies indexes 2-3 and answers with REQ_ID_B + BulkTokenizeResponse second = Utils.formatBulkTokenizeResponse( + tokenizeWire(String.format(batchJson, "v2", "v3")), + Arrays.asList(tokenizeRecord("v2", "g1"), tokenizeRecord("v3", "g1")), + 2, headers(REQ_ID_B)); + + Assert.assertEquals(Collections.singleton(REQ_ID_A), tokenizeRequestIds(first.getRecords())); + Assert.assertEquals(Collections.singleton(REQ_ID_B), tokenizeRequestIds(second.getRecords())); + + // merged, as the controller returns them: index tells you the record, requestId the call + List merged = new ArrayList<>(first.getRecords()); + merged.addAll(second.getRecords()); + Assert.assertEquals(4, merged.size()); + Assert.assertEquals(REQ_ID_A, merged.get(0).getTokens().get(0).getRequestId()); + Assert.assertEquals(REQ_ID_A, merged.get(1).getTokens().get(0).getRequestId()); + Assert.assertEquals(REQ_ID_B, merged.get(2).getTokens().get(0).getRequestId()); + Assert.assertEquals(REQ_ID_B, merged.get(3).getTokens().get(0).getRequestId()); + Assert.assertEquals(2, merged.get(2).getIndex()); + Assert.assertEquals(3, merged.get(3).getIndex()); + } + + @Test + public void testTokenize_missingHeaderLeavesRequestIdNull() { + String json = "{\"response\": [{\"value\":\"v0\",\"tokenGroupName\":null,\"token\":\"\"," + + "\"httpCode\":400,\"error\":\"bad group\"}]}"; + BulkTokenizeResponse result = Utils.formatBulkTokenizeResponse( + tokenizeWire(json), Collections.singletonList(tokenizeRecord("v0", "g1")), + 0, headers(null)); + + Assert.assertNull(result.getRecords().get(0).getTokens().get(0).getRequestId()); + } + + @Test + public void testTokenize_partialRecordStampsOnlyTheFailedGroup() { + String json = "{\"response\": [" + + "{\"value\":\"v0\",\"tokenGroupName\":\"g1\",\"token\":\"tok-1\",\"httpCode\":200,\"error\":null}," + + "{\"value\":\"v0\",\"tokenGroupName\":null,\"token\":\"\",\"httpCode\":400,\"error\":\"bad group\"}" + + "]}"; + BulkTokenizeResponse result = Utils.formatBulkTokenizeResponse( + tokenizeWire(json), Collections.singletonList(tokenizeRecord("v0", "g1", "g2")), + 0, headers(REQ_ID_A)); + + List tokens = result.getRecords().get(0).getTokens(); + Assert.assertEquals(2, tokens.size()); + Assert.assertNull(tokens.get(0).getRequestId()); + Assert.assertEquals(REQ_ID_A, tokens.get(1).getRequestId()); + } + + // ── tokenize: rejected requests ──────────────────────────────────────────── + + @Test + public void testTokenize_rejectedRequestStampsIdFromTheFailedCall() { + Map row = new LinkedHashMap<>(); + row.put("token", ""); + row.put("value", "v0"); + row.put("tokenGroupName", null); + row.put("error", "Invalid request. BYOT token should contain one token group."); + row.put("httpCode", 400); + Map body = new LinkedHashMap<>(); + body.put("response", Collections.singletonList(row)); + + Throwable ex = new RuntimeException( + apiException("Error with status code 400", 400, body, REQ_ID_B)); + + List records = Utils.handleBulkTokenizeBatchException( + ex, Collections.singletonList(tokenizeRecord("v0", "g1")), 0); + + Assert.assertEquals(REQ_ID_B, records.get(0).getTokens().get(0).getRequestId()); + } + + @Test + public void testTokenize_rejectedRequestWithNoBodyStillStampsEveryGroup() { + Throwable ex = new RuntimeException( + apiException("boom", 503, null, REQ_ID_A)); + + List records = Utils.handleBulkTokenizeBatchException( + ex, Collections.singletonList(tokenizeRecord("v0", "g1", "g2")), 0); + + List tokens = records.get(0).getTokens(); + Assert.assertEquals(2, tokens.size()); + Assert.assertEquals(REQ_ID_A, tokens.get(0).getRequestId()); + Assert.assertEquals(REQ_ID_A, tokens.get(1).getRequestId()); + } + + @Test + public void testTokenize_transportFailureHasNoRequestId() { + // never reached the API, so there is no call to point at + List records = Utils.handleBulkTokenizeBatchException( + new RuntimeException("connection reset"), + Collections.singletonList(tokenizeRecord("v0", "g1")), 0); + + Assert.assertNull(records.get(0).getTokens().get(0).getRequestId()); + Assert.assertEquals("connection reset", records.get(0).getTokens().get(0).getError()); + } + + // ── delete: success carries no id, errors carry the batch's ──────────────── + + private static V1FlowDeleteTokenResponse deleteWire(V1DeleteTokenResponseObject... rows) { + return V1FlowDeleteTokenResponse.builder().tokens(Arrays.asList(rows)).build(); + } + + private static V1DeleteTokenResponseObject deleted(String token) { + return V1DeleteTokenResponseObject.builder().value(token).httpCode(200).build(); + } + + private static V1DeleteTokenResponseObject failed(String token, String error) { + return V1DeleteTokenResponseObject.builder().value(token).error(error).httpCode(404).build(); + } + + private static V1FlowDeleteTokenRequest deleteBatch(String... tokens) { + return V1FlowDeleteTokenRequest.builder() + .vaultId("vault123").tokens(Arrays.asList(tokens)).build(); + } + + @Test + public void testDelete_requestIdOnErrorsOnly() { + BulkDeleteTokensResponse result = Utils.formatBulkDeleteTokensResponse( + deleteWire(deleted("t0"), failed("t1", "Token t1 is invalid.")), + deleteBatch("t0", "t1"), 0, 50, headers(REQ_ID_A)); + + Assert.assertNull("a deleted token must not carry a request id", + result.getRecords().get(0).getRequestId()); + Assert.assertEquals(REQ_ID_A, result.getRecords().get(1).getRequestId()); + } + + @Test + public void testDelete_everyErrorInOneBatchSharesOneRequestId() { + BulkDeleteTokensResponse result = Utils.formatBulkDeleteTokensResponse( + deleteWire(failed("t0", "invalid"), failed("t1", "invalid"), failed("t2", "invalid")), + deleteBatch("t0", "t1", "t2"), 0, 50, headers(REQ_ID_A)); + + Assert.assertEquals(Collections.singleton(REQ_ID_A), deleteRequestIds(result.getRecords())); + } + + @Test + public void testDelete_twoBatchesReportTwoDistinctRequestIds() { + // batch 0 of size 2 covers indexes 0-1 + BulkDeleteTokensResponse first = Utils.formatBulkDeleteTokensResponse( + deleteWire(failed("t0", "invalid"), failed("t1", "invalid")), + deleteBatch("t0", "t1"), 0, 2, headers(REQ_ID_A)); + // batch 1 of size 2 covers indexes 2-3 + BulkDeleteTokensResponse second = Utils.formatBulkDeleteTokensResponse( + deleteWire(failed("t2", "invalid"), failed("t3", "invalid")), + deleteBatch("t2", "t3"), 1, 2, headers(REQ_ID_B)); + + Assert.assertEquals(Collections.singleton(REQ_ID_A), deleteRequestIds(first.getRecords())); + Assert.assertEquals(Collections.singleton(REQ_ID_B), deleteRequestIds(second.getRecords())); + + List merged = new ArrayList<>(first.getRecords()); + merged.addAll(second.getRecords()); + Assert.assertEquals(4, merged.size()); + Assert.assertEquals(REQ_ID_A, merged.get(0).getRequestId()); + Assert.assertEquals(REQ_ID_A, merged.get(1).getRequestId()); + Assert.assertEquals(REQ_ID_B, merged.get(2).getRequestId()); + Assert.assertEquals(REQ_ID_B, merged.get(3).getRequestId()); + Assert.assertEquals(2, merged.get(2).getIndex()); + Assert.assertEquals(3, merged.get(3).getIndex()); + } + + @Test + public void testDelete_mixedBatchStampsOnlyTheFailures() { + BulkDeleteTokensResponse result = Utils.formatBulkDeleteTokensResponse( + deleteWire(deleted("t0"), failed("t1", "invalid"), deleted("t2"), failed("t3", "invalid")), + deleteBatch("t0", "t1", "t2", "t3"), 0, 50, headers(REQ_ID_A)); + + List records = result.getRecords(); + Assert.assertNull(records.get(0).getRequestId()); + Assert.assertEquals(REQ_ID_A, records.get(1).getRequestId()); + Assert.assertNull(records.get(2).getRequestId()); + Assert.assertEquals(REQ_ID_A, records.get(3).getRequestId()); + } + + @Test + public void testDelete_missingHeaderLeavesRequestIdNull() { + BulkDeleteTokensResponse result = Utils.formatBulkDeleteTokensResponse( + deleteWire(failed("t0", "invalid")), deleteBatch("t0"), 0, 50, headers(null)); + + Assert.assertNull(result.getRecords().get(0).getRequestId()); + } + + // ── delete: rejected requests ────────────────────────────────────────────── + + @Test + public void testDelete_rejectedRequestWithPerTokenBodyStampsEveryRecord() { + Map row = new LinkedHashMap<>(); + row.put("value", "t0"); + row.put("error", "Token t0 is invalid."); + row.put("httpCode", 404); + Map body = new LinkedHashMap<>(); + body.put("tokens", Collections.singletonList(row)); + + Throwable ex = new RuntimeException( + apiException("Error with status code 404", 404, body, REQ_ID_B)); + + List records = + Utils.handleBulkDeleteTokensBatchException(ex, deleteBatch("t0"), 0, 50); + + Assert.assertEquals(REQ_ID_B, records.get(0).getRequestId()); + } + + @Test + public void testDelete_rejectedRequestWithNoBodyStampsEveryToken() { + Throwable ex = new RuntimeException( + apiException("boom", 503, null, REQ_ID_A)); + + List records = + Utils.handleBulkDeleteTokensBatchException(ex, deleteBatch("t0", "t1"), 0, 50); + + Assert.assertEquals(2, records.size()); + Assert.assertEquals(Collections.singleton(REQ_ID_A), deleteRequestIds(records)); + } + + @Test + public void testDelete_rejectedBatchesKeepTheirOwnRequestIds() { + Throwable exA = new RuntimeException( + apiException("boom", 503, null, REQ_ID_A)); + Throwable exB = new RuntimeException( + apiException("boom", 503, null, REQ_ID_B)); + + List first = + Utils.handleBulkDeleteTokensBatchException(exA, deleteBatch("t0", "t1"), 0, 2); + List second = + Utils.handleBulkDeleteTokensBatchException(exB, deleteBatch("t2", "t3"), 1, 2); + + Assert.assertEquals(Collections.singleton(REQ_ID_A), deleteRequestIds(first)); + Assert.assertEquals(Collections.singleton(REQ_ID_B), deleteRequestIds(second)); + Assert.assertEquals(2, second.get(0).getIndex()); + Assert.assertEquals(3, second.get(1).getIndex()); + } + + @Test + public void testDelete_transportFailureHasNoRequestId() { + List records = Utils.handleBulkDeleteTokensBatchException( + new RuntimeException("connection reset"), deleteBatch("t0"), 0, 50); + + Assert.assertNull(records.get(0).getRequestId()); + Assert.assertEquals("connection reset", records.get(0).getError()); + } + + // ── JSON output ─────────────────────────────────────────────────────────── + + @Test + public void testRequestIdAppearsInToString() { + BulkDeleteTokensResponse result = Utils.formatBulkDeleteTokensResponse( + deleteWire(deleted("t0"), failed("t1", "invalid")), + deleteBatch("t0", "t1"), 0, 50, headers(REQ_ID_A)); + + String json = result.toString(); + Assert.assertTrue("errors must expose the id to callers reading the JSON", + json.contains("\"requestId\":\"" + REQ_ID_A + "\"")); + Assert.assertTrue("successes must show it as null rather than omit it", + json.contains("\"requestId\":null")); + } + + // ── helpers ─────────────────────────────────────────────────────────────── + + /** Every non-null request id across a tokenize result's error entries. */ + private static Set tokenizeRequestIds(List records) { + Set ids = new HashSet<>(); + for (BulkTokenizeResponseRecord record : records) { + for (TokenizeResponseToken token : record.getTokens()) { + if (token.getError() != null) { + Assert.assertNotNull("every error must carry a request id", token.getRequestId()); + ids.add(token.getRequestId()); + } + } + } + return ids; + } + + /** Every non-null request id across a delete result's error records. */ + private static Set deleteRequestIds(List records) { + Set ids = new HashSet<>(); + for (BulkDeleteTokensResponseRecord record : records) { + if (record.getError() != null) { + Assert.assertNotNull("every error must carry a request id", record.getRequestId()); + ids.add(record.getRequestId()); + } + } + return ids; + } +} diff --git a/flowvault/src/test/java/com/skyflow/utils/SkyflowRetryInterceptorTests.java b/flowvault/src/test/java/com/skyflow/utils/SkyflowRetryInterceptorTests.java new file mode 100644 index 00000000..aae9a6f9 --- /dev/null +++ b/flowvault/src/test/java/com/skyflow/utils/SkyflowRetryInterceptorTests.java @@ -0,0 +1,254 @@ +package com.skyflow.utils; + +import okhttp3.Response; +import org.junit.Assert; +import org.junit.Test; + +import java.io.IOException; + +/** + * Internals of the retry interceptor. Lives in com.skyflow.utils so the package-private + * backoff/should-retry helpers stay off the public API surface. + */ +public class SkyflowRetryInterceptorTests { + + @Test + public void testConstructor_rejectsNegativeMaxRetries() { + try { + new SkyflowRetryInterceptor(-1, 500L, 2000L); + Assert.fail("negative maxRetries should be rejected"); + } catch (IllegalArgumentException expected) { + Assert.assertTrue(expected.getMessage().contains("maxRetries")); + } + } + + @Test + public void testConstructor_rejectsNegativeInitialDelay() { + try { + new SkyflowRetryInterceptor(1, -1L, 2000L); + Assert.fail("negative initialRetryDelayMillis should be rejected"); + } catch (IllegalArgumentException expected) { + Assert.assertTrue(expected.getMessage().contains("initialRetryDelayMillis")); + } + } + + @Test + public void testConstructor_rejectsNegativeMaxDelay() { + try { + new SkyflowRetryInterceptor(1, 500L, -1L); + Assert.fail("negative maxRetryDelayMillis should be rejected"); + } catch (IllegalArgumentException expected) { + Assert.assertTrue(expected.getMessage().contains("maxRetryDelayMillis")); + } + } + + @Test + public void testBackoff_growsExponentiallyThenCaps() { + // Jitter is +/-20%, so assert bands rather than exact values. + SkyflowRetryInterceptor retry = new SkyflowRetryInterceptor(5, 100L, 400L); + + assertWithinJitter(100L, retry.backoffMillis(1)); + assertWithinJitter(200L, retry.backoffMillis(2)); + assertWithinJitter(400L, retry.backoffMillis(3)); + assertWithinJitter(400L, retry.backoffMillis(4)); + assertWithinJitter(400L, retry.backoffMillis(10)); + } + + @Test + public void testBackoff_neverExceedsTheCapAcrossManyDraws() { + SkyflowRetryInterceptor retry = new SkyflowRetryInterceptor(5, 100L, 400L); + + for (int i = 0; i < 200; i++) { + long delay = retry.backoffMillis(3); + Assert.assertTrue("jittered delay went negative: " + delay, delay >= 0); + Assert.assertTrue("jittered delay exceeded cap + jitter: " + delay, delay <= 480L); + } + } + + @Test + public void testBackoff_zeroDelayStaysZero() { + SkyflowRetryInterceptor retry = new SkyflowRetryInterceptor(3, 0L, 0L); + + Assert.assertEquals(0L, retry.backoffMillis(1)); + Assert.assertEquals(0L, retry.backoffMillis(5)); + } + + @Test + public void testBackoff_initialDelayAboveCapIsClampedToCap() { + SkyflowRetryInterceptor retry = new SkyflowRetryInterceptor(3, 5000L, 1000L); + + assertWithinJitter(1000L, retry.backoffMillis(1)); + assertWithinJitter(1000L, retry.backoffMillis(3)); + } + + @Test + public void testShouldRetry_retryableStatuses() { + Assert.assertTrue(SkyflowRetryInterceptor.shouldRetry(408)); + Assert.assertTrue(SkyflowRetryInterceptor.shouldRetry(429)); + Assert.assertTrue(SkyflowRetryInterceptor.shouldRetry(500)); + Assert.assertTrue(SkyflowRetryInterceptor.shouldRetry(502)); + Assert.assertTrue(SkyflowRetryInterceptor.shouldRetry(503)); + } + + @Test + public void testShouldRetry_nonRetryableStatuses() { + Assert.assertFalse(SkyflowRetryInterceptor.shouldRetry(200)); + Assert.assertFalse(SkyflowRetryInterceptor.shouldRetry(201)); + Assert.assertFalse(SkyflowRetryInterceptor.shouldRetry(400)); + Assert.assertFalse(SkyflowRetryInterceptor.shouldRetry(401)); + Assert.assertFalse(SkyflowRetryInterceptor.shouldRetry(404)); + Assert.assertFalse(SkyflowRetryInterceptor.shouldRetry(409)); + } + + @Test + public void testAccessors_reportWhatWasConfigured() { + SkyflowRetryInterceptor retry = new SkyflowRetryInterceptor(3, 100L, 900L); + + Assert.assertEquals(3, retry.getMaxRetries()); + Assert.assertEquals(100L, retry.getInitialRetryDelayMillis()); + Assert.assertEquals(900L, retry.getMaxRetryDelayMillis()); + } + + // ── intercept(): the retry loop ─────────────────────────────────────────── + // Delays are set to 0 so these do not actually sleep. + + private static SkyflowRetryInterceptor retrying(int maxRetries) { + return new SkyflowRetryInterceptor(maxRetries, 0L, 0L); + } + + @Test + public void testIntercept_successFirstTimeIsNotRetried() throws IOException { + FakeChain chain = new FakeChain(200); + + Response response = retrying(3).intercept(chain); + + Assert.assertEquals(1, chain.calls()); + Assert.assertEquals(200, response.code()); + } + + @Test + public void testIntercept_nonRetryableFailureIsNotRetried() throws IOException { + FakeChain chain = new FakeChain(400); + + Response response = retrying(3).intercept(chain); + + Assert.assertEquals("a 400 must not be replayed", 1, chain.calls()); + Assert.assertEquals(400, response.code()); + } + + @Test + public void testIntercept_retriesUpToTheBudgetThenReturnsTheLastFailure() throws IOException { + FakeChain chain = new FakeChain(500); + + Response response = retrying(2).intercept(chain); + + Assert.assertEquals("1 initial attempt + 2 retries", 3, chain.calls()); + Assert.assertEquals(500, response.code()); + } + + @Test + public void testIntercept_stopsAsSoonAsAnAttemptSucceeds() throws IOException { + FakeChain chain = new FakeChain(503, 200, 200); + + Response response = retrying(5).intercept(chain); + + Assert.assertEquals("must not keep retrying after success", 2, chain.calls()); + Assert.assertEquals(200, response.code()); + } + + @Test + public void testIntercept_stopsOnANonRetryableStatusMidWay() throws IOException { + FakeChain chain = new FakeChain(500, 404, 200); + + Response response = retrying(5).intercept(chain); + + Assert.assertEquals(2, chain.calls()); + Assert.assertEquals(404, response.code()); + } + + @Test + public void testIntercept_zeroBudgetMeansNoRetryAtAll() throws IOException { + FakeChain chain = new FakeChain(500); + + Response response = retrying(0).intercept(chain); + + Assert.assertEquals(1, chain.calls()); + Assert.assertEquals(500, response.code()); + } + + @Test + public void testIntercept_retriesEachRetryableStatus() throws IOException { + for (int code : new int[] {408, 429, 500, 502, 503}) { + FakeChain chain = new FakeChain(code, 200); + + Response response = retrying(1).intercept(chain); + + Assert.assertEquals("should have retried a " + code, 2, chain.calls()); + Assert.assertEquals(200, response.code()); + } + } + + @Test + public void testIntercept_closesEverySupersededResponse() throws IOException { + // Leaking the body of a response we are about to discard would leak the connection. + FakeChain chain = new FakeChain(500, 500, 200); + + retrying(2).intercept(chain); + + Assert.assertEquals(3, chain.bodies().size()); + Assert.assertTrue("first failed response not closed", chain.bodies().get(0).closed); + Assert.assertTrue("second failed response not closed", chain.bodies().get(1).closed); + Assert.assertFalse("the returned response must stay open", chain.bodies().get(2).closed); + } + + @Test + public void testIntercept_retryBudgetIsPerCallNotPerInterceptorInstance() throws IOException { + // The generated RetryInterceptor keeps its backoff counter on the instance, so one shared + // instance exhausts the budget once for the whole client. A single interceptor is installed + // on a shared OkHttpClient, so every call must get its own full budget. + SkyflowRetryInterceptor retry = retrying(2); + + FakeChain first = new FakeChain(500); + retry.intercept(first); + FakeChain second = new FakeChain(500); + retry.intercept(second); + FakeChain third = new FakeChain(500); + retry.intercept(third); + + Assert.assertEquals(3, first.calls()); + Assert.assertEquals("second call lost its retry budget", 3, second.calls()); + Assert.assertEquals("third call lost its retry budget", 3, third.calls()); + } + + @Test + public void testIntercept_interruptionSurfacesAsIOException() { + SkyflowRetryInterceptor retry = new SkyflowRetryInterceptor(2, 5_000L, 5_000L); + FakeChain chain = new FakeChain(500); + + Thread.currentThread().interrupt(); + try { + retry.intercept(chain); + Assert.fail("an interrupt while backing off should surface as IOException"); + } catch (IOException e) { + Assert.assertTrue(e.getMessage().contains("Interrupted")); + Assert.assertTrue("the interrupt flag must be restored", Thread.currentThread().isInterrupted()); + } finally { + Thread.interrupted(); // clear the flag so it cannot leak into another test + } + } + + @Test + public void testBackoff_growsPastHalfTheCapInOneStep() { + // initial > max/2, so the next step clamps straight to the cap instead of doubling past it. + SkyflowRetryInterceptor retry = new SkyflowRetryInterceptor(3, 300L, 400L); + + assertWithinJitter(300L, retry.backoffMillis(1)); + assertWithinJitter(400L, retry.backoffMillis(2)); + } + + private static void assertWithinJitter(long expected, long actual) { + long jitter = (long) (expected * 0.2); + Assert.assertTrue("expected ~" + expected + " (+/-" + jitter + ") but got " + actual, + actual >= expected - jitter && actual <= expected + jitter); + } +} diff --git a/flowvault/src/test/java/com/skyflow/utils/UtilsTests.java b/flowvault/src/test/java/com/skyflow/utils/UtilsTests.java new file mode 100644 index 00000000..d220084a --- /dev/null +++ b/flowvault/src/test/java/com/skyflow/utils/UtilsTests.java @@ -0,0 +1,1988 @@ +package com.skyflow.utils; + +import com.google.gson.JsonObject; +import com.skyflow.config.Credentials; +import com.skyflow.config.VaultConfig; +import com.skyflow.enums.Env; +import com.skyflow.errors.SkyflowException; +import com.skyflow.generated.rest.core.ApiClientApiException; +import com.skyflow.generated.rest.resources.flowservice.requests.V1FlowDeleteTokenRequest; +import com.skyflow.generated.rest.resources.flowservice.requests.V1FlowDetokenizeRequest; +import com.skyflow.generated.rest.resources.flowservice.requests.V1FlowTokenizeRequest; +import com.skyflow.generated.rest.resources.flowservice.requests.V1InsertRequest; +import com.skyflow.generated.rest.types.FlowEnumUpdateType; +import com.skyflow.generated.rest.types.FlowTokenizeResponseObjectToken; +import com.skyflow.generated.rest.types.V1DeleteTokenResponseObject; +import com.skyflow.generated.rest.types.V1FlowDeleteTokenResponse; +import com.skyflow.generated.rest.types.V1FlowDetokenizeResponse; +import com.skyflow.generated.rest.types.V1FlowDetokenizeResponseObject; +import com.skyflow.generated.rest.types.V1FlowTokenizeResponse; +import com.skyflow.generated.rest.types.V1FlowTokenizeResponseObject; +import com.skyflow.generated.rest.types.V1InsertRecordData; +import com.skyflow.generated.rest.types.V1InsertResponse; +import com.skyflow.generated.rest.types.V1RecordResponseObject; +import com.skyflow.vault.data.BulkDeleteTokensRequest; +import com.skyflow.vault.data.BulkDeleteTokensResponse; +import com.skyflow.vault.data.BulkDeleteTokensResponseRecord; +import com.skyflow.vault.data.BulkDetokenizeRequest; +import com.skyflow.vault.data.BulkDetokenizeResponse; +import com.skyflow.vault.data.BulkDetokenizeResponseRecord; +import com.skyflow.vault.data.BulkInsertRequestRecord; +import com.skyflow.vault.data.BulkInsertRequest; +import com.skyflow.vault.data.BulkInsertResponse; +import com.skyflow.vault.data.BulkTokenizeRequestRecord; +import com.skyflow.vault.data.BulkInsertResponseRecord; +import com.skyflow.vault.data.BulkTokenizeRequest; +import com.skyflow.vault.data.BulkTokenizeResponse; +import com.skyflow.vault.data.ErrorRecord; +import com.skyflow.vault.data.InsertRequestRecord; +import com.skyflow.vault.data.InsertRequest; +import com.skyflow.vault.data.TokenGroupRedactions; +import com.skyflow.vault.data.BulkTokenizeResponseRecord; +import com.skyflow.vault.data.TokenizeResponseRecord; +import com.skyflow.vault.data.TokenizeRequestRecord; +import com.skyflow.vault.data.TokenizeRequest; +import com.skyflow.vault.data.TokenizeResponse; +import com.skyflow.vault.data.UpsertOptions; +import org.junit.After; +import com.skyflow.vault.data.TokenizeResponseToken; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class UtilsTests { + + private static final String ENV_FILE = ".env"; + private byte[] originalEnvContent; + + @Before + public void saveEnvFileState() throws IOException { + File f = new File(ENV_FILE); + originalEnvContent = f.exists() ? Files.readAllBytes(Paths.get(ENV_FILE)) : null; + } + + @After + public void restoreEnvFile() throws IOException { + if (originalEnvContent != null) { + Files.write(Paths.get(ENV_FILE), originalEnvContent); + } else { + Files.deleteIfExists(Paths.get(ENV_FILE)); + } + } + + // ── getVaultUrl ─────────────────────────────────────────────────────────── + + @Test + public void testGetVaultURL_prodEnv() { + String url = Utils.getVaultUrl("cluster1", Env.PROD); + Assert.assertEquals("https://cluster1.skyvault.skyflowapis.com", url); + } + + @Test + public void testGetVaultURL_devEnv() { + String url = Utils.getVaultUrl("cluster1", Env.DEV); + Assert.assertEquals("https://cluster1.skyvault.skyflowapis.dev", url); + } + + @Test + public void testGetVaultURL_stageEnv() { + String url = Utils.getVaultUrl("cluster1", Env.STAGE); + Assert.assertEquals("https://cluster1.skyvault.skyflowapis.tech", url); + } + + @Test + public void testGetVaultURL_sandboxEnv() { + String url = Utils.getVaultUrl("cluster1", Env.SANDBOX); + Assert.assertEquals("https://cluster1.skyvault.skyflowapis-preview.com", url); + } + + // ── getMetrics ──────────────────────────────────────────────────────────── + + @Test + public void testGetMetrics_containsSdkVersion() { + JsonObject metrics = Utils.getMetrics(); + Assert.assertTrue(metrics.has(BaseConstants.SDK_METRIC_NAME_VERSION)); + String sdkVersionMetric = metrics.get(BaseConstants.SDK_METRIC_NAME_VERSION).getAsString(); + Assert.assertTrue(sdkVersionMetric.startsWith(Constants.SDK_METRIC_NAME_VERSION_PREFIX)); + } + + // ── getEnvVaultUrl ──────────────────────────────────────────────────────── + + @Test + public void testGetEnvVaultURL_doesNotThrowUnexpectedException() { + try { + Utils.getEnvVaultUrl(); + } catch (SkyflowException e) { + // acceptable if this environment happens to have an invalid VAULT_URL set + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testGetEnvVaultURL_emptyValueThrowsEmptyVaultUrl() throws Exception { + // No VAULT_URL system/real env var is set in the test environment, so this + // exercises the Dotenv.load() fallback path with an empty value. + try (FileWriter fw = new FileWriter(ENV_FILE)) { + fw.write("VAULT_URL=\n"); + } + + try { + Utils.getEnvVaultUrl(); + Assert.fail("Should have thrown SkyflowException for empty VAULT_URL"); + } catch (SkyflowException e) { + Assert.assertEquals(com.skyflow.errors.ErrorMessage.EmptyVaultUrl.getMessage(), e.getMessage()); + } + } + + @Test + public void testGetEnvVaultURL_invalidFormatThrowsInvalidVaultUrlFormat() throws Exception { + try (FileWriter fw = new FileWriter(ENV_FILE)) { + fw.write("VAULT_URL=http://example.com\n"); + } + + try { + Utils.getEnvVaultUrl(); + Assert.fail("Should have thrown SkyflowException for invalid VAULT_URL format"); + } catch (SkyflowException e) { + Assert.assertEquals(com.skyflow.errors.ErrorMessage.InvalidVaultUrlFormat.getMessage(), e.getMessage()); + } + } + + // ── isValidUrl ──────────────────────────────────────────────────────────── + + @Test + public void testIsValidURL_validHttpsUrl() { + Assert.assertTrue(Utils.isValidUrl("https://example.com")); + } + + @Test + public void testIsValidURL_httpUrlIsInvalid() { + Assert.assertFalse(Utils.isValidUrl("http://example.com")); + } + + @Test + public void testIsValidURL_malformedUrl() { + Assert.assertFalse(Utils.isValidUrl("not a url")); + } + + @Test + public void testIsValidURL_httpsUrlWithEmptyHostIsInvalid() { + Assert.assertFalse(Utils.isValidUrl("https:///path")); + } + + // ── generateBearerToken ─────────────────────────────────────────────────── + + @Test + public void testGenerateBearerToken_withDirectToken() throws SkyflowException { + Credentials credentials = new Credentials(); + credentials.setToken("direct-token-value"); + + String token = Utils.generateBearerToken(credentials); + + Assert.assertEquals("direct-token-value", token); + } + + @Test + public void testGenerateBearerToken_withInvalidCredentialsStringThrows() { + Credentials credentials = new Credentials(); + // A string that fails JSON syntax parsing (as opposed to e.g. a bare word, + // which Gson's lenient parser accepts as a JSON primitive rather than rejecting outright). + credentials.setCredentialsString("./src/test/credentials.json"); + try { + Utils.generateBearerToken(credentials); + Assert.fail("Should have thrown an exception"); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testGenerateBearerToken_withNonExistentPathThrows() { + Credentials credentials = new Credentials(); + credentials.setPath("/nonexistent/path/credentials.json"); + try { + Utils.generateBearerToken(credentials); + Assert.fail("Should have thrown an exception"); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + // ── getInsertRequestBody (unary) ────────────────────────────────────────── + + @Test + public void testGetInsertRequestBody_buildsCorrectRequest() { + Map data = new HashMap<>(); + data.put("name", "john"); + InsertRequestRecord record = InsertRequestRecord.builder().tableName("table1").data(data).build(); + ArrayList records = new ArrayList<>(); + records.add(record); + InsertRequest request = InsertRequest.builder().records(records).build(); + VaultConfig config = new VaultConfig(); + config.setVaultId("vault123"); + + V1InsertRequest body = Utils.getInsertRequestBody(request, config); + + Assert.assertEquals("vault123", body.getVaultId().get()); + Assert.assertEquals(1, body.getRecords().get().size()); + Assert.assertEquals("table1", body.getRecords().get().get(0).getTableName().get()); + Assert.assertEquals(data, body.getRecords().get().get(0).getData().get()); + } + + @Test + public void testGetInsertRequestBody_keepsRequestLevelTableNameOnEnvelopeOnly() { + Map data = new HashMap<>(); + data.put("name", "john"); + InsertRequestRecord record = InsertRequestRecord.builder().data(data).build(); + ArrayList records = new ArrayList<>(); + records.add(record); + InsertRequest request = InsertRequest.builder().tableName("table1").records(records).build(); + VaultConfig config = new VaultConfig(); + config.setVaultId("vault123"); + + V1InsertRequest body = Utils.getInsertRequestBody(request, config); + + Assert.assertEquals("table1", body.getTableName().get()); + Assert.assertFalse(body.getRecords().get().get(0).getTableName().isPresent()); + } + + @Test + public void testGetInsertRequestBody_withTokens() { + Map data = new HashMap<>(); + data.put("name", "john"); + Map tokens = new HashMap<>(); + tokens.put("name", "tok-abc"); + InsertRequestRecord record = InsertRequestRecord.builder().tableName("table1").data(data).tokens(tokens).build(); + ArrayList records = new ArrayList<>(); + records.add(record); + InsertRequest request = InsertRequest.builder().records(records).build(); + VaultConfig config = new VaultConfig(); + config.setVaultId("vault123"); + + V1InsertRequest body = Utils.getInsertRequestBody(request, config); + + Assert.assertEquals(tokens, body.getRecords().get().get(0).getTokens().get()); + } + + @Test + public void testGetInsertRequestBody_withUpsertAtRequestLevel() { + Map data = new HashMap<>(); + data.put("name", "john"); + InsertRequestRecord record = InsertRequestRecord.builder().tableName("table1").data(data).build(); + ArrayList records = new ArrayList<>(); + records.add(record); + UpsertOptions upsert = UpsertOptions.builder() + .uniqueColumns(Collections.singletonList("email")) + .updateType("UPDATE") + .build(); + InsertRequest request = InsertRequest.builder() + .records(records) + .upsert(upsert) + .build(); + VaultConfig config = new VaultConfig(); + config.setVaultId("vault123"); + + V1InsertRequest body = Utils.getInsertRequestBody(request, config); + + // Request-level upsert stays on the envelope; it is not copied onto the records. + Assert.assertFalse(body.getRecords().get().get(0).getUpsert().isPresent()); + Assert.assertTrue(body.getUpsert().isPresent()); + Assert.assertEquals(Collections.singletonList("email"), body.getUpsert().get().getUniqueColumns().get()); + Assert.assertEquals(FlowEnumUpdateType.UPDATE, body.getUpsert().get().getUpdateType().get()); + } + + @Test + public void testGetInsertRequestBody_withUpsertAtRecordLevel() { + Map data = new HashMap<>(); + data.put("name", "john"); + UpsertOptions upsert = UpsertOptions.builder() + .uniqueColumns(Collections.singletonList("email")) + .updateType("REPLACE") + .build(); + InsertRequestRecord record = InsertRequestRecord.builder() + .tableName("table1") + .data(data) + .upsert(upsert) + .build(); + ArrayList records = new ArrayList<>(); + records.add(record); + InsertRequest request = InsertRequest.builder().records(records).build(); + VaultConfig config = new VaultConfig(); + config.setVaultId("vault123"); + + V1InsertRequest body = Utils.getInsertRequestBody(request, config); + + Assert.assertEquals("table1", body.getRecords().get().get(0).getTableName().get()); + Assert.assertTrue(body.getRecords().get().get(0).getUpsert().isPresent()); + Assert.assertEquals(FlowEnumUpdateType.REPLACE, body.getRecords().get().get(0).getUpsert().get().getUpdateType().get()); + } + + // Tests for buildInsertResponse / getDetokenizeRequestBody / buildDetokenizeResponse / + // getTokenizeRequestBody / buildTokenizeResponse / getDeleteTokensRequestBody / + // buildDeleteTokensResponse were removed: those unary Utils helpers no longer exist (bulk-only module). + + // ── getBulkInsertRequestBody (bulk overload) ────────────────────────────── + + @Test + public void testGetBulkInsertRequestBody_bulk_buildsCorrectRequest() { + Map data = new HashMap<>(); + data.put("name", "john"); + BulkInsertRequestRecord record = BulkInsertRequestRecord.builder().data(data).build(); + ArrayList records = new ArrayList<>(); + records.add(record); + BulkInsertRequest request = BulkInsertRequest.builder().tableName("table1").records(records).build(); + VaultConfig config = new VaultConfig(); + config.setVaultId("vault123"); + + V1InsertRequest body = Utils.getBulkInsertRequestBody(request, config); + + Assert.assertEquals("vault123", body.getVaultId().get()); + Assert.assertEquals("table1", body.getTableName().get()); + Assert.assertEquals(1, body.getRecords().get().size()); + Assert.assertFalse(body.getRecords().get().get(0).getTableName().isPresent()); + Assert.assertEquals(data, body.getRecords().get().get(0).getData().get()); + } + + @Test + public void testGetBulkInsertRequestBody_bulk_withUpsertAtRequestLevel() { + Map data = new HashMap<>(); + data.put("name", "john"); + BulkInsertRequestRecord record = BulkInsertRequestRecord.builder().data(data).build(); + ArrayList records = new ArrayList<>(); + records.add(record); + BulkInsertRequest request = BulkInsertRequest.builder() + .tableName("table1") + .records(records) + .upsert(UpsertOptions.builder() + .uniqueColumns(Collections.singletonList("email")) + .updateType("UPDATE") + .build()) + .build(); + VaultConfig config = new VaultConfig(); + config.setVaultId("vault123"); + + V1InsertRequest body = Utils.getBulkInsertRequestBody(request, config); + + // Request-level upsert stays on the envelope; it is not copied onto the records. + Assert.assertFalse(body.getRecords().get().get(0).getUpsert().isPresent()); + Assert.assertTrue(body.getUpsert().isPresent()); + Assert.assertEquals(Collections.singletonList("email"), body.getUpsert().get().getUniqueColumns().get()); + Assert.assertEquals(FlowEnumUpdateType.UPDATE, body.getUpsert().get().getUpdateType().get()); + } + + @Test + public void testGetBulkInsertRequestBody_bulk_withUpsertAtRecordLevel() { + Map data = new HashMap<>(); + data.put("name", "john"); + BulkInsertRequestRecord record = BulkInsertRequestRecord.builder() + .tableName("table1") + .data(data) + .upsert(UpsertOptions.builder() + .uniqueColumns(Collections.singletonList("email")) + .updateType("REPLACE") + .build()) + .build(); + ArrayList records = new ArrayList<>(); + records.add(record); + BulkInsertRequest request = BulkInsertRequest.builder().records(records).build(); + VaultConfig config = new VaultConfig(); + config.setVaultId("vault123"); + + V1InsertRequest body = Utils.getBulkInsertRequestBody(request, config); + + Assert.assertEquals("table1", body.getRecords().get().get(0).getTableName().get()); + Assert.assertTrue(body.getRecords().get().get(0).getUpsert().isPresent()); + Assert.assertEquals(FlowEnumUpdateType.REPLACE, body.getRecords().get().get(0).getUpsert().get().getUpdateType().get()); + } + + // ── getBulkDetokenizeRequestBody ────────────────────────────────────────── + + @Test + public void testGetBulkDetokenizeRequestBody_buildsCorrectRequest() { + BulkDetokenizeRequest request = BulkDetokenizeRequest.builder() + .tokens(Arrays.asList("token1", "token2")) + .build(); + + V1FlowDetokenizeRequest body = Utils.getBulkDetokenizeRequestBody(request, "vault123"); + + Assert.assertEquals("vault123", body.getVaultId().get()); + Assert.assertEquals(Arrays.asList("token1", "token2"), body.getTokens().get()); + Assert.assertFalse(body.getTokenGroupRedactions().isPresent()); + } + + @Test + public void testGetBulkDetokenizeRequestBody_withTokenGroupRedactions() { + TokenGroupRedactions redaction = TokenGroupRedactions.builder() + .tokenGroupName("group1") + .redaction("MASKED") + .build(); + BulkDetokenizeRequest request = BulkDetokenizeRequest.builder() + .tokens(Collections.singletonList("token1")) + .tokenGroupRedactions(Collections.singletonList(redaction)) + .build(); + + V1FlowDetokenizeRequest body = Utils.getBulkDetokenizeRequestBody(request, "vault123"); + + Assert.assertTrue(body.getTokenGroupRedactions().isPresent()); + Assert.assertEquals(1, body.getTokenGroupRedactions().get().size()); + Assert.assertEquals("group1", body.getTokenGroupRedactions().get().get(0).getTokenGroupName().get()); + Assert.assertEquals("MASKED", body.getTokenGroupRedactions().get().get(0).getRedaction().get()); + } + + // ── getBulkDeleteTokensRequestBody ───────────────────────────────────────── + + @Test + public void testGetBulkDeleteTokensRequestBody_buildsCorrectRequest() { + BulkDeleteTokensRequest request = BulkDeleteTokensRequest.builder() + .tokens(Arrays.asList("token1", "token2")) + .build(); + + V1FlowDeleteTokenRequest body = Utils.getBulkDeleteTokensRequestBody(request, "vault123"); + + Assert.assertEquals("vault123", body.getVaultId().get()); + Assert.assertEquals(Arrays.asList("token1", "token2"), body.getTokens().get()); + } + + // ── getBulkTokenizeRequestBody ───────────────────────────────────────────── + + @Test + public void testGetBulkTokenizeRequestBody_buildsCorrectRequest() { + List records = Collections.singletonList( + BulkTokenizeRequestRecord.builder() + .value("value1") + .tokenGroupNames(Collections.singletonList("group1")) + .build()); + + V1FlowTokenizeRequest body = Utils.getBulkTokenizeRequestBody(records, "vault123"); + + Assert.assertEquals("vault123", body.getVaultId().get()); + Assert.assertEquals(1, body.getData().get().size()); + Assert.assertEquals("value1", body.getData().get().get(0).getValue().get()); + Assert.assertEquals(Collections.singletonList("group1"), body.getData().get().get(0).getTokenGroupNames().get()); + Assert.assertFalse(body.getData().get().get(0).getToken().isPresent()); + } + + @Test + public void testGetBulkTokenizeRequestBody_carriesByotToken() { + List records = Collections.singletonList( + BulkTokenizeRequestRecord.builder() + .value("value1") + .token("my-own-token") + .tokenGroupNames(Collections.singletonList("group1")) + .build()); + + V1FlowTokenizeRequest body = Utils.getBulkTokenizeRequestBody(records, "vault123"); + + Assert.assertEquals("my-own-token", body.getData().get().get(0).getToken().get()); + } + + // ── createBulkInsertBatches ──────────────────────────────────────────────── + + @Test + public void testCreateBulkInsertBatches_splitsEvenly() { + List records = new ArrayList<>(); + for (int i = 0; i < 4; i++) { + records.add(V1InsertRecordData.builder().data(new HashMap<>()).build()); + } + + List> batches = Utils.createBulkInsertBatches(records, 2); + + Assert.assertEquals(2, batches.size()); + Assert.assertEquals(2, batches.get(0).size()); + Assert.assertEquals(2, batches.get(1).size()); + } + + @Test + public void testCreateBulkInsertBatches_splitsWithRemainder() { + List records = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + records.add(V1InsertRecordData.builder().data(new HashMap<>()).build()); + } + + List> batches = Utils.createBulkInsertBatches(records, 2); + + Assert.assertEquals(3, batches.size()); + Assert.assertEquals(1, batches.get(2).size()); + } + + // ── createBulkDetokenizeBatches ──────────────────────────────────────────── + + @Test + public void testCreateBulkDetokenizeBatches_splitsTokens() { + V1FlowDetokenizeRequest request = V1FlowDetokenizeRequest.builder() + .vaultId("vault123") + .tokens(Arrays.asList("t1", "t2", "t3")) + .build(); + + List batches = Utils.createBulkDetokenizeBatches(request, 2); + + Assert.assertEquals(2, batches.size()); + Assert.assertEquals(Arrays.asList("t1", "t2"), batches.get(0).getTokens().get()); + Assert.assertEquals(Collections.singletonList("t3"), batches.get(1).getTokens().get()); + Assert.assertEquals("vault123", batches.get(0).getVaultId().get()); + } + + @Test + public void testCreateBulkDetokenizeBatches_carriesTokenGroupRedactions() { + com.skyflow.generated.rest.types.V1TokenGroupRedactions redaction = + com.skyflow.generated.rest.types.V1TokenGroupRedactions.builder() + .tokenGroupName("group1") + .redaction("MASKED") + .build(); + V1FlowDetokenizeRequest request = V1FlowDetokenizeRequest.builder() + .vaultId("vault123") + .tokens(Arrays.asList("t1", "t2")) + .tokenGroupRedactions(Collections.singletonList(redaction)) + .build(); + + List batches = Utils.createBulkDetokenizeBatches(request, 5); + + Assert.assertEquals(1, batches.size()); + Assert.assertTrue(batches.get(0).getTokenGroupRedactions().isPresent()); + Assert.assertEquals("group1", batches.get(0).getTokenGroupRedactions().get().get(0).getTokenGroupName().get()); + } + + // ── createBulkDeleteTokensBatches ────────────────────────────────────────── + + @Test + public void testCreateBulkDeleteTokensBatches_splitsTokens() { + V1FlowDeleteTokenRequest request = V1FlowDeleteTokenRequest.builder() + .vaultId("vault123") + .tokens(Arrays.asList("t1", "t2", "t3")) + .build(); + + List batches = Utils.createBulkDeleteTokensBatches(request, 2); + + Assert.assertEquals(2, batches.size()); + Assert.assertEquals(Arrays.asList("t1", "t2"), batches.get(0).getTokens().get()); + Assert.assertEquals(Collections.singletonList("t3"), batches.get(1).getTokens().get()); + } + + // ── createBulkTokenizeBatches ────────────────────────────────────────────── + + @Test + public void testCreateBulkTokenizeBatches_splitsData() { + List records = Arrays.asList( + BulkTokenizeRequestRecord.builder().value("v1").build(), + BulkTokenizeRequestRecord.builder().value("v2").build()); + + List> batches = Utils.createBulkTokenizeBatches(records, 1); + + Assert.assertEquals(2, batches.size()); + Assert.assertEquals(1, batches.get(0).size()); + Assert.assertEquals("v1", batches.get(0).get(0).getValue()); + Assert.assertEquals("v2", batches.get(1).get(0).getValue()); + } + + // ── createErrorRecord ────────────────────────────────────────────────────── + + @Test + public void testCreateErrorRecord_withHttpCodeKey() { + Map recordMap = new HashMap<>(); + recordMap.put("http_code", 400); + recordMap.put("error", "bad request"); + + ErrorRecord err = Utils.createErrorRecord(recordMap, 0, "req-1"); + + Assert.assertEquals(400, err.getCode()); + Assert.assertEquals("bad request", err.getError()); + Assert.assertEquals("req-1", err.getRequestId()); + } + + @Test + public void testCreateErrorRecord_withStatusCodeKey() { + Map recordMap = new HashMap<>(); + recordMap.put("statusCode", 500); + recordMap.put("message", "server error"); + + ErrorRecord err = Utils.createErrorRecord(recordMap, 2, null); + + Assert.assertEquals(500, err.getCode()); + Assert.assertEquals("server error", err.getError()); + Assert.assertEquals(2, err.getIndex()); + } + + @Test + public void testCreateErrorRecord_nullMapReturnsNull() { + Assert.assertNull(Utils.createErrorRecord(null, 0, null)); + } + + @Test + public void testCreateErrorRecord_noCodeKeyDefaultsTo500() { + Map recordMap = new HashMap<>(); + recordMap.put("error", "something went wrong"); + + ErrorRecord err = Utils.createErrorRecord(recordMap, 1, "req-2"); + + Assert.assertEquals(500, err.getCode()); + Assert.assertEquals("something went wrong", err.getError()); + } + + @Test + public void testCreateErrorRecord_noErrorOrMessageKeyDefaultsToUnknownError() { + Map recordMap = new HashMap<>(); + recordMap.put("http_code", 403); + + ErrorRecord err = Utils.createErrorRecord(recordMap, 3, null); + + Assert.assertEquals(403, err.getCode()); + Assert.assertEquals("Unknown error", err.getError()); + } + + // ── handleBulkInsertBatchException ──────────────────────────────────────── + + @Test + public void testHandleBulkInsertBatchException_apiExceptionWithRecordsBody() { + Map errorRecordMap = new HashMap<>(); + errorRecordMap.put("error", "duplicate"); + errorRecordMap.put("httpCode", 409); + Map body = new HashMap<>(); + body.put("records", Collections.singletonList(errorRecordMap)); + ApiClientApiException apiEx = new ApiClientApiException("insert failed", 409, body); + RuntimeException wrapper = new RuntimeException(apiEx); + + List batch = Collections.singletonList(V1InsertRecordData.builder().data(new HashMap<>()).build()); + List errors = Utils.handleBulkInsertBatchException(wrapper, batch, 0, 50); + + Assert.assertEquals(1, errors.size()); + Assert.assertEquals(409, errors.get(0).getHttpCode()); + Assert.assertEquals("duplicate", errors.get(0).getError()); + } + + @Test + public void testHandleBulkInsertBatchException_apiExceptionWithNoParsableBody() { + ApiClientApiException apiEx = new ApiClientApiException("insert failed", 401, "unauthorized"); + RuntimeException wrapper = new RuntimeException(apiEx); + + List batch = Arrays.asList( + V1InsertRecordData.builder().data(new HashMap<>()).build(), + V1InsertRecordData.builder().data(new HashMap<>()).build()); + List errors = Utils.handleBulkInsertBatchException(wrapper, batch, 0, 50); + + Assert.assertEquals(2, errors.size()); + Assert.assertEquals(401, errors.get(0).getHttpCode()); + Assert.assertEquals("insert failed", errors.get(0).getError()); + } + + @Test + public void testHandleBulkInsertBatchException_apiExceptionWithErrorKeyBody() { + Map body = new HashMap<>(); + body.put("error", "top level auth error"); + ApiClientApiException apiEx = new ApiClientApiException("insert failed", 401, body); + RuntimeException wrapper = new RuntimeException(apiEx); + + List batch = Collections.singletonList(V1InsertRecordData.builder().data(new HashMap<>()).build()); + List errors = Utils.handleBulkInsertBatchException(wrapper, batch, 0, 50); + + Assert.assertEquals(1, errors.size()); + Assert.assertEquals(401, errors.get(0).getHttpCode()); + Assert.assertEquals("top level auth error", errors.get(0).getError()); + } + + @Test + public void testHandleBulkInsertBatchException_genericException() { + RuntimeException ex = new RuntimeException("boom"); + List batch = Collections.singletonList(V1InsertRecordData.builder().data(new HashMap<>()).build()); + + List errors = Utils.handleBulkInsertBatchException(ex, batch, 1, 2); + + Assert.assertEquals(1, errors.size()); + Assert.assertEquals(500, errors.get(0).getHttpCode()); + Assert.assertEquals("boom", errors.get(0).getError()); + Assert.assertEquals(2, errors.get(0).getIndex()); + // Projected error records carry no table/id/field data. + Assert.assertNull(errors.get(0).getTableName()); + Assert.assertNull(errors.get(0).getSkyflowId()); + Assert.assertNull(errors.get(0).getFields()); + Assert.assertNull(errors.get(0).getHashedData()); + } + + @Test + public void testHandleBulkInsertBatchException_recordsBodyCarriesTableAndSkyflowId() { + // createInsertErrorRecord now builds a BulkInsertResponseRecord directly, so per-record + // table/id data from the error body survives instead of being nulled out. + Map recordMap = new HashMap<>(); + recordMap.put("tableName", "cards"); + recordMap.put("error", "duplicate"); + recordMap.put("httpCode", 409); + Map body = new HashMap<>(); + body.put("records", Collections.singletonList(recordMap)); + ApiClientApiException apiEx = new ApiClientApiException("insert failed", 409, body); + RuntimeException wrapper = new RuntimeException(apiEx); + + List batch = Collections.singletonList( + V1InsertRecordData.builder().data(new HashMap<>()).build()); + List records = Utils.handleBulkInsertBatchException(wrapper, batch, 0, 50); + + Assert.assertEquals(1, records.size()); + Assert.assertEquals("cards", records.get(0).getTableName()); + Assert.assertEquals("duplicate", records.get(0).getError()); + Assert.assertEquals(409, records.get(0).getHttpCode()); + } + + @Test + public void testHandleBulkInsertBatchException_recordsBodyFallsBackToUnknownError() { + // An entry with no error/message key still produces a record rather than being skipped. + Map recordMap = new HashMap<>(); + recordMap.put("httpCode", 500); + Map body = new HashMap<>(); + body.put("records", Collections.singletonList(recordMap)); + ApiClientApiException apiEx = new ApiClientApiException("insert failed", 500, body); + RuntimeException wrapper = new RuntimeException(apiEx); + + List batch = Collections.singletonList( + V1InsertRecordData.builder().data(new HashMap<>()).build()); + List records = Utils.handleBulkInsertBatchException(wrapper, batch, 0, 50); + + Assert.assertEquals(1, records.size()); + Assert.assertEquals("Unknown error", records.get(0).getError()); + Assert.assertEquals(500, records.get(0).getHttpCode()); + } + + @Test + public void testHandleBulkInsertBatchException_indexOffsetAcrossBatches() { + Map body = new HashMap<>(); + body.put("error", "auth error"); + ApiClientApiException apiEx = new ApiClientApiException("insert failed", 401, body); + RuntimeException wrapper = new RuntimeException(apiEx); + + List batch = Arrays.asList( + V1InsertRecordData.builder().data(new HashMap<>()).build(), + V1InsertRecordData.builder().data(new HashMap<>()).build()); + List records = Utils.handleBulkInsertBatchException(wrapper, batch, 2, 50); + + Assert.assertEquals(100, records.get(0).getIndex()); + Assert.assertEquals(101, records.get(1).getIndex()); + } + + @Test + public void testHandleBulkInsertBatchException_genericExceptionHasNoRequestId() { + // A non-API failure has no response headers to read a request id from. + RuntimeException ex = new RuntimeException("boom"); + List batch = Collections.singletonList( + V1InsertRecordData.builder().data(new HashMap<>()).build()); + + List records = Utils.handleBulkInsertBatchException(ex, batch, 0, 50); + + Assert.assertEquals(1, records.size()); + Assert.assertNull(records.get(0).getRequestId()); + Assert.assertEquals("boom", records.get(0).getError()); + } + + @Test + public void testHandleBulkInsertBatchException_nonApiCauseUsesCauseMessage() { + // Cause is non-null but not an ApiClientApiException: the message ladder should + // pick up the cause's own message rather than the outer wrapper's. + RuntimeException ex = new RuntimeException("wrapper", new IllegalStateException("inner boom")); + List batch = Collections.singletonList( + V1InsertRecordData.builder().data(new HashMap<>()).build()); + + List records = Utils.handleBulkInsertBatchException(ex, batch, 0, 50); + + Assert.assertEquals(1, records.size()); + Assert.assertEquals(500, records.get(0).getHttpCode()); + Assert.assertEquals("inner boom", records.get(0).getError()); + } + + @Test + public void testHandleBulkInsertBatchException_nonApiCauseWithNestedCauseUsesNestedToString() { + // When the cause itself wraps another throwable, the ladder resolves the message + // down to the nested cause's toString(). + RuntimeException ex = new RuntimeException("wrapper", + new IllegalStateException("inner boom", new IllegalArgumentException("root cause"))); + List batch = Collections.singletonList( + V1InsertRecordData.builder().data(new HashMap<>()).build()); + + List records = Utils.handleBulkInsertBatchException(ex, batch, 0, 50); + + Assert.assertEquals(1, records.size()); + Assert.assertEquals(500, records.get(0).getHttpCode()); + Assert.assertEquals("java.lang.IllegalArgumentException: root cause", records.get(0).getError()); + } + + // ── createInsertErrorRecord / createDetokenizeErrorRecord branch coverage ─ + + @Test + public void testCreateInsertErrorRecord_nullRecordMapReturnsNull() { + Assert.assertNull(Utils.createInsertErrorRecord(null, 0, "req-1")); + Assert.assertNull(Utils.createDetokenizeErrorRecord(null, 0, "req-1")); + } + + @Test + public void testCreateErrorRecords_snakeCaseHttpCodeKey() { + Map recordMap = new HashMap<>(); + recordMap.put("http_code", 409); + recordMap.put("error", "duplicate"); + + Assert.assertEquals(409, Utils.createInsertErrorRecord(recordMap, 0, null).getHttpCode()); + Assert.assertEquals(409, Utils.createDetokenizeErrorRecord(recordMap, 0, null).getHttpCode()); + } + + @Test + public void testCreateErrorRecords_statusCodeKey() { + Map recordMap = new HashMap<>(); + recordMap.put("statusCode", 422); + recordMap.put("error", "unprocessable"); + + Assert.assertEquals(422, Utils.createInsertErrorRecord(recordMap, 0, null).getHttpCode()); + Assert.assertEquals(422, Utils.createDetokenizeErrorRecord(recordMap, 0, null).getHttpCode()); + } + + @Test + public void testCreateErrorRecords_noHttpCodeKeyDefaultsTo500() { + Map recordMap = new HashMap<>(); + recordMap.put("error", "no code supplied"); + + Assert.assertEquals(500, Utils.createInsertErrorRecord(recordMap, 0, null).getHttpCode()); + Assert.assertEquals(500, Utils.createDetokenizeErrorRecord(recordMap, 0, null).getHttpCode()); + } + + // ── error-record building: keys present with explicit null values ──────── + // Regression: the vault sends "skyflowID": null / "tableName": null on failed records, and + // containsKey() is true for those. Reading them unguarded threw NPE and masked the real error. + + @Test + public void testCreateInsertErrorRecord_nullSkyflowIdAndTableNameDoNotThrow() { + Map recordMap = new HashMap<>(); + recordMap.put("skyflowID", null); + recordMap.put("tableName", null); + recordMap.put("error", "Invalid request. Table not found."); + recordMap.put("httpCode", 400); + + BulkInsertResponseRecord record = Utils.createInsertErrorRecord(recordMap, 0, "req-1"); + + Assert.assertNull(record.getSkyflowId()); + Assert.assertNull(record.getTableName()); + Assert.assertEquals(400, record.getHttpCode()); + Assert.assertEquals("Invalid request. Table not found.", record.getError()); + } + + @Test + public void testCreateDetokenizeErrorRecord_nullTokenFieldsDoNotThrow() { + Map recordMap = new HashMap<>(); + recordMap.put("token", null); + recordMap.put("tokenGroupName", null); + recordMap.put("error", "Token not found."); + recordMap.put("httpCode", 404); + + BulkDetokenizeResponseRecord record = Utils.createDetokenizeErrorRecord(recordMap, 0, "req-1"); + + Assert.assertNull(record.getToken()); + Assert.assertNull(record.getTokenGroupName()); + Assert.assertEquals(404, record.getHttpCode()); + Assert.assertEquals("Token not found.", record.getError()); + } + + @Test + public void testCreateErrorRecords_nullHttpCodeValueFallsBackTo500() { + Map recordMap = new HashMap<>(); + recordMap.put("httpCode", null); + recordMap.put("error", "boom"); + + Assert.assertEquals(500, Utils.createErrorRecord(recordMap, 0, null).getCode()); + Assert.assertEquals(500, Utils.createInsertErrorRecord(recordMap, 0, null).getHttpCode()); + Assert.assertEquals(500, Utils.createDetokenizeErrorRecord(recordMap, 0, null).getHttpCode()); + } + + @Test + public void testCreateErrorRecords_nonIntegerHttpCodeIsCoerced() { + Map asLong = new HashMap<>(); + asLong.put("httpCode", 409L); + asLong.put("error", "conflict"); + Assert.assertEquals(409, Utils.createInsertErrorRecord(asLong, 0, null).getHttpCode()); + + Map asDouble = new HashMap<>(); + asDouble.put("httpCode", 503.0d); + asDouble.put("error", "unavailable"); + Assert.assertEquals(503, Utils.createInsertErrorRecord(asDouble, 0, null).getHttpCode()); + + Map asText = new HashMap<>(); + asText.put("httpCode", "422"); + asText.put("error", "unprocessable"); + Assert.assertEquals(422, Utils.createInsertErrorRecord(asText, 0, null).getHttpCode()); + } + + @Test + public void testCreateErrorRecords_nonStringErrorDoesNotThrow() { + Map nested = new HashMap<>(); + nested.put("detail", "inner"); + Map recordMap = new HashMap<>(); + recordMap.put("error", nested); + recordMap.put("httpCode", 500); + + Assert.assertNotNull(Utils.createInsertErrorRecord(recordMap, 0, null).getError()); + Assert.assertNotNull(Utils.createErrorRecord(recordMap, 0, null).getError()); + } + + @Test + public void testCreateErrorRecords_nullErrorValueFallsThroughToMessage() { + // A null error text would make the record read as a SUCCESS downstream, since failures are + // counted by getError() != null. + Map withMessage = new HashMap<>(); + withMessage.put("error", null); + withMessage.put("message", "vault unreachable"); + Assert.assertEquals("vault unreachable", Utils.createInsertErrorRecord(withMessage, 0, null).getError()); + + Map withNeither = new HashMap<>(); + withNeither.put("error", null); + withNeither.put("message", null); + Assert.assertEquals("Unknown error", Utils.createInsertErrorRecord(withNeither, 0, null).getError()); + } + + @Test + public void testCreateErrorRecords_messageKeyIsUsedWhenErrorKeyAbsent() { + Map recordMap = new HashMap<>(); + recordMap.put("message", "vault not found"); + recordMap.put("httpCode", 404); + + Assert.assertEquals("vault not found", Utils.createInsertErrorRecord(recordMap, 0, null).getError()); + Assert.assertEquals("vault not found", Utils.createDetokenizeErrorRecord(recordMap, 0, null).getError()); + } + + @Test + public void testCreateInsertErrorRecord_readsSkyflowIdUsingWireCasing() { + // The API returns the id as "skyflowID" — matching @JsonProperty("skyflowID") on the + // generated V1RecordResponseObject — even though the SDK exposes it as getSkyflowId(). + Map recordMap = new HashMap<>(); + recordMap.put("skyflowID", "id-1"); + recordMap.put("tableName", "cards"); + recordMap.put("error", "duplicate"); + + BulkInsertResponseRecord record = Utils.createInsertErrorRecord(recordMap, 0, null); + + Assert.assertEquals("id-1", record.getSkyflowId()); + Assert.assertEquals("cards", record.getTableName()); + } + + @Test + public void testCreateErrorRecords_requestIdIsCarried() { + Map recordMap = new HashMap<>(); + recordMap.put("error", "boom"); + + Assert.assertEquals("req-7", Utils.createInsertErrorRecord(recordMap, 3, "req-7").getRequestId()); + Assert.assertEquals("req-7", Utils.createDetokenizeErrorRecord(recordMap, 3, "req-7").getRequestId()); + Assert.assertEquals(3, Utils.createInsertErrorRecord(recordMap, 3, "req-7").getIndex()); + } + + // ── handleBulkInsertBatchException, remaining branches ──────────────────── + + @Test + public void testHandleBulkInsertBatchException_errorFieldAsObjectUsesHelper() { + // The API's structured error envelope: {"error": {message, httpCode, ...}} + Map errorObject = new HashMap<>(); + errorObject.put("message", "vault not found"); + errorObject.put("httpCode", 404); + Map body = new HashMap<>(); + body.put("error", errorObject); + ApiClientApiException apiEx = new ApiClientApiException("insert failed", 404, body); + RuntimeException wrapper = new RuntimeException(apiEx); + + List batch = Arrays.asList( + V1InsertRecordData.builder().data(new HashMap<>()).build(), + V1InsertRecordData.builder().data(new HashMap<>()).build()); + List records = Utils.handleBulkInsertBatchException(wrapper, batch, 0, 50); + + Assert.assertEquals(2, records.size()); + for (BulkInsertResponseRecord record : records) { + Assert.assertEquals("vault not found", record.getError()); + Assert.assertEquals(404, record.getHttpCode()); + } + } + + @Test + public void testHandleBulkInsertBatchException_errorFieldNeitherMapNorStringUsesApiMessage() { + Map body = new HashMap<>(); + body.put("error", 500); + ApiClientApiException apiEx = new ApiClientApiException("insert failed", 500, body); + RuntimeException wrapper = new RuntimeException(apiEx); + + List batch = Collections.singletonList( + V1InsertRecordData.builder().data(new HashMap<>()).build()); + List records = Utils.handleBulkInsertBatchException(wrapper, batch, 0, 50); + + Assert.assertEquals(1, records.size()); + Assert.assertEquals("insert failed", records.get(0).getError()); + } + + @Test + public void testHandleBulkInsertBatchException_recordsNotAListFallsBackToBatchWideError() { + Map body = new HashMap<>(); + body.put("records", "not-a-list"); + ApiClientApiException apiEx = new ApiClientApiException("insert failed", 400, body); + RuntimeException wrapper = new RuntimeException(apiEx); + + List batch = Collections.singletonList( + V1InsertRecordData.builder().data(new HashMap<>()).build()); + List records = Utils.handleBulkInsertBatchException(wrapper, batch, 0, 50); + + Assert.assertEquals(1, records.size()); + Assert.assertEquals("insert failed", records.get(0).getError()); + Assert.assertEquals(400, records.get(0).getHttpCode()); + } + + @Test + public void testHandleBulkInsertBatchException_nonMapEntriesAreSkipped() { + Map body = new HashMap<>(); + body.put("records", Arrays.asList("not-a-map", null)); + ApiClientApiException apiEx = new ApiClientApiException("insert failed", 400, body); + RuntimeException wrapper = new RuntimeException(apiEx); + + List batch = Collections.singletonList( + V1InsertRecordData.builder().data(new HashMap<>()).build()); + List records = Utils.handleBulkInsertBatchException(wrapper, batch, 0, 50); + + // No entry parsed, so the batch-wide fallback fires instead. + Assert.assertEquals(1, records.size()); + Assert.assertEquals("insert failed", records.get(0).getError()); + } + + @Test + public void testHandleBulkInsertBatchException_bodyWithNeitherRecordsNorErrorKey() { + // A map body that matches neither branch falls through to the batch-wide fallback. + Map body = new HashMap<>(); + body.put("unexpected", "shape"); + ApiClientApiException apiEx = new ApiClientApiException("insert failed", 503, body); + RuntimeException wrapper = new RuntimeException(apiEx); + + List batch = Collections.singletonList( + V1InsertRecordData.builder().data(new HashMap<>()).build()); + List records = Utils.handleBulkInsertBatchException(wrapper, batch, 0, 50); + + Assert.assertEquals(1, records.size()); + Assert.assertEquals("insert failed", records.get(0).getError()); + Assert.assertEquals(503, records.get(0).getHttpCode()); + } + + // ── handleBulkDetokenizeBatchException ──────────────────────────────────── + + @Test + public void testHandleBulkDetokenizeBatchException_apiExceptionWithResponseBody() { + Map errorRecordMap = new HashMap<>(); + errorRecordMap.put("error", "token not found"); + errorRecordMap.put("httpCode", 404); + Map body = new HashMap<>(); + body.put("response", Collections.singletonList(errorRecordMap)); + ApiClientApiException apiEx = new ApiClientApiException("detokenize failed", 404, body); + RuntimeException wrapper = new RuntimeException(apiEx); + + V1FlowDetokenizeRequest batch = V1FlowDetokenizeRequest.builder() + .vaultId("vault123") + .tokens(Collections.singletonList("t1")) + .build(); + List errors = Utils.handleBulkDetokenizeBatchException(wrapper, batch, 0, 50); + + Assert.assertEquals(1, errors.size()); + Assert.assertEquals(404, errors.get(0).getHttpCode()); + Assert.assertEquals("token not found", errors.get(0).getError()); + Assert.assertEquals(0, errors.get(0).getIndex()); + // This entry carried no token/group of its own, so those stay null. + Assert.assertNull(errors.get(0).getToken()); + Assert.assertNull(errors.get(0).getTokenGroupName()); + Assert.assertNull(errors.get(0).getMetadata()); + } + + @Test + public void testHandleBulkDetokenizeBatchException_responseBodyCarriesTokenAndGroup() { + // createDetokenizeErrorRecord builds a BulkDetokenizeResponseRecord directly, so the + // failing token echoed back by the API survives instead of being nulled out. + Map errorRecordMap = new HashMap<>(); + errorRecordMap.put("token", "tok-bad"); + errorRecordMap.put("tokenGroupName", "email_group"); + errorRecordMap.put("error", "token not found"); + errorRecordMap.put("httpCode", 404); + Map body = new HashMap<>(); + body.put("response", Collections.singletonList(errorRecordMap)); + ApiClientApiException apiEx = new ApiClientApiException("detokenize failed", 404, body); + RuntimeException wrapper = new RuntimeException(apiEx); + + V1FlowDetokenizeRequest batch = V1FlowDetokenizeRequest.builder() + .vaultId("vault123") + .tokens(Collections.singletonList("tok-bad")) + .build(); + List records = Utils.handleBulkDetokenizeBatchException(wrapper, batch, 0, 50); + + Assert.assertEquals(1, records.size()); + Assert.assertEquals("tok-bad", records.get(0).getToken()); + Assert.assertEquals("email_group", records.get(0).getTokenGroupName()); + Assert.assertEquals("token not found", records.get(0).getError()); + Assert.assertEquals(404, records.get(0).getHttpCode()); + } + + @Test + public void testHandleBulkDetokenizeBatchException_responseBodyFallsBackToUnknownError() { + Map errorRecordMap = new HashMap<>(); + errorRecordMap.put("httpCode", 500); + Map body = new HashMap<>(); + body.put("response", Collections.singletonList(errorRecordMap)); + ApiClientApiException apiEx = new ApiClientApiException("detokenize failed", 500, body); + RuntimeException wrapper = new RuntimeException(apiEx); + + V1FlowDetokenizeRequest batch = V1FlowDetokenizeRequest.builder() + .vaultId("vault123") + .tokens(Collections.singletonList("t1")) + .build(); + List records = Utils.handleBulkDetokenizeBatchException(wrapper, batch, 0, 50); + + Assert.assertEquals(1, records.size()); + Assert.assertEquals("Unknown error", records.get(0).getError()); + Assert.assertEquals(500, records.get(0).getHttpCode()); + } + + @Test + public void testHandleBulkDetokenizeBatchException_topLevelErrorAppliesToEveryToken() { + Map body = new HashMap<>(); + body.put("error", "top level auth error"); + ApiClientApiException apiEx = new ApiClientApiException("detokenize failed", 401, body); + RuntimeException wrapper = new RuntimeException(apiEx); + + V1FlowDetokenizeRequest batch = V1FlowDetokenizeRequest.builder() + .vaultId("vault123") + .tokens(Arrays.asList("t1", "t2")) + .build(); + List records = Utils.handleBulkDetokenizeBatchException(wrapper, batch, 2, 50); + + Assert.assertEquals(2, records.size()); + Assert.assertEquals("top level auth error", records.get(0).getError()); + Assert.assertEquals(401, records.get(0).getHttpCode()); + // index continues from the batch offset + Assert.assertEquals(100, records.get(0).getIndex()); + Assert.assertEquals(101, records.get(1).getIndex()); + } + + @Test + public void testHandleBulkDetokenizeBatchException_errorFieldAsObjectUsesHelper() { + Map errorObject = new HashMap<>(); + errorObject.put("message", "vault not found"); + errorObject.put("httpCode", 404); + Map body = new HashMap<>(); + body.put("error", errorObject); + ApiClientApiException apiEx = new ApiClientApiException("detokenize failed", 404, body); + RuntimeException wrapper = new RuntimeException(apiEx); + + V1FlowDetokenizeRequest batch = V1FlowDetokenizeRequest.builder() + .vaultId("vault123") + .tokens(Arrays.asList("t1", "t2")) + .build(); + List records = Utils.handleBulkDetokenizeBatchException(wrapper, batch, 0, 50); + + Assert.assertEquals(2, records.size()); + for (BulkDetokenizeResponseRecord record : records) { + Assert.assertEquals("vault not found", record.getError()); + Assert.assertEquals(404, record.getHttpCode()); + } + } + + @Test + public void testHandleBulkDetokenizeBatchException_errorFieldNeitherMapNorStringUsesApiMessage() { + Map body = new HashMap<>(); + body.put("error", 500); + ApiClientApiException apiEx = new ApiClientApiException("detokenize failed", 500, body); + RuntimeException wrapper = new RuntimeException(apiEx); + + V1FlowDetokenizeRequest batch = V1FlowDetokenizeRequest.builder() + .vaultId("vault123") + .tokens(Collections.singletonList("t1")) + .build(); + List records = Utils.handleBulkDetokenizeBatchException(wrapper, batch, 0, 50); + + Assert.assertEquals(1, records.size()); + Assert.assertEquals("detokenize failed", records.get(0).getError()); + } + + @Test + public void testHandleBulkDetokenizeBatchException_responseNotAListFallsBackToBatchWideError() { + Map body = new HashMap<>(); + body.put("response", "not-a-list"); + ApiClientApiException apiEx = new ApiClientApiException("detokenize failed", 400, body); + RuntimeException wrapper = new RuntimeException(apiEx); + + V1FlowDetokenizeRequest batch = V1FlowDetokenizeRequest.builder() + .vaultId("vault123") + .tokens(Collections.singletonList("t1")) + .build(); + List records = Utils.handleBulkDetokenizeBatchException(wrapper, batch, 0, 50); + + Assert.assertEquals(1, records.size()); + Assert.assertEquals("detokenize failed", records.get(0).getError()); + } + + @Test + public void testHandleBulkDetokenizeBatchException_nonMapEntriesAreSkipped() { + Map body = new HashMap<>(); + body.put("response", Arrays.asList("not-a-map", null)); + ApiClientApiException apiEx = new ApiClientApiException("detokenize failed", 400, body); + RuntimeException wrapper = new RuntimeException(apiEx); + + V1FlowDetokenizeRequest batch = V1FlowDetokenizeRequest.builder() + .vaultId("vault123") + .tokens(Collections.singletonList("t1")) + .build(); + List records = Utils.handleBulkDetokenizeBatchException(wrapper, batch, 0, 50); + + Assert.assertEquals(1, records.size()); + Assert.assertEquals("detokenize failed", records.get(0).getError()); + } + + @Test + public void testHandleBulkDetokenizeBatchException_batchWithNoTokensProducesNoRecords() { + Map body = new HashMap<>(); + body.put("error", "auth error"); + ApiClientApiException apiEx = new ApiClientApiException("detokenize failed", 401, body); + RuntimeException wrapper = new RuntimeException(apiEx); + + V1FlowDetokenizeRequest batch = V1FlowDetokenizeRequest.builder().vaultId("vault123").build(); + List records = Utils.handleBulkDetokenizeBatchException(wrapper, batch, 0, 50); + + Assert.assertTrue(records.isEmpty()); + } + + @Test + public void testHandleBulkDetokenizeBatchException_bodyWithNeitherResponseNorErrorKey() { + Map body = new HashMap<>(); + body.put("unexpected", "shape"); + ApiClientApiException apiEx = new ApiClientApiException("detokenize failed", 503, body); + RuntimeException wrapper = new RuntimeException(apiEx); + + V1FlowDetokenizeRequest batch = V1FlowDetokenizeRequest.builder() + .vaultId("vault123") + .tokens(Collections.singletonList("t1")) + .build(); + List records = Utils.handleBulkDetokenizeBatchException(wrapper, batch, 0, 50); + + Assert.assertEquals(1, records.size()); + Assert.assertEquals("detokenize failed", records.get(0).getError()); + Assert.assertEquals(503, records.get(0).getHttpCode()); + } + + // ── formatBulk*Response, empty/absent bodies ────────────────────────────── + + @Test + public void testFormatBulkResponses_nullResponseReturnsNull() { + Assert.assertNull(Utils.formatBulkInsertResponse(null, 0, 50, null)); + Assert.assertNull(Utils.formatBulkDetokenizeResponse(null, 0, 50, null)); + } + + @Test + public void testFormatBulkResponses_absentRecordsReturnsNull() { + Assert.assertNull(Utils.formatBulkInsertResponse( + V1InsertResponse.builder().build(), 0, 50, null)); + Assert.assertNull(Utils.formatBulkDetokenizeResponse( + V1FlowDetokenizeResponse.builder().build(), 0, 50, null)); + } + + @Test + public void testHandleBulkDetokenizeBatchException_genericExceptionHasNoRequestId() { + RuntimeException ex = new RuntimeException("boom"); + V1FlowDetokenizeRequest batch = V1FlowDetokenizeRequest.builder() + .vaultId("vault123") + .tokens(Collections.singletonList("t1")) + .build(); + + List records = Utils.handleBulkDetokenizeBatchException(ex, batch, 0, 50); + + Assert.assertEquals(1, records.size()); + Assert.assertNull(records.get(0).getRequestId()); + Assert.assertEquals("boom", records.get(0).getError()); + } + + @Test + public void testHandleBulkDetokenizeBatchException_genericException() { + RuntimeException ex = new RuntimeException("boom"); + V1FlowDetokenizeRequest batch = V1FlowDetokenizeRequest.builder() + .vaultId("vault123") + .tokens(Collections.singletonList("t1")) + .build(); + + List errors = Utils.handleBulkDetokenizeBatchException(ex, batch, 0, 50); + + Assert.assertEquals(1, errors.size()); + Assert.assertEquals(500, errors.get(0).getHttpCode()); + Assert.assertEquals("boom", errors.get(0).getError()); + } + + @Test + public void testHandleBulkDetokenizeBatchException_nonApiCauseUsesCauseMessage() { + // Cause is non-null but not an ApiClientApiException: the message ladder resolves the + // nested cause's toString() rather than the outer wrapper's message. + RuntimeException ex = new RuntimeException("wrapper", + new IllegalStateException("inner boom", new IllegalArgumentException("root cause"))); + V1FlowDetokenizeRequest batch = V1FlowDetokenizeRequest.builder() + .vaultId("vault123") + .tokens(Collections.singletonList("t1")) + .build(); + + List records = Utils.handleBulkDetokenizeBatchException(ex, batch, 0, 50); + + Assert.assertEquals(1, records.size()); + Assert.assertEquals(500, records.get(0).getHttpCode()); + Assert.assertEquals("java.lang.IllegalArgumentException: root cause", records.get(0).getError()); + } + + // ── handleBulkDeleteTokensBatchException ────────────────────────────────── + + @Test + public void testHandleBulkDeleteTokensBatchException_apiExceptionWithTokensBody() { + Map errorRecordMap = new HashMap<>(); + errorRecordMap.put("error", "not found"); + errorRecordMap.put("httpCode", 404); + Map body = new HashMap<>(); + body.put("tokens", Collections.singletonList(errorRecordMap)); + ApiClientApiException apiEx = new ApiClientApiException("delete failed", 404, body); + RuntimeException wrapper = new RuntimeException(apiEx); + + V1FlowDeleteTokenRequest batch = V1FlowDeleteTokenRequest.builder() + .vaultId("vault123") + .tokens(Collections.singletonList("t1")) + .build(); + List errors = Utils.handleBulkDeleteTokensBatchException(wrapper, batch, 0, 50); + + Assert.assertEquals(1, errors.size()); + Assert.assertEquals(Integer.valueOf(404), errors.get(0).getHttpCode()); + Assert.assertEquals("not found", errors.get(0).getError()); + // token comes from the batch we sent, so an error record is never missing it + Assert.assertEquals("t1", errors.get(0).getToken()); + Assert.assertEquals(0, errors.get(0).getIndex()); + } + + @Test + public void testHandleBulkDeleteTokensBatchException_apiExceptionWithErrorKeyBody() { + Map body = new HashMap<>(); + body.put("error", "top level delete error"); + ApiClientApiException apiEx = new ApiClientApiException("delete failed", 403, body); + RuntimeException wrapper = new RuntimeException(apiEx); + + V1FlowDeleteTokenRequest batch = V1FlowDeleteTokenRequest.builder() + .vaultId("vault123") + .tokens(Collections.singletonList("t1")) + .build(); + List errors = Utils.handleBulkDeleteTokensBatchException(wrapper, batch, 0, 50); + + Assert.assertEquals(1, errors.size()); + Assert.assertEquals(Integer.valueOf(403), errors.get(0).getHttpCode()); + Assert.assertEquals("top level delete error", errors.get(0).getError()); + Assert.assertEquals("t1", errors.get(0).getToken()); + } + + @Test + public void testHandleBulkDeleteTokensBatchException_genericException() { + RuntimeException ex = new RuntimeException("boom"); + V1FlowDeleteTokenRequest batch = V1FlowDeleteTokenRequest.builder() + .vaultId("vault123") + .tokens(Collections.singletonList("t1")) + .build(); + + List errors = Utils.handleBulkDeleteTokensBatchException(ex, batch, 0, 50); + + Assert.assertEquals(1, errors.size()); + Assert.assertEquals(Integer.valueOf(500), errors.get(0).getHttpCode()); + Assert.assertEquals("t1", errors.get(0).getToken()); + } + + @Test + public void testHandleBulkDeleteTokensBatchException_errorFieldAsObjectUsesHelper() { + // Structured error envelope {"error": {message, httpCode}} → parsed per token via the helper. + Map errorObject = new HashMap<>(); + errorObject.put("message", "vault not found"); + errorObject.put("httpCode", 404); + Map body = new HashMap<>(); + body.put("error", errorObject); + ApiClientApiException apiEx = new ApiClientApiException("delete failed", 404, body); + RuntimeException wrapper = new RuntimeException(apiEx); + + V1FlowDeleteTokenRequest batch = V1FlowDeleteTokenRequest.builder() + .vaultId("vault123") + .tokens(Collections.singletonList("t1")) + .build(); + List errors = Utils.handleBulkDeleteTokensBatchException(wrapper, batch, 0, 50); + + Assert.assertEquals(1, errors.size()); + Assert.assertEquals(Integer.valueOf(404), errors.get(0).getHttpCode()); + Assert.assertEquals("vault not found", errors.get(0).getError()); + Assert.assertEquals("t1", errors.get(0).getToken()); + } + + @Test + public void testHandleBulkDeleteTokensBatchException_errorFieldNeitherMapNorStringUsesApiMessage() { + Map body = new HashMap<>(); + body.put("error", 500); + ApiClientApiException apiEx = new ApiClientApiException("delete failed", 500, body); + RuntimeException wrapper = new RuntimeException(apiEx); + + V1FlowDeleteTokenRequest batch = V1FlowDeleteTokenRequest.builder() + .vaultId("vault123") + .tokens(Collections.singletonList("t1")) + .build(); + List errors = Utils.handleBulkDeleteTokensBatchException(wrapper, batch, 0, 50); + + Assert.assertEquals(1, errors.size()); + Assert.assertEquals("delete failed", errors.get(0).getError()); + } + + @Test + public void testHandleBulkDeleteTokensBatchException_bodyWithNeitherTokensNorErrorKey() { + // A map body matching neither branch falls through to the batch-wide fallback. + Map body = new HashMap<>(); + body.put("unexpected", "shape"); + ApiClientApiException apiEx = new ApiClientApiException("delete failed", 503, body); + RuntimeException wrapper = new RuntimeException(apiEx); + + V1FlowDeleteTokenRequest batch = V1FlowDeleteTokenRequest.builder() + .vaultId("vault123") + .tokens(Arrays.asList("t1", "t2")) + .build(); + List errors = Utils.handleBulkDeleteTokensBatchException(wrapper, batch, 1, 50); + + Assert.assertEquals(2, errors.size()); + Assert.assertEquals(Integer.valueOf(503), errors.get(0).getHttpCode()); + Assert.assertEquals("delete failed", errors.get(0).getError()); + // startIndex = batchNumber * batchSize = 50 + Assert.assertEquals(50, errors.get(0).getIndex()); + Assert.assertEquals(51, errors.get(1).getIndex()); + Assert.assertEquals("t2", errors.get(1).getToken()); + } + + @Test + public void testHandleBulkDeleteTokensBatchException_tokensNotAListFallsBackToBatchWideError() { + Map body = new HashMap<>(); + body.put("tokens", "not-a-list"); + ApiClientApiException apiEx = new ApiClientApiException("delete failed", 400, body); + RuntimeException wrapper = new RuntimeException(apiEx); + + V1FlowDeleteTokenRequest batch = V1FlowDeleteTokenRequest.builder() + .vaultId("vault123") + .tokens(Collections.singletonList("t1")) + .build(); + List errors = Utils.handleBulkDeleteTokensBatchException(wrapper, batch, 0, 50); + + Assert.assertEquals(1, errors.size()); + Assert.assertEquals("delete failed", errors.get(0).getError()); + Assert.assertEquals("t1", errors.get(0).getToken()); + } + + @Test + public void testHandleBulkDeleteTokensBatchException_nonMapEntriesAreSkipped() { + Map body = new HashMap<>(); + body.put("tokens", Arrays.asList("not-a-map", null)); + ApiClientApiException apiEx = new ApiClientApiException("delete failed", 400, body); + RuntimeException wrapper = new RuntimeException(apiEx); + + V1FlowDeleteTokenRequest batch = V1FlowDeleteTokenRequest.builder() + .vaultId("vault123") + .tokens(Collections.singletonList("t1")) + .build(); + List errors = Utils.handleBulkDeleteTokensBatchException(wrapper, batch, 0, 50); + + // No entry parsed, so the batch-wide fallback fires instead. + Assert.assertEquals(1, errors.size()); + Assert.assertEquals("delete failed", errors.get(0).getError()); + } + + @Test + public void testHandleBulkDeleteTokensBatchException_recordEchoesValueAndReadsHttpCodeAndMessage() { + // createDeleteTokensErrorRecord: http_code key, "message" key, and an echoed "value" token. + Map tokenMap = new HashMap<>(); + tokenMap.put("http_code", 409); + tokenMap.put("message", "already deleted"); + tokenMap.put("value", "echoed-token"); + Map body = new HashMap<>(); + body.put("tokens", Collections.singletonList(tokenMap)); + ApiClientApiException apiEx = new ApiClientApiException("delete failed", 409, body); + RuntimeException wrapper = new RuntimeException(apiEx); + + V1FlowDeleteTokenRequest batch = V1FlowDeleteTokenRequest.builder() + .vaultId("vault123") + .tokens(Collections.singletonList("requested-token")) + .build(); + List errors = Utils.handleBulkDeleteTokensBatchException(wrapper, batch, 0, 50); + + Assert.assertEquals(1, errors.size()); + Assert.assertEquals(Integer.valueOf(409), errors.get(0).getHttpCode()); + Assert.assertEquals("already deleted", errors.get(0).getError()); + // the echoed "value" wins over the token from the request batch + Assert.assertEquals("echoed-token", errors.get(0).getToken()); + } + + @Test + public void testHandleBulkDeleteTokensBatchException_recordUsesStatusCodeAndUnknownError() { + // createDeleteTokensErrorRecord: statusCode key and the no-error/no-message fallback. + Map tokenMap = new HashMap<>(); + tokenMap.put("statusCode", 410); + Map body = new HashMap<>(); + body.put("tokens", Collections.singletonList(tokenMap)); + ApiClientApiException apiEx = new ApiClientApiException("delete failed", 410, body); + RuntimeException wrapper = new RuntimeException(apiEx); + + V1FlowDeleteTokenRequest batch = V1FlowDeleteTokenRequest.builder() + .vaultId("vault123") + .tokens(Collections.singletonList("t1")) + .build(); + List errors = Utils.handleBulkDeleteTokensBatchException(wrapper, batch, 0, 50); + + Assert.assertEquals(1, errors.size()); + Assert.assertEquals(Integer.valueOf(410), errors.get(0).getHttpCode()); + Assert.assertEquals("Unknown error", errors.get(0).getError()); + // no echoed value, so the requested token is reported + Assert.assertEquals("t1", errors.get(0).getToken()); + } + + // ── handleBulkTokenizeBatchException ─────────────────────────────────────── + + private static List tokenizeBatch(String value, String... groups) { + return Collections.singletonList(BulkTokenizeRequestRecord.builder() + .value(value).tokenGroupNames(Arrays.asList(groups)).build()); + } + + @Test + public void testHandleBulkTokenizeBatchException_apiExceptionFailsEveryTokenGroup() { + Map body = new HashMap<>(); + body.put("error", "invalid value"); + ApiClientApiException apiEx = new ApiClientApiException("tokenize failed", 400, body); + RuntimeException wrapper = new RuntimeException(apiEx); + + List errors = Utils.handleBulkTokenizeBatchException( + wrapper, tokenizeBatch("v1", "group1", "group2"), 0); + + Assert.assertEquals(1, errors.size()); + BulkTokenizeResponseRecord record = errors.get(0); + // index is derived from the batch position; the value is echoed from the request + Assert.assertEquals(0, record.getIndex()); + Assert.assertEquals("v1", record.getValue()); + // one failed token entry per requested group + Assert.assertEquals(2, record.getTokens().size()); + Assert.assertEquals("group1", record.getTokens().get(0).getTokenGroupName()); + Assert.assertEquals("invalid value", record.getTokens().get(0).getError()); + Assert.assertEquals(Integer.valueOf(400), record.getTokens().get(0).getHttpCode()); + Assert.assertEquals("group2", record.getTokens().get(1).getTokenGroupName()); + Assert.assertEquals("invalid value", record.getTokens().get(1).getError()); + } + + @Test + public void testHandleBulkTokenizeBatchException_genericException() { + RuntimeException ex = new RuntimeException("boom"); + + // this batch starts at index 3 in the caller's list + List errors = Utils.handleBulkTokenizeBatchException( + ex, tokenizeBatch("v1", "group1"), 3); + + Assert.assertEquals(1, errors.size()); + Assert.assertEquals(3, errors.get(0).getIndex()); + Assert.assertEquals(Integer.valueOf(500), errors.get(0).getTokens().get(0).getHttpCode()); + Assert.assertEquals("boom", errors.get(0).getTokens().get(0).getError()); + } + + @Test + public void testHandleBulkTokenizeBatchException_indexesRunConsecutivelyFromBatchStart() { + RuntimeException ex = new RuntimeException("boom"); + List batch = Arrays.asList( + BulkTokenizeRequestRecord.builder().value("v1").build(), + BulkTokenizeRequestRecord.builder().value("v2").build()); + + // this batch starts at index 20, so it covers 20 and 21 + List errors = Utils.handleBulkTokenizeBatchException(ex, batch, 20); + + Assert.assertEquals(2, errors.size()); + Assert.assertEquals(20, errors.get(0).getIndex()); + Assert.assertEquals("v1", errors.get(0).getValue()); + Assert.assertEquals(21, errors.get(1).getIndex()); + Assert.assertEquals("v2", errors.get(1).getValue()); + } + + @Test + public void testHandleBulkTokenizeBatchException_noTokenGroupsStillReportsOneEntry() { + RuntimeException ex = new RuntimeException("boom"); + List batch = Collections.singletonList( + BulkTokenizeRequestRecord.builder().value("v1").build()); + + List errors = Utils.handleBulkTokenizeBatchException(ex, batch, 0); + + Assert.assertEquals(1, errors.get(0).getTokens().size()); + Assert.assertNull(errors.get(0).getTokens().get(0).getTokenGroupName()); + Assert.assertEquals("boom", errors.get(0).getTokens().get(0).getError()); + } + + @Test + public void testHandleBulkTokenizeBatchException_errorBodyWithResponseArrayRebuildsRecords() { + // A 4xx whose body echoes the per-row "response" array is rebuilt via tokenizeRecordsFromErrorBody + // rather than summarized by the bare status code. + Map tokenRow = new HashMap<>(); + tokenRow.put("tokenGroupName", "group1"); + tokenRow.put("error", "BYOT token should contain one token group"); + tokenRow.put("httpCode", 400); + Map responseRow = new HashMap<>(); + responseRow.put("value", "v1"); + responseRow.put("tokens", Collections.singletonList(tokenRow)); + Map body = new HashMap<>(); + body.put("response", Collections.singletonList(responseRow)); + ApiClientApiException apiEx = new ApiClientApiException("tokenize failed", 400, body); + RuntimeException wrapper = new RuntimeException(apiEx); + + List errors = Utils.handleBulkTokenizeBatchException( + wrapper, tokenizeBatch("v1", "group1"), 0); + + Assert.assertEquals(1, errors.size()); + Assert.assertEquals("v1", errors.get(0).getValue()); + Assert.assertEquals(1, errors.get(0).getTokens().size()); + Assert.assertEquals("group1", errors.get(0).getTokens().get(0).getTokenGroupName()); + Assert.assertEquals("BYOT token should contain one token group", + errors.get(0).getTokens().get(0).getError()); + Assert.assertEquals(Integer.valueOf(400), errors.get(0).getTokens().get(0).getHttpCode()); + } + + @Test + public void testHandleBulkTokenizeBatchException_errorFieldAsObjectUsesStructuredMessage() { + // extractBatchErrorMessage reads {"error": {message}} when the body has no per-row response. + Map errorObject = new HashMap<>(); + errorObject.put("message", "vault not found"); + Map body = new HashMap<>(); + body.put("error", errorObject); + ApiClientApiException apiEx = new ApiClientApiException("tokenize failed", 404, body); + RuntimeException wrapper = new RuntimeException(apiEx); + + List errors = Utils.handleBulkTokenizeBatchException( + wrapper, tokenizeBatch("v1", "group1"), 0); + + Assert.assertEquals(1, errors.size()); + Assert.assertEquals("vault not found", errors.get(0).getTokens().get(0).getError()); + Assert.assertEquals(Integer.valueOf(404), errors.get(0).getTokens().get(0).getHttpCode()); + } + + @Test + public void testHandleBulkTokenizeBatchException_nonMapBodyUsesApiMessage() { + // Body is not a map, so extractBatchErrorMessage falls back to the exception's own message. + ApiClientApiException apiEx = new ApiClientApiException("tokenize failed", 500, "raw string body"); + RuntimeException wrapper = new RuntimeException(apiEx); + + List errors = Utils.handleBulkTokenizeBatchException( + wrapper, tokenizeBatch("v1", "group1"), 0); + + Assert.assertEquals(1, errors.size()); + Assert.assertEquals("tokenize failed", errors.get(0).getTokens().get(0).getError()); + Assert.assertEquals(Integer.valueOf(500), errors.get(0).getTokens().get(0).getHttpCode()); + } + + @Test + public void testHandleBulkTokenizeBatchException_nullBatchReturnsEmpty() { + RuntimeException ex = new RuntimeException("boom"); + + List errors = Utils.handleBulkTokenizeBatchException(ex, null, 0); + + Assert.assertTrue(errors.isEmpty()); + } + + // ── formatBulkInsertResponse ─────────────────────────────────────────────── + + @Test + public void testFormatBulkInsertResponse_success() { + Map tokens = new HashMap<>(); + tokens.put("name", "tok-abc"); + V1RecordResponseObject record = V1RecordResponseObject.builder() + .skyflowId("sky-id-1") + .tokens(tokens) + .build(); + V1InsertResponse response = V1InsertResponse.builder().records(Collections.singletonList(record)).build(); + + BulkInsertResponse result = Utils.formatBulkInsertResponse(response, 0, 50, new HashMap<>()); + + Assert.assertEquals(1, result.getRecords().size()); + BulkInsertResponseRecord inserted = result.getRecords().get(0); + Assert.assertEquals("sky-id-1", inserted.getSkyflowId()); + Assert.assertEquals(tokens, inserted.getFields()); + Assert.assertEquals(0, inserted.getIndex()); + Assert.assertEquals(200, inserted.getHttpCode()); + Assert.assertNull(inserted.getError()); + } + + @Test + public void testFormatBulkInsertResponse_indexOffsetByBatchNumber() { + V1RecordResponseObject record = V1RecordResponseObject.builder().skyflowId("sky-id-1").build(); + V1InsertResponse response = V1InsertResponse.builder().records(Collections.singletonList(record)).build(); + + BulkInsertResponse result = Utils.formatBulkInsertResponse(response, 2, 50, new HashMap<>()); + + Assert.assertEquals(100, result.getRecords().get(0).getIndex()); + } + + @Test + public void testFormatBulkInsertResponse_errorWithMissingHttpCodeDefaultsTo500() { + V1RecordResponseObject record = V1RecordResponseObject.builder() + .error("insert failed") + .build(); + V1InsertResponse response = V1InsertResponse.builder().records(Collections.singletonList(record)).build(); + + BulkInsertResponse result = Utils.formatBulkInsertResponse(response, 0, 50, new HashMap<>()); + + Assert.assertEquals(1, result.getRecords().size()); + Assert.assertEquals(500, result.getRecords().get(0).getHttpCode()); + Assert.assertEquals("insert failed", result.getRecords().get(0).getError()); + } + + @Test + public void testFormatBulkInsertResponse_nullResponseReturnsNull() { + Assert.assertNull(Utils.formatBulkInsertResponse(null, 0, 50, new HashMap<>())); + } + + // ── formatBulkDetokenizeResponse ─────────────────────────────────────────── + + @Test + public void testFormatBulkDetokenizeResponse_success() { + V1FlowDetokenizeResponseObject record = V1FlowDetokenizeResponseObject.builder() + .token("token1") + .value("secret-value") + .build(); + V1FlowDetokenizeResponse response = V1FlowDetokenizeResponse.builder() + .response(Collections.singletonList(record)) + .build(); + + BulkDetokenizeResponse result = Utils.formatBulkDetokenizeResponse(response, 0, 50, new HashMap<>()); + + Assert.assertEquals(1, result.getRecords().size()); + BulkDetokenizeResponseRecord detokenized = result.getRecords().get(0); + Assert.assertEquals("token1", detokenized.getToken()); + Assert.assertEquals(0, detokenized.getIndex()); + // Success records default to httpCode 200 and carry no error. + Assert.assertEquals(200, detokenized.getHttpCode()); + Assert.assertNull(detokenized.getError()); + // Per-batch responses leave the summary unset. + Assert.assertNull(result.getSummary()); + } + + @Test + public void testFormatBulkDetokenizeResponse_indexOffsetByBatchNumber() { + V1FlowDetokenizeResponseObject record = V1FlowDetokenizeResponseObject.builder() + .token("token1") + .build(); + V1FlowDetokenizeResponse response = V1FlowDetokenizeResponse.builder() + .response(Collections.singletonList(record)) + .build(); + + BulkDetokenizeResponse result = Utils.formatBulkDetokenizeResponse(response, 2, 50, new HashMap<>()); + + Assert.assertEquals(100, result.getRecords().get(0).getIndex()); + } + + @Test + public void testFormatBulkDetokenizeResponse_errorWithMissingHttpCodeDefaultsTo500() { + V1FlowDetokenizeResponseObject record = V1FlowDetokenizeResponseObject.builder() + .token("token1") + .error("token not found") + .build(); + V1FlowDetokenizeResponse response = V1FlowDetokenizeResponse.builder() + .response(Collections.singletonList(record)) + .build(); + + BulkDetokenizeResponse result = Utils.formatBulkDetokenizeResponse(response, 0, 50, new HashMap<>()); + + Assert.assertEquals(1, result.getRecords().size()); + Assert.assertEquals(500, result.getRecords().get(0).getHttpCode()); + Assert.assertEquals("token not found", result.getRecords().get(0).getError()); + } + + @Test + public void testFormatBulkDetokenizeResponse_emptyResponseReturnsNull() { + V1FlowDetokenizeResponse response = V1FlowDetokenizeResponse.builder().build(); + Assert.assertNull(Utils.formatBulkDetokenizeResponse(response, 0, 50, new HashMap<>())); + } + + // ── formatBulkDeleteTokensResponse ───────────────────────────────────────── + + private static V1FlowDeleteTokenRequest deleteBatchOf(String... tokens) { + return V1FlowDeleteTokenRequest.builder() + .vaultId("vault123") + .tokens(Arrays.asList(tokens)) + .build(); + } + + @Test + public void testFormatBulkDeleteTokensResponse_duplicateTokenRelaysEachRowVerbatim() { + // the same token sent twice: the API decides each position independently, and has been + // observed returning both 200,200 and 200,404 for the identical request. Whatever it says + // must reach the caller unchanged - no deduplication, no normalising one row against the other. + String token = "e5874be2-940a-4c74-9c08-dc6c1e8c6f9b"; + String message = "DeleteToken failed. Token " + token + " is invalid. Specify a valid token."; + V1FlowDeleteTokenResponse response = V1FlowDeleteTokenResponse.builder() + .tokens(Arrays.asList( + V1DeleteTokenResponseObject.builder().value(token).httpCode(200).build(), + V1DeleteTokenResponseObject.builder() + .value(token).error(message).httpCode(404).build())) + .build(); + + BulkDeleteTokensResponse result = Utils.formatBulkDeleteTokensResponse( + response, deleteBatchOf(token, token), 0, 50, new HashMap<>()); + BulkDeleteTokensResponse withPayload = new BulkDeleteTokensResponse( + result.getRecords(), Arrays.asList(token, token)); + + Assert.assertEquals(2, withPayload.getRecords().size()); + Assert.assertEquals(0, withPayload.getRecords().get(0).getIndex()); + Assert.assertEquals(Integer.valueOf(200), withPayload.getRecords().get(0).getHttpCode()); + Assert.assertNull(withPayload.getRecords().get(0).getError()); + Assert.assertEquals(1, withPayload.getRecords().get(1).getIndex()); + Assert.assertEquals(Integer.valueOf(404), withPayload.getRecords().get(1).getHttpCode()); + Assert.assertEquals(message, withPayload.getRecords().get(1).getError()); + // the summary follows the rows, so a duplicate that the API rejected is not counted deleted + Assert.assertEquals(2, withPayload.getSummary().getTotalTokens()); + Assert.assertEquals(1, withPayload.getSummary().getTotalDeleted()); + Assert.assertEquals(1, withPayload.getSummary().getTotalFailed()); + // 404 is not retryable, so nothing is offered for resubmission + Assert.assertTrue(withPayload.getTokensToRetry().isEmpty()); + } + + @Test + public void testFormatBulkDeleteTokensResponse_success() { + V1DeleteTokenResponseObject record = V1DeleteTokenResponseObject.builder() + .value("token1") + .build(); + V1FlowDeleteTokenResponse response = V1FlowDeleteTokenResponse.builder() + .tokens(Collections.singletonList(record)) + .build(); + + BulkDeleteTokensResponse result = Utils.formatBulkDeleteTokensResponse( + response, deleteBatchOf("token1"), 0, 50, new HashMap<>()); + + Assert.assertEquals(1, result.getRecords().size()); + Assert.assertEquals("token1", result.getRecords().get(0).getToken()); + Assert.assertEquals(Integer.valueOf(200), result.getRecords().get(0).getHttpCode()); + Assert.assertNull(result.getRecords().get(0).getError()); + Assert.assertEquals(0, result.getRecords().get(0).getIndex()); + } + + @Test + public void testFormatBulkDeleteTokensResponse_error() { + V1DeleteTokenResponseObject record = V1DeleteTokenResponseObject.builder() + .error("token not found") + .httpCode(404) + .build(); + V1FlowDeleteTokenResponse response = V1FlowDeleteTokenResponse.builder() + .tokens(Collections.singletonList(record)) + .build(); + + BulkDeleteTokensResponse result = Utils.formatBulkDeleteTokensResponse( + response, deleteBatchOf("token1"), 0, 50, new HashMap<>()); + + Assert.assertEquals(1, result.getRecords().size()); + Assert.assertEquals(Integer.valueOf(404), result.getRecords().get(0).getHttpCode()); + Assert.assertEquals("token not found", result.getRecords().get(0).getError()); + // API omitted the echoed value, so the token falls back to the one we sent + Assert.assertEquals("token1", result.getRecords().get(0).getToken()); + } + + @Test + public void testFormatBulkDeleteTokensResponse_errorTextWithoutHttpCodeTreatedAsSuccess() { + V1DeleteTokenResponseObject record = V1DeleteTokenResponseObject.builder() + .value("token1") + .error("transient warning") + .build(); + V1FlowDeleteTokenResponse response = V1FlowDeleteTokenResponse.builder() + .tokens(Collections.singletonList(record)) + .build(); + + BulkDeleteTokensResponse result = Utils.formatBulkDeleteTokensResponse( + response, deleteBatchOf("token1"), 0, 50, new HashMap<>()); + + Assert.assertEquals(1, result.getRecords().size()); + Assert.assertNull(result.getRecords().get(0).getError()); + Assert.assertEquals("token1", result.getRecords().get(0).getToken()); + } + + @Test + public void testFormatBulkDeleteTokensResponse_indexesOffsetByBatch() { + V1FlowDeleteTokenResponse response = V1FlowDeleteTokenResponse.builder() + .tokens(Arrays.asList( + V1DeleteTokenResponseObject.builder().value("token3").build(), + V1DeleteTokenResponseObject.builder().value("token4").build())) + .build(); + + // batch 1 with batchSize 2 => indexes continue at 2 + BulkDeleteTokensResponse result = Utils.formatBulkDeleteTokensResponse( + response, deleteBatchOf("token3", "token4"), 1, 2, new HashMap<>()); + + Assert.assertEquals(2, result.getRecords().get(0).getIndex()); + Assert.assertEquals(3, result.getRecords().get(1).getIndex()); + } + + @Test + public void testFormatBulkDeleteTokensResponse_emptyResponseReturnsNull() { + V1FlowDeleteTokenResponse response = V1FlowDeleteTokenResponse.builder().build(); + Assert.assertNull(Utils.formatBulkDeleteTokensResponse( + response, deleteBatchOf("token1"), 0, 50, new HashMap<>())); + } + + // ── formatBulkTokenizeResponse ───────────────────────────────────────────── + + private static V1FlowTokenizeResponse tokenizeWire(V1FlowTokenizeResponseObject... records) { + return V1FlowTokenizeResponse.builder().response(java.util.Arrays.asList(records)).build(); + } + + @Test + public void testFormatBulkTokenizeResponse_success() { + V1FlowTokenizeResponse response = tokenizeWire(V1FlowTokenizeResponseObject.builder() + .value("value1") + .tokens(Collections.singletonList(FlowTokenizeResponseObjectToken.builder() + .tokenGroupName("group1").token("tok-abc").build())) + .build()); + + BulkTokenizeResponse result = Utils.formatBulkTokenizeResponse( + response, tokenizeBatch("value1", "group1"), 0, new HashMap<>()); + + Assert.assertEquals(1, result.getRecords().size()); + BulkTokenizeResponseRecord record = result.getRecords().get(0); + Assert.assertEquals(0, record.getIndex()); + Assert.assertEquals("value1", record.getValue()); + Assert.assertEquals("tok-abc", record.getTokens().get(0).getToken()); + Assert.assertNull(record.getTokens().get(0).getError()); + } + + @Test + public void testFormatBulkTokenizeResponse_tokenError() { + V1FlowTokenizeResponse response = tokenizeWire(V1FlowTokenizeResponseObject.builder() + .value("value1") + .tokens(Collections.singletonList(FlowTokenizeResponseObjectToken.builder() + .tokenGroupName("group1").error("invalid value").httpCode(400).build())) + .build()); + + BulkTokenizeResponse result = Utils.formatBulkTokenizeResponse( + response, tokenizeBatch("value1", "group1"), 0, new HashMap<>()); + + Assert.assertEquals(1, result.getRecords().size()); + TokenizeResponseToken token = result.getRecords().get(0).getTokens().get(0); + Assert.assertEquals(Integer.valueOf(400), token.getHttpCode()); + Assert.assertEquals("invalid value", token.getError()); + } + + @Test + public void testFormatBulkTokenizeResponse_derivesIndexFromBatchPosition() { + V1FlowTokenizeResponse response = tokenizeWire(V1FlowTokenizeResponseObject.builder() + .value("value1") + .tokens(Collections.singletonList(FlowTokenizeResponseObjectToken.builder() + .tokenGroupName("group1").token("tok-abc").build())) + .build()); + + // this batch starts at index 40 in the caller's list + BulkTokenizeResponse result = Utils.formatBulkTokenizeResponse( + response, tokenizeBatch("value1", "group1"), 40, new HashMap<>()); + + Assert.assertEquals(40, result.getRecords().get(0).getIndex()); + } + + @Test + public void testFormatBulkTokenizeResponse_emptyResponseReturnsNull() { + Assert.assertNull(Utils.formatBulkTokenizeResponse( + V1FlowTokenizeResponse.builder().build(), + tokenizeBatch("value1", "group1"), 0, new HashMap<>())); + } + + // Tests for getQueryRequestBody / buildQueryResponse / getGetRequestBody / buildGetResponse + // were removed: get and query Utils helpers no longer exist (bulk-only module). + +} diff --git a/flowvault/src/test/java/com/skyflow/utils/validations/ValidationsTests.java b/flowvault/src/test/java/com/skyflow/utils/validations/ValidationsTests.java new file mode 100644 index 00000000..44a58686 --- /dev/null +++ b/flowvault/src/test/java/com/skyflow/utils/validations/ValidationsTests.java @@ -0,0 +1,1530 @@ +package com.skyflow.utils.validations; + +import com.skyflow.config.Credentials; +import com.skyflow.config.VaultConfig; +import com.skyflow.enums.Env; +import com.skyflow.errors.ErrorMessage; +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.data.BulkDeleteTokensRequest; +import com.skyflow.vault.data.BulkInsertRequestRecord; +import com.skyflow.vault.data.BulkInsertRequest; +import com.skyflow.vault.data.BulkDetokenizeRequest; +import com.skyflow.vault.data.BulkTokenizeRequestRecord; +import com.skyflow.vault.data.BulkTokenizeRequest; +import com.skyflow.vault.data.DetokenizeRequest; +import com.skyflow.vault.data.InsertRequestRecord; +import com.skyflow.vault.data.InsertRequest; +import com.skyflow.vault.data.TokenGroupRedactions; +import com.skyflow.vault.data.TokenizeRequestRecord; +import com.skyflow.vault.data.TokenizeRequest; +import com.skyflow.vault.data.UpsertOptions; +import org.junit.Assert; +import org.junit.Test; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ValidationsTests { + private static final String EXCEPTION_NOT_THROWN = "Should have thrown an exception"; + private static final String INVALID_EXCEPTION_THROWN = "Should not have thrown any exception"; + + // Tests for validateTokenizeRequest / validateDeleteTokensRequest were removed: + // those unary validators no longer exist (bulk-only module). + + // ── validateCredentials ─────────────────────────────────────────────────── + + @Test + public void testValidateCredentials_validToken() { + Credentials credentials = new Credentials(); + credentials.setToken("some-token"); + try { + Validations.validateCredentials(credentials); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testValidateCredentials_validApiKey() { + Credentials credentials = new Credentials(); + credentials.setApiKey("sky-ab123-abcd1234cdef1234abcd4321cdef4321"); + try { + Validations.validateCredentials(credentials); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testValidateCredentials_invalidApiKeyFormat() { + Credentials credentials = new Credentials(); + credentials.setApiKey("not-a-valid-api-key"); + try { + Validations.validateCredentials(credentials); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateCredentials_noAuthMeansPassed() { + Credentials credentials = new Credentials(); + try { + Validations.validateCredentials(credentials); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateCredentials_multipleAuthMeansPassed() { + Credentials credentials = new Credentials(); + credentials.setToken("some-token"); + credentials.setApiKey("sky-ab123-abcd1234cdef1234abcd4321cdef4321"); + try { + Validations.validateCredentials(credentials); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateCredentials_emptyRoles() { + Credentials credentials = new Credentials(); + credentials.setToken("some-token"); + credentials.setRoles(new ArrayList<>()); + try { + Validations.validateCredentials(credentials); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateCredentials_blankEntryInRoles() { + Credentials credentials = new Credentials(); + credentials.setToken("some-token"); + ArrayList roles = new ArrayList<>(); + roles.add("validRole"); + roles.add(" "); + credentials.setRoles(roles); + try { + Validations.validateCredentials(credentials); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateCredentials_emptyPath() { + Credentials credentials = new Credentials(); + credentials.setPath(""); + try { + Validations.validateCredentials(credentials); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateCredentials_emptyCredentialsString() { + Credentials credentials = new Credentials(); + credentials.setCredentialsString(""); + try { + Validations.validateCredentials(credentials); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateCredentials_emptyToken() { + Credentials credentials = new Credentials(); + credentials.setToken(""); + try { + Validations.validateCredentials(credentials); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateCredentials_emptyStringContext() { + Credentials credentials = new Credentials(); + credentials.setToken("some-token"); + credentials.setContext(""); + try { + Validations.validateCredentials(credentials); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateCredentials_emptyMapContext() { + Credentials credentials = new Credentials(); + credentials.setToken("some-token"); + credentials.setContext(new HashMap<>()); + try { + Validations.validateCredentials(credentials); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateCredentials_invalidMapKeyContext() { + Credentials credentials = new Credentials(); + credentials.setToken("some-token"); + Map context = new HashMap<>(); + context.put("invalid-key!", "value"); + credentials.setContext(context); + try { + Validations.validateCredentials(credentials); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateCredentials_invalidContextType() throws Exception { + Credentials credentials = new Credentials(); + credentials.setToken("some-token"); + Field contextField = credentials.getClass().getSuperclass().getDeclaredField("context"); + contextField.setAccessible(true); + contextField.set(credentials, Integer.valueOf(5)); + try { + Validations.validateCredentials(credentials); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + // ── validateVaultConfiguration ──────────────────────────────────────────── + + @Test + public void testValidateVaultConfiguration_nullVaultId() { + VaultConfig config = new VaultConfig(); + config.setClusterId("cluster1"); + try { + Validations.validateVaultConfiguration(config); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateVaultConfiguration_emptyVaultId() { + VaultConfig config = new VaultConfig(); + config.setVaultId(" "); + config.setClusterId("cluster1"); + try { + Validations.validateVaultConfiguration(config); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateVaultConfiguration_neitherVaultUrlNorClusterId() { + VaultConfig config = new VaultConfig(); + config.setVaultId("vault123"); + try { + Validations.validateVaultConfiguration(config); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateVaultConfiguration_emptyClusterId() { + VaultConfig config = new VaultConfig(); + config.setVaultId("vault123"); + config.setClusterId(" "); + try { + Validations.validateVaultConfiguration(config); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateVaultConfiguration_emptyVaultUrl() { + VaultConfig config = new VaultConfig(); + config.setVaultId("vault123"); + config.setClusterId("cluster1"); + config.setVaultUrl(" "); + try { + Validations.validateVaultConfiguration(config); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateVaultConfiguration_invalidVaultUrlFormat() { + VaultConfig config = new VaultConfig(); + config.setVaultId("vault123"); + config.setVaultUrl("http://not-https.example.com"); + try { + Validations.validateVaultConfiguration(config); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateVaultConfiguration_validWithClusterId() { + VaultConfig config = new VaultConfig(); + config.setVaultId("vault123"); + config.setClusterId("cluster1"); + config.setEnv(Env.DEV); + try { + Validations.validateVaultConfiguration(config); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testValidateVaultConfiguration_validWithVaultUrl() { + VaultConfig config = new VaultConfig(); + config.setVaultId("vault123"); + config.setVaultUrl("https://myvault.example.com"); + try { + Validations.validateVaultConfiguration(config); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testValidateVaultConfiguration_delegatesToCredentialValidation() { + VaultConfig config = new VaultConfig(); + config.setVaultId("vault123"); + config.setClusterId("cluster1"); + Credentials credentials = new Credentials(); + credentials.setApiKey("not-a-valid-api-key"); + config.setCredentials(credentials); + try { + Validations.validateVaultConfiguration(config); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + // ── validateInsertRequest ───────────────────────────────────────────────── + + @Test + public void testValidateInsertRequest_nullRequest() { + try { + Validations.validateInsertRequest(null); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateInsertRequest_nullRecords() { + InsertRequest request = InsertRequest.builder().records(null).build(); + try { + Validations.validateInsertRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateInsertRequest_emptyRecords() { + InsertRequest request = InsertRequest.builder().records(new ArrayList<>()).build(); + try { + Validations.validateInsertRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateInsertRequest_nullRecordInList() { + ArrayList records = new ArrayList<>(); + records.add(null); + InsertRequest request = InsertRequest.builder().records(records).build(); + try { + Validations.validateInsertRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + // NOTE: a table name may now be supplied at either the record level or the request level. + // EmptyTable is thrown only when it is missing from both. Specifying it at both levels is + // currently accepted (no conflict error) — that decision is deliberately deferred. Likewise + // the old "upsert present at record/request level requires table at the other level" checks are + // gone, since upsert is no longer coupled to where the table is specified — it's just an + // UpsertOptions object validated the same way at either level (see the upsert tests below). + + @Test + public void testValidateInsertRequest_missingTableNameInRecordThrowsEmptyTable() { + Map data = new HashMap<>(); + data.put("name", "john"); + ArrayList records = new ArrayList<>(); + records.add(InsertRequestRecord.builder().data(data).build()); + InsertRequest request = InsertRequest.builder().records(records).build(); + try { + Validations.validateInsertRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateInsertRequest_emptyTableNameInRecordThrowsEmptyTable() { + Map data = new HashMap<>(); + data.put("name", "john"); + ArrayList records = new ArrayList<>(); + records.add(InsertRequestRecord.builder().tableName(" ").data(data).build()); + InsertRequest request = InsertRequest.builder().records(records).build(); + try { + Validations.validateInsertRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateInsertRequest_requestLevelTableNameSatisfiesRecordsWithoutOne() { + Map data = new HashMap<>(); + data.put("name", "john"); + ArrayList records = new ArrayList<>(); + records.add(InsertRequestRecord.builder().data(data).build()); + InsertRequest request = InsertRequest.builder().tableName("table1").records(records).build(); + try { + Validations.validateInsertRequest(request); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testValidateInsertRequest_emptyUpsertAtRequestLevel() { + Map data = new HashMap<>(); + data.put("name", "john"); + ArrayList records = new ArrayList<>(); + records.add(InsertRequestRecord.builder().tableName("table1").data(data).build()); + UpsertOptions upsert = UpsertOptions.builder().uniqueColumns(new ArrayList<>()).build(); + InsertRequest request = InsertRequest.builder() + .records(records) + .upsert(upsert) + .build(); + try { + Validations.validateInsertRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateInsertRequest_emptyUpsertAtRecordLevel() { + Map data = new HashMap<>(); + data.put("name", "john"); + UpsertOptions upsert = UpsertOptions.builder().uniqueColumns(new ArrayList<>()).build(); + ArrayList records = new ArrayList<>(); + records.add(InsertRequestRecord.builder().tableName("table1").data(data).upsert(upsert).build()); + InsertRequest request = InsertRequest.builder().records(records).build(); + try { + Validations.validateInsertRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateInsertRequest_emptyOrNullKeyInData() { + Map data = new HashMap<>(); + data.put("", "john"); + ArrayList records = new ArrayList<>(); + records.add(InsertRequestRecord.builder().tableName("table1").data(data).build()); + InsertRequest request = InsertRequest.builder().records(records).build(); + try { + Validations.validateInsertRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateInsertRequest_emptyOrNullValueInData() { + Map data = new HashMap<>(); + data.put("name", ""); + ArrayList records = new ArrayList<>(); + records.add(InsertRequestRecord.builder().tableName("table1").data(data).build()); + InsertRequest request = InsertRequest.builder().records(records).build(); + try { + Validations.validateInsertRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateInsertRequest_validRequest() { + Map data = new HashMap<>(); + data.put("name", "john"); + ArrayList records = new ArrayList<>(); + records.add(InsertRequestRecord.builder().tableName("table1").data(data).build()); + InsertRequest request = InsertRequest.builder().records(records).build(); + try { + Validations.validateInsertRequest(request); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testValidateInsertRequest_validRequestWithUpsertAtRequestLevel() { + Map data = new HashMap<>(); + data.put("name", "john"); + ArrayList records = new ArrayList<>(); + records.add(InsertRequestRecord.builder().data(data).build()); + UpsertOptions upsert = UpsertOptions.builder() + .uniqueColumns(Collections.singletonList("email")) + .updateType("UPDATE") + .build(); + InsertRequest request = InsertRequest.builder() + .tableName("table1") + .records(records) + .upsert(upsert) + .build(); + try { + Validations.validateInsertRequest(request); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testValidateInsertRequest_validRequestWithUpsertAtRecordLevel() { + Map data = new HashMap<>(); + data.put("name", "john"); + UpsertOptions upsert = UpsertOptions.builder() + .uniqueColumns(Collections.singletonList("email")) + .updateType("REPLACE") + .build(); + ArrayList records = new ArrayList<>(); + records.add(InsertRequestRecord.builder().tableName("table1").data(data).upsert(upsert).build()); + InsertRequest request = InsertRequest.builder().records(records).build(); + try { + Validations.validateInsertRequest(request); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testValidateInsertRequest_validRequestWithTokens() { + Map tokens = new HashMap<>(); + tokens.put("name", "tok-abc"); + ArrayList records = new ArrayList<>(); + records.add(InsertRequestRecord.builder().tableName("table1").tokens(tokens).build()); + InsertRequest request = InsertRequest.builder().records(records).build(); + try { + Validations.validateInsertRequest(request); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + // ── validateDetokenizeRequest ───────────────────────────────────────────── + + @Test + public void testValidateDetokenizeRequest_nullRequest() { + try { + Validations.validateDetokenizeRequest(null); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateDetokenizeRequest_nullTokens() { + DetokenizeRequest request = DetokenizeRequest.builder().tokens(null).build(); + try { + Validations.validateDetokenizeRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateDetokenizeRequest_emptyTokens() { + DetokenizeRequest request = DetokenizeRequest.builder().tokens(new ArrayList<>()).build(); + try { + Validations.validateDetokenizeRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateDetokenizeRequest_nullTokenInList() { + List tokens = new ArrayList<>(); + tokens.add(null); + DetokenizeRequest request = DetokenizeRequest.builder().tokens(tokens).build(); + try { + Validations.validateDetokenizeRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateDetokenizeRequest_blankTokenInList() { + DetokenizeRequest request = DetokenizeRequest.builder() + .tokens(Collections.singletonList(" ")) + .build(); + try { + Validations.validateDetokenizeRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateDetokenizeRequest_nullTokenGroupRedactionInList() { + List groupRedactions = new ArrayList<>(); + groupRedactions.add(null); + DetokenizeRequest request = DetokenizeRequest.builder() + .tokens(Collections.singletonList("token1")) + .tokenGroupRedactions(groupRedactions) + .build(); + try { + Validations.validateDetokenizeRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateDetokenizeRequest_blankTokenGroupNameInRedaction() { + List groupRedactions = Collections.singletonList( + TokenGroupRedactions.builder().tokenGroupName(" ").redaction("MASKED").build()); + DetokenizeRequest request = DetokenizeRequest.builder() + .tokens(Collections.singletonList("token1")) + .tokenGroupRedactions(groupRedactions) + .build(); + try { + Validations.validateDetokenizeRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateDetokenizeRequest_blankRedactionInGroup() { + List groupRedactions = Collections.singletonList( + TokenGroupRedactions.builder().tokenGroupName("group1").redaction(" ").build()); + DetokenizeRequest request = DetokenizeRequest.builder() + .tokens(Collections.singletonList("token1")) + .tokenGroupRedactions(groupRedactions) + .build(); + try { + Validations.validateDetokenizeRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateDetokenizeRequest_validRequest() { + DetokenizeRequest request = DetokenizeRequest.builder() + .tokens(Collections.singletonList("token1")) + .build(); + try { + Validations.validateDetokenizeRequest(request); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testValidateDetokenizeRequest_validRequestWithTokenGroupRedactions() { + List groupRedactions = Collections.singletonList( + TokenGroupRedactions.builder().tokenGroupName("group1").redaction("MASKED").build()); + DetokenizeRequest request = DetokenizeRequest.builder() + .tokens(Collections.singletonList("token1")) + .tokenGroupRedactions(groupRedactions) + .build(); + try { + Validations.validateDetokenizeRequest(request); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + // ── validateBulkInsertRequest ────────────────────────────────────────────── + + @Test + public void testValidateBulkInsertRequest_nullRequest() { + try { + Validations.validateBulkInsertRequest(null); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateBulkInsertRequest_nullRecords() { + BulkInsertRequest request = BulkInsertRequest.builder().records(null).build(); + try { + Validations.validateBulkInsertRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateBulkInsertRequest_emptyRecords() { + BulkInsertRequest request = BulkInsertRequest.builder().records(new ArrayList<>()).build(); + try { + Validations.validateBulkInsertRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateBulkInsertRequest_nullRecordInList() { + ArrayList records = new ArrayList<>(); + records.add(null); + BulkInsertRequest request = BulkInsertRequest.builder().records(records).build(); + try { + Validations.validateBulkInsertRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + // Removed: testValidateBulkInsertRequest_tableSpecifiedAtBothPlaces — bulk insert now + // delegates to validateInsertRequest, which deliberately accepts a table name at both + // the request and record level (TableSpecifiedInRequestAndRecordObject is no longer thrown). + + // Removed: testValidateBulkInsertRequest_upsertAtRecordLevelWhenTableAtRequestLevel and + // testValidateBulkInsertRequest_upsertAtRequestLevelWhenNoTableAtRequestLevel — upsert is + // now an UpsertOptions object accepted at either level, so UpsertTableRequestAtRecordLevel / + // UpsertTableRequestAtRequestLevel are no longer thrown for insert. + + @Test + public void testValidateBulkInsertRequest_tableNotSpecifiedAnywhereThrowsEmptyTable() { + Map data = new HashMap<>(); + data.put("name", "john"); + ArrayList records = new ArrayList<>(); + records.add(BulkInsertRequestRecord.builder().data(data).build()); + BulkInsertRequest request = BulkInsertRequest.builder().records(records).build(); + try { + Validations.validateBulkInsertRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorMessage.TableNotSpecifiedInRequestAndRecordObject.getMessage(), e.getMessage()); + } + } + + @Test + public void testValidateBulkInsertRequest_requestLevelTableNameSatisfiesRecordsWithoutOne() { + Map data = new HashMap<>(); + data.put("name", "john"); + ArrayList records = new ArrayList<>(); + records.add(BulkInsertRequestRecord.builder().data(data).build()); + BulkInsertRequest request = BulkInsertRequest.builder().tableName("table1").records(records).build(); + try { + Validations.validateBulkInsertRequest(request); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testValidateBulkInsertRequest_tableNameAtBothLevelsThrows() { + // Table name must live at exactly one level, never both. + Map data = new HashMap<>(); + data.put("name", "john"); + ArrayList records = new ArrayList<>(); + records.add(BulkInsertRequestRecord.builder().tableName("table1").data(data).build()); + BulkInsertRequest request = BulkInsertRequest.builder().tableName("table1").records(records).build(); + try { + Validations.validateBulkInsertRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorMessage.TableSpecifiedInRequestAndRecordObject.getMessage(), e.getMessage()); + } + } + + @Test + public void testValidateBulkInsertRequest_emptyUpsertAtRecordLevel() { + Map data = new HashMap<>(); + data.put("name", "john"); + ArrayList records = new ArrayList<>(); + records.add(BulkInsertRequestRecord.builder() + .tableName("table1") + .data(data) + .upsert(UpsertOptions.builder().updateType("UPDATE").build()) + .build()); + BulkInsertRequest request = BulkInsertRequest.builder().records(records).build(); + try { + Validations.validateBulkInsertRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorMessage.EmptyUpsertValues.getMessage(), e.getMessage()); + } + } + + @Test + public void testValidateBulkInsertRequest_emptyUpsertAtRequestLevel() { + Map data = new HashMap<>(); + data.put("name", "john"); + ArrayList records = new ArrayList<>(); + records.add(BulkInsertRequestRecord.builder().tableName("table1").data(data).build()); + BulkInsertRequest request = BulkInsertRequest.builder() + .records(records) + .upsert(UpsertOptions.builder().uniqueColumns(new ArrayList<>()).build()) + .build(); + try { + Validations.validateBulkInsertRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorMessage.EmptyUpsertValues.getMessage(), e.getMessage()); + } + } + + @Test + public void testValidateBulkInsertRequest_emptyKeyInData() { + Map data = new HashMap<>(); + data.put("", "john"); + ArrayList records = new ArrayList<>(); + records.add(BulkInsertRequestRecord.builder().tableName("table1").data(data).build()); + BulkInsertRequest request = BulkInsertRequest.builder().records(records).build(); + try { + Validations.validateBulkInsertRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateBulkInsertRequest_emptyValueInData() { + Map data = new HashMap<>(); + data.put("name", ""); + ArrayList records = new ArrayList<>(); + records.add(BulkInsertRequestRecord.builder().tableName("table1").data(data).build()); + BulkInsertRequest request = BulkInsertRequest.builder().records(records).build(); + try { + Validations.validateBulkInsertRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateBulkInsertRequest_validRequest() { + Map data = new HashMap<>(); + data.put("name", "john"); + ArrayList records = new ArrayList<>(); + records.add(BulkInsertRequestRecord.builder().tableName("table1").data(data).build()); + BulkInsertRequest request = BulkInsertRequest.builder().records(records).build(); + try { + Validations.validateBulkInsertRequest(request); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testValidateBulkInsertRequest_validRequestWithUpsertAndTokens() { + Map data = new HashMap<>(); + data.put("name", "john"); + Map tokens = new HashMap<>(); + tokens.put("name", "tok-abc"); + ArrayList records = new ArrayList<>(); + records.add(BulkInsertRequestRecord.builder() + .tableName("table1") + .data(data) + .tokens(tokens) + .upsert(UpsertOptions.builder() + .updateType("REPLACE") + .uniqueColumns(Collections.singletonList("email")) + .build()) + .build()); + BulkInsertRequest request = BulkInsertRequest.builder().records(records).build(); + try { + Validations.validateBulkInsertRequest(request); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testValidateBulkInsertRequest_over10000RecordsThrows() { + // Constants.MAX_BULK_DATA_SIZE is a hard ceiling; batching splits the payload but + // does not lift it. + Map data = new HashMap<>(); + data.put("name", "john"); + ArrayList records = new ArrayList<>(); + for (int i = 0; i < 10001; i++) { + records.add(BulkInsertRequestRecord.builder().tableName("table1").data(data).build()); + } + BulkInsertRequest request = BulkInsertRequest.builder().records(records).build(); + try { + Validations.validateBulkInsertRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorMessage.RecordSizeExceedError.getMessage(), e.getMessage()); + } + } + + @Test + public void testValidateBulkInsertRequest_exactly10000RecordsIsValid() { + Map data = new HashMap<>(); + data.put("name", "john"); + ArrayList records = new ArrayList<>(); + for (int i = 0; i < 10000; i++) { + records.add(BulkInsertRequestRecord.builder().tableName("table1").data(data).build()); + } + BulkInsertRequest request = BulkInsertRequest.builder().records(records).build(); + try { + Validations.validateBulkInsertRequest(request); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testValidateBulkDetokenizeRequest_over10000TokensThrows() { + List tokens = new ArrayList<>(); + for (int i = 0; i < 10001; i++) { + tokens.add("token-" + i); + } + BulkDetokenizeRequest request = BulkDetokenizeRequest.builder().tokens(tokens).build(); + try { + Validations.validateBulkDetokenizeRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorMessage.TokensSizeExceedError.getMessage(), e.getMessage()); + } + } + + @Test + public void testValidateBulkDeleteTokensRequest_over10000TokensThrows() { + List tokens = new ArrayList<>(); + for (int i = 0; i < 10001; i++) { + tokens.add("token-" + i); + } + BulkDeleteTokensRequest request = BulkDeleteTokensRequest.builder().tokens(tokens).build(); + try { + Validations.validateBulkDeleteTokensRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorMessage.DeleteTokensSizeExceedError.getMessage(), e.getMessage()); + } + } + + @Test + public void testValidateBulkTokenizeRequest_over10000RecordsThrows() { + ArrayList records = new ArrayList<>(); + for (int i = 0; i < 10001; i++) { + records.add(BulkTokenizeRequestRecord.builder() + .value("value-" + i) + .tokenGroupNames(Collections.singletonList("group")) + .build()); + } + BulkTokenizeRequest request = BulkTokenizeRequest.builder().records(records).build(); + try { + Validations.validateBulkTokenizeRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorMessage.TokenizeDataSizeExceedError.getMessage(), e.getMessage()); + } + } + + @Test + public void testValidateInsertRequest_unrecognizedUpdateTypeThrows() { + // Previously this was silently dropped during mapping; it is now rejected up front. + Map data = new HashMap<>(); + data.put("name", "john"); + ArrayList records = new ArrayList<>(); + records.add(InsertRequestRecord.builder().data(data).build()); + InsertRequest request = InsertRequest.builder() + .tableName("table1") + .records(records) + .upsert(UpsertOptions.builder() + .uniqueColumns(Collections.singletonList("email")) + .updateType("MERGE") + .build()) + .build(); + try { + Validations.validateInsertRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorMessage.InvalidUpsertUpdateType.getMessage(), e.getMessage()); + } + } + + @Test + public void testValidateInsertRequest_updateTypeWithStrayWhitespaceThrows() { + Map data = new HashMap<>(); + data.put("name", "john"); + ArrayList records = new ArrayList<>(); + records.add(InsertRequestRecord.builder().data(data).build()); + InsertRequest request = InsertRequest.builder() + .tableName("table1") + .records(records) + .upsert(UpsertOptions.builder() + .uniqueColumns(Collections.singletonList("email")) + .updateType("update ") + .build()) + .build(); + try { + Validations.validateInsertRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorMessage.InvalidUpsertUpdateType.getMessage(), e.getMessage()); + } + } + + @Test + public void testValidateInsertRequest_nullUpdateTypeIsValid() { + // updateType is optional; only a non-null unrecognized value is rejected. + Map data = new HashMap<>(); + data.put("name", "john"); + ArrayList records = new ArrayList<>(); + records.add(InsertRequestRecord.builder().data(data).build()); + InsertRequest request = InsertRequest.builder() + .tableName("table1") + .records(records) + .upsert(UpsertOptions.builder().uniqueColumns(Collections.singletonList("email")).build()) + .build(); + try { + Validations.validateInsertRequest(request); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testValidateBulkInsertRequest_plainInsertRequestRecordRejected() { + // `records` is inherited as List, so a plain unary record is + // accepted by the compiler. Bulk must reject it, otherwise getRecordsToRetry() would + // hit a ClassCastException later. + Map data = new HashMap<>(); + data.put("name", "john"); + ArrayList records = new ArrayList<>(); + records.add(InsertRequestRecord.builder().tableName("table1").data(data).build()); + BulkInsertRequest request = BulkInsertRequest.builder().records(records).build(); + try { + Validations.validateBulkInsertRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorMessage.InvalidRecord.getMessage(), e.getMessage()); + } + } + + // ── validateBulkInsertRequest: table-name presence rule ──────────────────── + // + // The rule is: a table name must be present at the request level OR on EVERY record. + // "Present" means non-null and non-blank at both levels (Validations trims before testing, + // and Utils.hasText applies the same test when mapping to the wire), so a blank record-level + // name is treated as absent and satisfied by the request-level one. + + @Test + public void testValidateBulkInsertRequest_requestLevelTableNameSatisfiesBlankRecordTableNames() { + Map data = new HashMap<>(); + data.put("name", "john"); + ArrayList records = new ArrayList<>(); + records.add(BulkInsertRequestRecord.builder().tableName(" ").data(data).build()); + records.add(BulkInsertRequestRecord.builder().tableName("").data(data).build()); + records.add(BulkInsertRequestRecord.builder().data(data).build()); + BulkInsertRequest request = BulkInsertRequest.builder().tableName("cards").records(records).build(); + try { + Validations.validateBulkInsertRequest(request); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testValidateBulkInsertRequest_noRequestLevelTableNameButEveryRecordHasOneIsValid() { + Map data = new HashMap<>(); + data.put("name", "john"); + ArrayList records = new ArrayList<>(); + records.add(BulkInsertRequestRecord.builder().tableName("cards").data(data).build()); + records.add(BulkInsertRequestRecord.builder().tableName("accounts").data(data).build()); + records.add(BulkInsertRequestRecord.builder().tableName("people").data(data).build()); + BulkInsertRequest request = BulkInsertRequest.builder().records(records).build(); + try { + Validations.validateBulkInsertRequest(request); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testValidateBulkInsertRequest_noRequestLevelTableNameAndOneRecordMissingThrowsEmptyTable() { + Map data = new HashMap<>(); + data.put("name", "john"); + ArrayList records = new ArrayList<>(); + records.add(BulkInsertRequestRecord.builder().tableName("cards").data(data).build()); + records.add(BulkInsertRequestRecord.builder().data(data).build()); + records.add(BulkInsertRequestRecord.builder().tableName("people").data(data).build()); + BulkInsertRequest request = BulkInsertRequest.builder().records(records).build(); + try { + Validations.validateBulkInsertRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorMessage.TableNotSpecifiedInRequestAndRecordObject.getMessage(), e.getMessage()); + } + } + + @Test + public void testValidateBulkInsertRequest_noRequestLevelTableNameAndOneRecordBlankThrowsEmptyTable() { + Map data = new HashMap<>(); + data.put("name", "john"); + ArrayList records = new ArrayList<>(); + records.add(BulkInsertRequestRecord.builder().tableName("cards").data(data).build()); + records.add(BulkInsertRequestRecord.builder().tableName(" ").data(data).build()); + BulkInsertRequest request = BulkInsertRequest.builder().records(records).build(); + try { + Validations.validateBulkInsertRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorMessage.TableNotSpecifiedInRequestAndRecordObject.getMessage(), e.getMessage()); + } + } + + @Test + public void testValidateBulkInsertRequest_blankRequestLevelTableNameDoesNotSatisfyRecords() { + // A blank request-level table name is treated as absent too, so records must supply one. + Map data = new HashMap<>(); + data.put("name", "john"); + ArrayList records = new ArrayList<>(); + records.add(BulkInsertRequestRecord.builder().data(data).build()); + BulkInsertRequest request = BulkInsertRequest.builder().tableName(" ").records(records).build(); + try { + Validations.validateBulkInsertRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorMessage.TableNotSpecifiedInRequestAndRecordObject.getMessage(), e.getMessage()); + } + } + + @Test + public void testValidateInsertRequest_upsertAtRequestLevelWithTableAtRecordLevelThrows() { + // upsert must sit at the same level as the table name. + Map data = new HashMap<>(); + data.put("name", "john"); + ArrayList records = new ArrayList<>(); + records.add(InsertRequestRecord.builder().tableName("table1").data(data).build()); + InsertRequest request = InsertRequest.builder() + .records(records) + .upsert(UpsertOptions.builder().uniqueColumns(Collections.singletonList("email")).build()) + .build(); + try { + Validations.validateInsertRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorMessage.UpsertTableRequestAtRequestLevel.getMessage(), e.getMessage()); + } + } + + @Test + public void testValidateInsertRequest_upsertAtRecordLevelWithTableAtRequestLevelThrows() { + Map data = new HashMap<>(); + data.put("name", "john"); + ArrayList records = new ArrayList<>(); + records.add(InsertRequestRecord.builder() + .data(data) + .upsert(UpsertOptions.builder().uniqueColumns(Collections.singletonList("email")).build()) + .build()); + InsertRequest request = InsertRequest.builder().tableName("table1").records(records).build(); + try { + Validations.validateInsertRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertEquals(ErrorMessage.UpsertTableRequestAtRecordLevel.getMessage(), e.getMessage()); + } + } + + @Test + public void testValidateInsertRequest_upsertOnSomeRecordsOnlyIsValid() { + // Upsert is optional per record; it only has to sit at the same level as the table name. + Map data = new HashMap<>(); + data.put("name", "john"); + ArrayList records = new ArrayList<>(); + records.add(InsertRequestRecord.builder() + .tableName("table1") + .data(data) + .upsert(UpsertOptions.builder().uniqueColumns(Collections.singletonList("email")).build()) + .build()); + records.add(InsertRequestRecord.builder().tableName("table1").data(data).build()); + InsertRequest request = InsertRequest.builder().records(records).build(); + try { + Validations.validateInsertRequest(request); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testValidateInsertRequest_upsertOnEveryRecordWithTableOnEveryRecordIsValid() { + Map data = new HashMap<>(); + data.put("name", "john"); + ArrayList records = new ArrayList<>(); + for (int i = 0; i < 2; i++) { + records.add(InsertRequestRecord.builder() + .tableName("table1") + .data(data) + .upsert(UpsertOptions.builder().uniqueColumns(Collections.singletonList("email")).build()) + .build()); + } + InsertRequest request = InsertRequest.builder().records(records).build(); + try { + Validations.validateInsertRequest(request); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testValidateInsertRequest_noUpsertAnywhereIsValid() { + Map data = new HashMap<>(); + data.put("name", "john"); + ArrayList records = new ArrayList<>(); + records.add(InsertRequestRecord.builder().data(data).build()); + InsertRequest request = InsertRequest.builder().tableName("table1").records(records).build(); + try { + Validations.validateInsertRequest(request); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + // ── validateBulkDetokenizeRequest ────────────────────────────────────────── + + @Test + public void testValidateBulkDetokenizeRequest_nullRequest() { + try { + Validations.validateBulkDetokenizeRequest(null); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateBulkDetokenizeRequest_nullTokens() { + BulkDetokenizeRequest request = BulkDetokenizeRequest.builder().tokens(null).build(); + try { + Validations.validateBulkDetokenizeRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateBulkDetokenizeRequest_emptyTokens() { + BulkDetokenizeRequest request = BulkDetokenizeRequest.builder().tokens(new ArrayList<>()).build(); + try { + Validations.validateBulkDetokenizeRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateBulkDetokenizeRequest_nullTokenInList() { + List tokens = new ArrayList<>(); + tokens.add(null); + BulkDetokenizeRequest request = BulkDetokenizeRequest.builder().tokens(tokens).build(); + try { + Validations.validateBulkDetokenizeRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateBulkDetokenizeRequest_nullRedactionGroupObject() { + List groupRedactions = new ArrayList<>(); + groupRedactions.add(null); + BulkDetokenizeRequest request = BulkDetokenizeRequest.builder() + .tokens(Collections.singletonList("token1")) + .tokenGroupRedactions(groupRedactions) + .build(); + try { + Validations.validateBulkDetokenizeRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateBulkDetokenizeRequest_nullGroupNameInRedaction() { + List groupRedactions = Collections.singletonList( + TokenGroupRedactions.builder().redaction("MASKED").build()); + BulkDetokenizeRequest request = BulkDetokenizeRequest.builder() + .tokens(Collections.singletonList("token1")) + .tokenGroupRedactions(groupRedactions) + .build(); + try { + Validations.validateBulkDetokenizeRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateBulkDetokenizeRequest_nullRedactionInGroup() { + List groupRedactions = Collections.singletonList( + TokenGroupRedactions.builder().tokenGroupName("group1").build()); + BulkDetokenizeRequest request = BulkDetokenizeRequest.builder() + .tokens(Collections.singletonList("token1")) + .tokenGroupRedactions(groupRedactions) + .build(); + try { + Validations.validateBulkDetokenizeRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateBulkDetokenizeRequest_validRequest() { + BulkDetokenizeRequest request = BulkDetokenizeRequest.builder() + .tokens(Collections.singletonList("token1")) + .build(); + try { + Validations.validateBulkDetokenizeRequest(request); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testValidateBulkDetokenizeRequest_validRequestWithRedactions() { + List groupRedactions = Collections.singletonList( + TokenGroupRedactions.builder().tokenGroupName("group1").redaction("MASKED").build()); + BulkDetokenizeRequest request = BulkDetokenizeRequest.builder() + .tokens(Collections.singletonList("token1")) + .tokenGroupRedactions(groupRedactions) + .build(); + try { + Validations.validateBulkDetokenizeRequest(request); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + // ── validateBulkDeleteTokensRequest ──────────────────────────────────────── + + @Test + public void testValidateBulkDeleteTokensRequest_nullRequest() { + try { + Validations.validateBulkDeleteTokensRequest(null); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateBulkDeleteTokensRequest_emptyTokens() { + BulkDeleteTokensRequest request = BulkDeleteTokensRequest.builder().tokens(new ArrayList<>()).build(); + try { + Validations.validateBulkDeleteTokensRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateBulkDeleteTokensRequest_emptyTokenInList() { + BulkDeleteTokensRequest request = BulkDeleteTokensRequest.builder() + .tokens(Arrays.asList("token1", " ")) + .build(); + try { + Validations.validateBulkDeleteTokensRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateBulkDeleteTokensRequest_validRequest() { + BulkDeleteTokensRequest request = BulkDeleteTokensRequest.builder() + .tokens(Collections.singletonList("token1")) + .build(); + try { + Validations.validateBulkDeleteTokensRequest(request); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + // ── validateBulkTokenizeRequest ──────────────────────────────────────────── + + @Test + public void testValidateBulkTokenizeRequest_nullRequest() { + try { + Validations.validateBulkTokenizeRequest(null); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateBulkTokenizeRequest_emptyData() { + BulkTokenizeRequest request = BulkTokenizeRequest.builder().records(new ArrayList<>()).build(); + try { + Validations.validateBulkTokenizeRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateBulkTokenizeRequest_nullRecordInList() { + ArrayList data = new ArrayList<>(); + data.add(null); + BulkTokenizeRequest request = BulkTokenizeRequest.builder().records(data).build(); + try { + Validations.validateBulkTokenizeRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateBulkTokenizeRequest_emptyValue() { + ArrayList data = new ArrayList<>(); + data.add(BulkTokenizeRequestRecord.builder().value(" ").build()); + BulkTokenizeRequest request = BulkTokenizeRequest.builder().records(data).build(); + try { + Validations.validateBulkTokenizeRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateBulkTokenizeRequest_emptyGroupNameInList() { + ArrayList data = new ArrayList<>(); + data.add(BulkTokenizeRequestRecord.builder().value("value1").tokenGroupNames(Arrays.asList("group1", " ")).build()); + BulkTokenizeRequest request = BulkTokenizeRequest.builder().records(data).build(); + try { + Validations.validateBulkTokenizeRequest(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testValidateBulkTokenizeRequest_validRequestWithTokenGroupNames() { + ArrayList data = new ArrayList<>(); + data.add(BulkTokenizeRequestRecord.builder().value("value1").tokenGroupNames(Collections.singletonList("group1")).build()); + BulkTokenizeRequest request = BulkTokenizeRequest.builder().records(data).build(); + try { + Validations.validateBulkTokenizeRequest(request); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testValidateBulkTokenizeRequest_validRequestWithoutTokenGroupNames() { + // Unlike the non-bulk tokenize validator, tokenGroupNames is optional for bulk requests. + ArrayList data = new ArrayList<>(); + data.add(BulkTokenizeRequestRecord.builder().value("value1").build()); + BulkTokenizeRequest request = BulkTokenizeRequest.builder().records(data).build(); + try { + Validations.validateBulkTokenizeRequest(request); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testValidateBulkTokenizeRequest_nonSequentialIndexesAccepted() { + // indexes only need to be present and unique - gaps and ordering are the caller's business + ArrayList data = new ArrayList<>(); + data.add(BulkTokenizeRequestRecord.builder().value("value1").build()); + data.add(BulkTokenizeRequestRecord.builder().value("value2").build()); + BulkTokenizeRequest request = BulkTokenizeRequest.builder().records(data).build(); + try { + Validations.validateBulkTokenizeRequest(request); + } catch (SkyflowException e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + // Tests for validateQueryRequest / validateGetRequest were removed: + // those unary validators no longer exist (bulk-only module). + +} diff --git a/flowvault/src/test/java/com/skyflow/vault/controller/BatchConfigResolutionTests.java b/flowvault/src/test/java/com/skyflow/vault/controller/BatchConfigResolutionTests.java new file mode 100644 index 00000000..e75b9e1e --- /dev/null +++ b/flowvault/src/test/java/com/skyflow/vault/controller/BatchConfigResolutionTests.java @@ -0,0 +1,264 @@ +package com.skyflow.vault.controller; + +import com.skyflow.VaultClient; +import com.skyflow.config.VaultConfig; +import com.skyflow.enums.Env; +import com.skyflow.errors.SkyflowException; +import com.skyflow.utils.Constants; +import org.junit.After; +import org.junit.Assert; +import org.junit.Test; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +/** + * Batch size and concurrency resolution for all four bulk operations. + * + *

These settings are user-tunable only through the environment, so the tests swap + * {@link VaultController#settingResolver} rather than mutating the JVM environment. Each operation + * has its own near-identical resolver, so every case runs against all four to stop one drifting. + */ +public class BatchConfigResolutionTests { + + private final Function originalResolver = VaultController.settingResolver; + + @After + public void restoreResolver() { + VaultController.settingResolver = originalResolver; + } + + /** Operation name, its env-var prefix, and its default/max constants. */ + private static final String[][] OPS = { + {"Insert", "INSERT"}, + {"Detokenize", "DETOKENIZE"}, + {"Tokenize", "TOKENIZE"}, + {"DeleteTokens", "DELETE_TOKENS"}, + }; + + private static void useSettings(Map settings) { + VaultController.settingResolver = settings::get; + } + + private static Map settings(String prefix, String batchSize, String concurrency) { + Map map = new HashMap<>(); + if (batchSize != null) { + map.put(prefix + "_BATCH_SIZE", batchSize); + } + if (concurrency != null) { + map.put(prefix + "_CONCURRENCY_LIMIT", concurrency); + } + return map; + } + + private static VaultController controller() throws SkyflowException { + VaultConfig config = new VaultConfig(); + config.setVaultId("vault1"); + config.setClusterId("cluster1"); + config.setEnv(Env.DEV); + return new VaultController(config, null); + } + + /** Invokes the private configure<Op>ConcurrencyAndBatchSize and reads the result. */ + private static int[] resolve(String op, int totalRequests) throws Exception { + Method method = VaultController.class.getDeclaredMethod( + "configure" + op + "ConcurrencyAndBatchSize", int.class); + method.setAccessible(true); + Object cfg; + try { + cfg = method.invoke(controller(), totalRequests); + } catch (InvocationTargetException e) { + throw (Exception) e.getCause(); + } + Field batchSize = cfg.getClass().getDeclaredField("batchSize"); + Field concurrency = cfg.getClass().getDeclaredField("concurrencyLimit"); + batchSize.setAccessible(true); + concurrency.setAccessible(true); + return new int[] {(int) batchSize.get(cfg), (int) concurrency.get(cfg)}; + } + + private static int defaultBatchSize(String prefix) { + switch (prefix) { + case "INSERT": return Constants.INSERT_BATCH_SIZE; + case "DETOKENIZE": return Constants.DETOKENIZE_BATCH_SIZE; + case "TOKENIZE": return Constants.TOKENIZE_BATCH_SIZE; + default: return Constants.DELETE_TOKENS_BATCH_SIZE; + } + } + + private static int maxBatchSize(String prefix) { + switch (prefix) { + case "INSERT": return Constants.MAX_INSERT_BATCH_SIZE; + case "DETOKENIZE": return Constants.MAX_DETOKENIZE_BATCH_SIZE; + case "TOKENIZE": return Constants.MAX_TOKENIZE_BATCH_SIZE; + default: return Constants.MAX_DELETE_TOKENS_BATCH_SIZE; + } + } + + private static int maxConcurrency(String prefix) { + switch (prefix) { + case "INSERT": return Constants.MAX_INSERT_CONCURRENCY_LIMIT; + case "DETOKENIZE": return Constants.MAX_DETOKENIZE_CONCURRENCY_LIMIT; + case "TOKENIZE": return Constants.MAX_TOKENIZE_CONCURRENCY_LIMIT; + default: return Constants.MAX_DELETE_TOKENS_CONCURRENCY_LIMIT; + } + } + + // ── batch size ──────────────────────────────────────────────────────────── + + @Test + public void testBatchSize_defaultsWhenNothingIsConfigured() throws Exception { + for (String[] op : OPS) { + useSettings(new HashMap<>()); + Assert.assertEquals(op[0], defaultBatchSize(op[1]), resolve(op[0], 10_000)[0]); + } + } + + @Test + public void testBatchSize_honoursAValidSetting() throws Exception { + for (String[] op : OPS) { + useSettings(settings(op[1], "200", null)); + Assert.assertEquals(op[0], 200, resolve(op[0], 10_000)[0]); + } + } + + @Test + public void testBatchSize_isCappedAtTheMaximum() throws Exception { + for (String[] op : OPS) { + useSettings(settings(op[1], "999999", null)); + Assert.assertEquals(op[0], maxBatchSize(op[1]), resolve(op[0], 10_000)[0]); + } + } + + @Test + public void testBatchSize_exactlyAtTheMaximumIsAccepted() throws Exception { + for (String[] op : OPS) { + useSettings(settings(op[1], String.valueOf(maxBatchSize(op[1])), null)); + Assert.assertEquals(op[0], maxBatchSize(op[1]), resolve(op[0], 10_000)[0]); + } + } + + @Test + public void testBatchSize_zeroFallsBackToTheDefault() throws Exception { + for (String[] op : OPS) { + useSettings(settings(op[1], "0", null)); + Assert.assertEquals(op[0], defaultBatchSize(op[1]), resolve(op[0], 10_000)[0]); + } + } + + @Test + public void testBatchSize_negativeFallsBackToTheDefault() throws Exception { + for (String[] op : OPS) { + useSettings(settings(op[1], "-5", null)); + Assert.assertEquals(op[0], defaultBatchSize(op[1]), resolve(op[0], 10_000)[0]); + } + } + + @Test + public void testBatchSize_nonNumericFallsBackToTheDefault() throws Exception { + for (String[] op : OPS) { + useSettings(settings(op[1], "not-a-number", null)); + Assert.assertEquals(op[0], defaultBatchSize(op[1]), resolve(op[0], 10_000)[0]); + } + } + + @Test + public void testBatchSize_emptyStringFallsBackToTheDefault() throws Exception { + for (String[] op : OPS) { + useSettings(settings(op[1], "", null)); + Assert.assertEquals(op[0], defaultBatchSize(op[1]), resolve(op[0], 10_000)[0]); + } + } + + // ── concurrency limit ───────────────────────────────────────────────────── + + @Test + public void testConcurrency_defaultsToOneWhenNothingIsConfigured() throws Exception { + for (String[] op : OPS) { + useSettings(new HashMap<>()); + Assert.assertEquals(op[0], 1, resolve(op[0], 10_000)[1]); + } + } + + @Test + public void testConcurrency_honoursAValidSetting() throws Exception { + for (String[] op : OPS) { + useSettings(settings(op[1], "1", "5")); + Assert.assertEquals(op[0], 5, resolve(op[0], 10_000)[1]); + } + } + + @Test + public void testConcurrency_isCappedAtTheMaximum() throws Exception { + for (String[] op : OPS) { + useSettings(settings(op[1], "1", "999")); + Assert.assertEquals(op[0], maxConcurrency(op[1]), resolve(op[0], 10_000)[1]); + } + } + + @Test + public void testConcurrency_zeroFallsBackToTheDefault() throws Exception { + for (String[] op : OPS) { + useSettings(settings(op[1], "1", "0")); + Assert.assertEquals(op[0], 1, resolve(op[0], 10_000)[1]); + } + } + + @Test + public void testConcurrency_negativeFallsBackToTheDefault() throws Exception { + for (String[] op : OPS) { + useSettings(settings(op[1], "1", "-3")); + Assert.assertEquals(op[0], 1, resolve(op[0], 10_000)[1]); + } + } + + @Test + public void testConcurrency_nonNumericFallsBackToTheDefault() throws Exception { + for (String[] op : OPS) { + useSettings(settings(op[1], "1", "lots")); + Assert.assertEquals(op[0], 1, resolve(op[0], 10_000)[1]); + } + } + + // ── concurrency is further capped by how many batches there actually are ── + + @Test + public void testConcurrency_neverExceedsTheNumberOfBatches() throws Exception { + for (String[] op : OPS) { + // 25 records at batch size 10 is 3 batches, so 10 threads would leave 7 idle. + useSettings(settings(op[1], "10", "10")); + Assert.assertEquals(op[0], 3, resolve(op[0], 25)[1]); + } + } + + @Test + public void testConcurrency_singleBatchUsesASingleThread() throws Exception { + for (String[] op : OPS) { + useSettings(settings(op[1], "100", "10")); + Assert.assertEquals(op[0], 1, resolve(op[0], 5)[1]); + } + } + + @Test + public void testConcurrency_exactBatchMultipleDoesNotRoundUp() throws Exception { + for (String[] op : OPS) { + useSettings(settings(op[1], "10", "10")); + Assert.assertEquals(op[0], 2, resolve(op[0], 20)[1]); + } + } + + @Test + public void testBatchSizeAndConcurrency_resolveIndependently() throws Exception { + for (String[] op : OPS) { + // batch size invalid (falls back), concurrency valid + useSettings(settings(op[1], "oops", "4")); + int[] cfg = resolve(op[0], 10_000); + Assert.assertEquals(op[0], defaultBatchSize(op[1]), cfg[0]); + Assert.assertEquals(op[0], 4, cfg[1]); + } + } +} diff --git a/flowvault/src/test/java/com/skyflow/vault/controller/BulkDeleteTokensBatchingTests.java b/flowvault/src/test/java/com/skyflow/vault/controller/BulkDeleteTokensBatchingTests.java new file mode 100644 index 00000000..66bb20a0 --- /dev/null +++ b/flowvault/src/test/java/com/skyflow/vault/controller/BulkDeleteTokensBatchingTests.java @@ -0,0 +1,235 @@ +package com.skyflow.vault.controller; + +import com.skyflow.VaultClient; +import com.skyflow.config.Credentials; +import com.skyflow.config.VaultConfig; +import com.skyflow.enums.Env; +import com.skyflow.generated.rest.ApiClient; +import com.skyflow.generated.rest.core.ApiClientApiException; +import com.skyflow.generated.rest.core.ApiClientHttpResponse; +import com.skyflow.generated.rest.resources.flowservice.FlowserviceClient; +import com.skyflow.generated.rest.resources.flowservice.RawFlowserviceClient; +import com.skyflow.generated.rest.resources.flowservice.requests.V1FlowDeleteTokenRequest; +import com.skyflow.generated.rest.types.V1DeleteTokenResponseObject; +import com.skyflow.generated.rest.types.V1FlowDeleteTokenResponse; +import com.skyflow.utils.Constants; +import com.skyflow.vault.data.BulkDeleteTokensRequest; +import com.skyflow.vault.data.BulkDeleteTokensResponse; +import com.skyflow.vault.data.BulkDeleteTokensResponseRecord; +import okhttp3.Protocol; +import okhttp3.Request; +import okhttp3.Response; +import org.junit.After; +import org.junit.Assert; +import org.junit.Test; +import org.mockito.Mockito; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +/** + * Batching and concurrency behaviour for bulkDeleteTokens / bulkDeleteTokensAsync. + * + *

Batch size and concurrency are user-tunable only through the environment, so these tests swap + * {@link VaultController#settingResolver} instead of mutating the JVM environment. Every case here + * drives more than one batch and forces batches to complete out of order, so an implementation that + * derived the record index from completion order (rather than from the batch's position in the + * original request) would fail. + */ +public class BulkDeleteTokensBatchingTests { + + private static final int TOTAL_TOKENS = 50; + + private final Function originalResolver = VaultController.settingResolver; + + @After + public void restoreResolver() { + VaultController.settingResolver = originalResolver; + } + + private static void useBatching(int batchSize, int concurrency) { + Map settings = new HashMap<>(); + settings.put("DELETE_TOKENS_BATCH_SIZE", String.valueOf(batchSize)); + settings.put("DELETE_TOKENS_CONCURRENCY_LIMIT", String.valueOf(concurrency)); + VaultController.settingResolver = settings::get; + } + + private static Response okHttp() { + return new Response.Builder() + .request(new Request.Builder().url("https://dummy.example.com").build()) + .protocol(Protocol.HTTP_1_1).code(200).message("OK") + .header(Constants.REQUEST_ID_HEADER_KEY, "req-test-123").build(); + } + + /** Delay derived from the batch's first token so batches finish in a scrambled order. */ + private static void scrambleCompletion(List tokens) throws InterruptedException { + int first = Integer.parseInt(tokens.get(0).substring(4)); + Thread.sleep(((first * 7919L) % 41) + 3); + } + + private static List tokens(int count) { + List tokens = new ArrayList<>(); + for (int i = 0; i < count; i++) { + tokens.add("tok-" + i); + } + return tokens; + } + + private static VaultController controllerWith(ApiClient mockApi) throws Exception { + Credentials creds = new Credentials(); + creds.setApiKey("sky-ab123-abcd1234cdef1234abcd4321cdef4321"); + VaultConfig config = new VaultConfig(); + config.setVaultId("vault123"); + config.setClusterId("cluster123"); + config.setEnv(Env.DEV); + VaultController controller = new VaultController(config, creds); + Field field = VaultClient.class.getDeclaredField("apiClient"); + field.setAccessible(true); + field.set(controller, mockApi); + return controller; + } + + private static RawFlowserviceClient mockRawFlowservice(ApiClient mockApi) { + FlowserviceClient mockFlow = Mockito.mock(FlowserviceClient.class); + RawFlowserviceClient mockRaw = Mockito.mock(RawFlowserviceClient.class); + when(mockApi.flowservice()).thenReturn(mockFlow); + when(mockFlow.withRawResponse()).thenReturn(mockRaw); + return mockRaw; + } + + /** Echoes each batch back; any token whose number ends in 7 fails with 404. */ + private static ApiClient mockApiEchoingBatches() throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + RawFlowserviceClient mockRaw = mockRawFlowservice(mockApi); + when(mockRaw.deletetoken(any(), any())).thenAnswer(invocation -> { + V1FlowDeleteTokenRequest request = invocation.getArgument(0); + List batchTokens = request.getTokens().get(); + scrambleCompletion(batchTokens); + List records = new ArrayList<>(); + for (String token : batchTokens) { + if (token.endsWith("7")) { + records.add(V1DeleteTokenResponseObject.builder() + .value(token).error("Token not found").httpCode(404).build()); + } else { + records.add(V1DeleteTokenResponseObject.builder() + .value(token).httpCode(200).build()); + } + } + return new ApiClientHttpResponse<>( + V1FlowDeleteTokenResponse.builder().tokens(records).build(), okHttp()); + }); + return mockApi; + } + + private static void assertIndexesPreserved(BulkDeleteTokensResponse response) { + Assert.assertEquals(TOTAL_TOKENS, response.getRecords().size()); + for (int i = 0; i < TOTAL_TOKENS; i++) { + BulkDeleteTokensResponseRecord record = response.getRecords().get(i); + Assert.assertEquals("index at position " + i, i, record.getIndex()); + Assert.assertEquals("token at position " + i, "tok-" + i, record.getToken()); + } + } + + @Test + public void testBulkDeleteTokens_parallelBatchesPreserveIndexOrder() throws Exception { + useBatching(10, 5); + VaultController controller = controllerWith(mockApiEchoingBatches()); + + BulkDeleteTokensResponse response = controller.bulkDeleteTokens( + BulkDeleteTokensRequest.builder().tokens(tokens(TOTAL_TOKENS)).build()); + + assertIndexesPreserved(response); + // tok-7, 17, 27, 37, 47 fail; every failure keeps its own index and token + Assert.assertEquals(TOTAL_TOKENS, response.getSummary().getTotalTokens()); + Assert.assertEquals(5, response.getSummary().getTotalFailed()); + Assert.assertEquals(45, response.getSummary().getTotalDeleted()); + Assert.assertEquals("Token not found", response.getRecords().get(7).getError()); + Assert.assertEquals(Integer.valueOf(404), response.getRecords().get(7).getHttpCode()); + Assert.assertNull(response.getRecords().get(8).getError()); + } + + @Test + public void testBulkDeleteTokensAsync_parallelBatchesPreserveIndexOrder() throws Exception { + useBatching(10, 5); + VaultController controller = controllerWith(mockApiEchoingBatches()); + + BulkDeleteTokensResponse response = controller.bulkDeleteTokensAsync( + BulkDeleteTokensRequest.builder().tokens(tokens(TOTAL_TOKENS)).build()) + .get(30, TimeUnit.SECONDS); + + assertIndexesPreserved(response); + Assert.assertEquals(5, response.getSummary().getTotalFailed()); + Assert.assertEquals(45, response.getSummary().getTotalDeleted()); + } + + @Test + public void testBulkDeleteTokens_maxConcurrencyStillPreservesIndexOrder() throws Exception { + // batch size 5 => 10 batches, all runnable at once at the max concurrency limit + useBatching(5, 10); + VaultController controller = controllerWith(mockApiEchoingBatches()); + + BulkDeleteTokensResponse response = controller.bulkDeleteTokens( + BulkDeleteTokensRequest.builder().tokens(tokens(TOTAL_TOKENS)).build()); + + assertIndexesPreserved(response); + Assert.assertEquals(TOTAL_TOKENS, response.getSummary().getTotalTokens()); + } + + @Test + public void testBulkDeleteTokens_failedBatchKeepsItsOwnIndexSlice() throws Exception { + useBatching(10, 5); + ApiClient mockApi = Mockito.mock(ApiClient.class); + RawFlowserviceClient mockRaw = mockRawFlowservice(mockApi); + // the batch starting at tok-20 fails wholesale; the rest succeed + when(mockRaw.deletetoken(any(), any())).thenAnswer(invocation -> { + V1FlowDeleteTokenRequest request = invocation.getArgument(0); + List batchTokens = request.getTokens().get(); + scrambleCompletion(batchTokens); + if ("tok-20".equals(batchTokens.get(0))) { + throw new ApiClientApiException("delete failed", 503, "service unavailable"); + } + List records = new ArrayList<>(); + for (String token : batchTokens) { + records.add(V1DeleteTokenResponseObject.builder().value(token).httpCode(200).build()); + } + return new ApiClientHttpResponse<>( + V1FlowDeleteTokenResponse.builder().tokens(records).build(), okHttp()); + }); + VaultController controller = controllerWith(mockApi); + + BulkDeleteTokensResponse response = controller.bulkDeleteTokens( + BulkDeleteTokensRequest.builder().tokens(tokens(TOTAL_TOKENS)).build()); + + // no gaps and no shifting: the failed batch still contributes exactly indexes 20..29 + assertIndexesPreserved(response); + Assert.assertEquals(10, response.getSummary().getTotalFailed()); + Assert.assertEquals(40, response.getSummary().getTotalDeleted()); + for (int i = 20; i < 30; i++) { + Assert.assertNotNull("expected error at index " + i, response.getRecords().get(i).getError()); + Assert.assertEquals(Integer.valueOf(503), response.getRecords().get(i).getHttpCode()); + Assert.assertEquals("tok-" + i, response.getRecords().get(i).getToken()); + } + Assert.assertNull(response.getRecords().get(19).getError()); + Assert.assertNull(response.getRecords().get(30).getError()); + } + + @Test + public void testBulkDeleteTokens_batchSizeAboveMaxIsCapped() throws Exception { + // 5000 exceeds MAX_DELETE_TOKENS_BATCH_SIZE (1000) — capped, so 50 tokens stay one batch + useBatching(5000, 1); + VaultController controller = controllerWith(mockApiEchoingBatches()); + + BulkDeleteTokensResponse response = controller.bulkDeleteTokens( + BulkDeleteTokensRequest.builder().tokens(tokens(TOTAL_TOKENS)).build()); + + assertIndexesPreserved(response); + } +} diff --git a/flowvault/src/test/java/com/skyflow/vault/controller/VaultControllerTests.java b/flowvault/src/test/java/com/skyflow/vault/controller/VaultControllerTests.java new file mode 100644 index 00000000..f23fd2d2 --- /dev/null +++ b/flowvault/src/test/java/com/skyflow/vault/controller/VaultControllerTests.java @@ -0,0 +1,1119 @@ +package com.skyflow.vault.controller; + +import com.skyflow.VaultClient; +import com.skyflow.config.Credentials; +import com.skyflow.config.VaultConfig; +import com.skyflow.enums.CustomHeaderKey; +import com.skyflow.enums.Env; +import com.skyflow.errors.SkyflowException; +import com.skyflow.generated.rest.ApiClient; +import com.skyflow.generated.rest.core.ApiClientApiException; +import com.skyflow.generated.rest.core.ApiClientHttpResponse; +import com.skyflow.generated.rest.core.RequestOptions; +import com.skyflow.generated.rest.resources.flowservice.FlowserviceClient; +import com.skyflow.generated.rest.resources.flowservice.RawFlowserviceClient; +import com.skyflow.generated.rest.types.FlowTokenizeResponseObjectToken; +import com.skyflow.generated.rest.types.V1DeleteTokenResponseObject; +import com.skyflow.generated.rest.types.V1FlowDeleteTokenResponse; +import com.skyflow.generated.rest.types.V1FlowDetokenizeResponse; +import com.skyflow.generated.rest.types.V1FlowDetokenizeResponseObject; +import com.skyflow.generated.rest.types.V1FlowTokenizeResponse; +import com.skyflow.generated.rest.types.V1FlowTokenizeResponseObject; +import com.skyflow.generated.rest.types.V1InsertResponse; +import com.skyflow.generated.rest.types.V1RecordResponseObject; +import com.skyflow.utils.Constants; +import com.skyflow.vault.data.BulkDeleteTokensOptions; +import com.skyflow.vault.data.BulkTokenizeOptions; +import com.skyflow.vault.data.BulkDeleteTokensRequest; +import com.skyflow.vault.data.BulkDeleteTokensResponse; +import com.skyflow.vault.data.BulkDetokenizeRequest; +import com.skyflow.vault.data.BulkDetokenizeResponse; +import com.skyflow.vault.data.BulkInsertRequestRecord; +import com.skyflow.vault.data.BulkInsertRequest; +import com.skyflow.vault.data.BulkInsertResponse; +import com.skyflow.vault.data.BulkTokenizeRequestRecord; +import com.skyflow.vault.data.BulkTokenizeResponseRecord; +import com.skyflow.vault.data.BulkInsertResponseRecord; +import com.skyflow.vault.data.BulkTokenizeRequest; +import com.skyflow.vault.data.BulkTokenizeResponse; +import com.skyflow.vault.data.BulkDetokenizeOptions; +import com.skyflow.vault.data.BulkInsertOptions; +import com.skyflow.vault.data.DeleteTokensOptions; +import com.skyflow.vault.data.InsertRequestRecord; +import com.skyflow.vault.data.RequestInterceptor; +import com.skyflow.vault.data.TokenGroupRedactions; +import com.skyflow.vault.data.TokenizeOptions; +import com.skyflow.vault.data.TokenizeRequestRecord; +import com.skyflow.vault.data.TokenizeRequest; +import com.skyflow.vault.data.TokenizeResponse; +import okhttp3.Protocol; +import okhttp3.Request; +import okhttp3.Response; +import org.junit.Assert; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +public class VaultControllerTests { + private static final String EXCEPTION_NOT_THROWN = "Should have thrown an exception"; + private static final String INVALID_EXCEPTION_THROWN = "Should not have thrown any exception"; + + private static VaultController createControllerWithMock(ApiClient mockApiClient) throws Exception { + Credentials creds = new Credentials(); + creds.setApiKey("sky-ab123-abcd1234cdef1234abcd4321cdef4321"); + + VaultConfig config = new VaultConfig(); + config.setVaultId("vault123"); + config.setClusterId("cluster123"); + config.setEnv(Env.DEV); + + VaultController controller = new VaultController(config, creds); + Field field = VaultClient.class.getDeclaredField("apiClient"); + field.setAccessible(true); + field.set(controller, mockApiClient); + return controller; + } + + private static Response buildOkHttpResponse() { + return new Response.Builder() + .request(new Request.Builder().url("https://dummy.example.com").build()) + .protocol(Protocol.HTTP_1_1) + .code(200) + .message("OK") + .header(Constants.REQUEST_ID_HEADER_KEY, "req-test-123") + .build(); + } + + private static RawFlowserviceClient mockRawFlowservice(ApiClient mockApi) { + FlowserviceClient mockFlowservice = Mockito.mock(FlowserviceClient.class); + RawFlowserviceClient mockRaw = Mockito.mock(RawFlowserviceClient.class); + when(mockApi.flowservice()).thenReturn(mockFlowservice); + when(mockFlowservice.withRawResponse()).thenReturn(mockRaw); + return mockRaw; + } + + // Tests for the unary insert / detokenize / tokenize / deleteTokens controller methods + // (and their interceptor-header wiring) were removed: VaultController is bulk-only now. + + // ── bulkInsert ──────────────────────────────────────────────────────────── + + @Test + public void testBulkInsert_success() throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + RawFlowserviceClient mockRaw = mockRawFlowservice(mockApi); + + Map tokens = new HashMap<>(); + tokens.put("name", "tok-abc"); + V1RecordResponseObject record = V1RecordResponseObject.builder().skyflowId("sky-id-1").tokens(tokens).build(); + V1InsertResponse body = V1InsertResponse.builder().records(Collections.singletonList(record)).build(); + ApiClientHttpResponse httpResp = new ApiClientHttpResponse<>(body, buildOkHttpResponse()); + when(mockRaw.insert(any(), any())).thenReturn(httpResp); + + VaultController controller = createControllerWithMock(mockApi); + + Map data = new HashMap<>(); + data.put("name", "john"); + ArrayList records = new ArrayList<>(); + records.add(BulkInsertRequestRecord.builder().tableName("table1").data(data).build()); + BulkInsertRequest request = BulkInsertRequest.builder().records(records).build(); + + BulkInsertResponse response = controller.bulkInsert(request); + Assert.assertNotNull(INVALID_EXCEPTION_THROWN, response); + Assert.assertEquals(1, response.getRecords().size()); + Assert.assertEquals("sky-id-1", response.getRecords().get(0).getSkyflowId()); + Assert.assertNull(response.getRecords().get(0).getError()); + Assert.assertEquals(1, response.getSummary().getTotalRecords()); + Assert.assertEquals(1, response.getSummary().getTotalInserted()); + Assert.assertEquals(0, response.getSummary().getTotalFailed()); + } + + @Test + public void testBulkInsert_invalidRequestThrowsSkyflowException() throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + VaultController controller = createControllerWithMock(mockApi); + BulkInsertRequest request = BulkInsertRequest.builder().records(new ArrayList<>()).build(); + try { + controller.bulkInsert(request); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testBulkInsertAsync_success() throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + RawFlowserviceClient mockRaw = mockRawFlowservice(mockApi); + + Map tokens = new HashMap<>(); + tokens.put("name", "tok-abc"); + V1RecordResponseObject record = V1RecordResponseObject.builder().skyflowId("sky-id-1").tokens(tokens).build(); + V1InsertResponse body = V1InsertResponse.builder().records(Collections.singletonList(record)).build(); + ApiClientHttpResponse httpResp = new ApiClientHttpResponse<>(body, buildOkHttpResponse()); + when(mockRaw.insert(any(), any())).thenReturn(httpResp); + + VaultController controller = createControllerWithMock(mockApi); + + Map data = new HashMap<>(); + data.put("name", "john"); + ArrayList records = new ArrayList<>(); + records.add(BulkInsertRequestRecord.builder().tableName("table1").data(data).build()); + BulkInsertRequest request = BulkInsertRequest.builder().records(records).build(); + + BulkInsertResponse response = controller.bulkInsertAsync(request).get(5, TimeUnit.SECONDS); + Assert.assertNotNull(INVALID_EXCEPTION_THROWN, response); + Assert.assertEquals(1, response.getRecords().size()); + Assert.assertEquals("sky-id-1", response.getRecords().get(0).getSkyflowId()); + } + + @Test + public void testBulkInsert_interceptorAddsCustomHeader() throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + RawFlowserviceClient mockRaw = mockRawFlowservice(mockApi); + + Map tokens = new HashMap<>(); + tokens.put("name", "tok-abc"); + V1RecordResponseObject record = V1RecordResponseObject.builder().skyflowId("sky-id-1").tokens(tokens).build(); + V1InsertResponse body = V1InsertResponse.builder().records(Collections.singletonList(record)).build(); + ApiClientHttpResponse httpResp = new ApiClientHttpResponse<>(body, buildOkHttpResponse()); + when(mockRaw.insert(any(), any())).thenReturn(httpResp); + + VaultController controller = createControllerWithMock(mockApi); + + Map data = new HashMap<>(); + data.put("name", "john"); + ArrayList records = new ArrayList<>(); + records.add(BulkInsertRequestRecord.builder().tableName("table1").data(data).build()); + BulkInsertRequest request = BulkInsertRequest.builder().records(records).build(); + + RequestInterceptor interceptor = ctx -> ctx.addHeader(CustomHeaderKey.SkyflowAccountId, "acct-123"); + BulkInsertOptions options = BulkInsertOptions.builder().interceptor(interceptor).build(); + + controller.bulkInsert(request, options); + + ArgumentCaptor captor = ArgumentCaptor.forClass(RequestOptions.class); + Mockito.verify(mockRaw).insert(any(), captor.capture()); + Assert.assertEquals("acct-123", captor.getValue().getHeaders().get(CustomHeaderKey.SkyflowAccountId.toString())); + } + + // ── bulkDetokenize ──────────────────────────────────────────────────────── + + @Test + public void testBulkDetokenize_success() throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + RawFlowserviceClient mockRaw = mockRawFlowservice(mockApi); + + V1FlowDetokenizeResponseObject record = V1FlowDetokenizeResponseObject.builder() + .token("token1").value("secret-value").build(); + V1FlowDetokenizeResponse body = V1FlowDetokenizeResponse.builder() + .response(Collections.singletonList(record)).build(); + ApiClientHttpResponse httpResp = new ApiClientHttpResponse<>(body, buildOkHttpResponse()); + when(mockRaw.detokenize(any(), any())).thenReturn(httpResp); + + VaultController controller = createControllerWithMock(mockApi); + + BulkDetokenizeRequest request = BulkDetokenizeRequest.builder() + .tokens(Collections.singletonList("token1")) + .build(); + + BulkDetokenizeResponse response = controller.bulkDetokenize(request); + Assert.assertNotNull(INVALID_EXCEPTION_THROWN, response); + Assert.assertEquals(1, response.getRecords().size()); + Assert.assertEquals("token1", response.getRecords().get(0).getToken()); + Assert.assertNull(response.getRecords().get(0).getError()); + Assert.assertEquals(1, response.getSummary().getTotalDetokenized()); + Assert.assertEquals(0, response.getSummary().getTotalFailed()); + } + + @Test + public void testBulkDetokenize_nullRequestThrowsSkyflowExceptionNotNPE() throws Exception { + // Regression test: configureDetokenizeConcurrencyAndBatchSize() must run AFTER validation, + // otherwise detokenizeRequest.getTokens().size() NPEs on a null request before validation + // has a chance to reject it gracefully. + ApiClient mockApi = Mockito.mock(ApiClient.class); + VaultController controller = createControllerWithMock(mockApi); + try { + controller.bulkDetokenize(null); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } catch (NullPointerException e) { + Assert.fail("Expected SkyflowException, got NullPointerException"); + } + } + + @Test + public void testBulkDetokenizeAsync_success() throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + RawFlowserviceClient mockRaw = mockRawFlowservice(mockApi); + + V1FlowDetokenizeResponseObject record = V1FlowDetokenizeResponseObject.builder() + .token("token1").value("secret-value").build(); + V1FlowDetokenizeResponse body = V1FlowDetokenizeResponse.builder() + .response(Collections.singletonList(record)).build(); + ApiClientHttpResponse httpResp = new ApiClientHttpResponse<>(body, buildOkHttpResponse()); + when(mockRaw.detokenize(any(), any())).thenReturn(httpResp); + + VaultController controller = createControllerWithMock(mockApi); + + BulkDetokenizeRequest request = BulkDetokenizeRequest.builder() + .tokens(Collections.singletonList("token1")) + .build(); + + BulkDetokenizeResponse response = controller.bulkDetokenizeAsync(request).get(5, TimeUnit.SECONDS); + Assert.assertNotNull(INVALID_EXCEPTION_THROWN, response); + Assert.assertEquals(1, response.getRecords().size()); + Assert.assertNull(response.getRecords().get(0).getError()); + } + + @Test + public void testBulkDetokenizeAsync_nullRequestThrowsSkyflowExceptionNotNPE() throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + VaultController controller = createControllerWithMock(mockApi); + try { + controller.bulkDetokenizeAsync(null); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } catch (NullPointerException e) { + Assert.fail("Expected SkyflowException, got NullPointerException"); + } + } + + // ── bulkDeleteTokens ────────────────────────────────────────────────────── + + @Test + public void testBulkDeleteTokens_success() throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + RawFlowserviceClient mockRaw = mockRawFlowservice(mockApi); + + V1DeleteTokenResponseObject record = V1DeleteTokenResponseObject.builder().value("token1").build(); + V1FlowDeleteTokenResponse body = V1FlowDeleteTokenResponse.builder() + .tokens(Collections.singletonList(record)).build(); + ApiClientHttpResponse httpResp = new ApiClientHttpResponse<>(body, buildOkHttpResponse()); + when(mockRaw.deletetoken(any(), any())).thenReturn(httpResp); + + VaultController controller = createControllerWithMock(mockApi); + + BulkDeleteTokensRequest request = BulkDeleteTokensRequest.builder() + .tokens(Collections.singletonList("token1")) + .build(); + + BulkDeleteTokensResponse response = controller.bulkDeleteTokens(request); + Assert.assertNotNull(INVALID_EXCEPTION_THROWN, response); + Assert.assertEquals(1, response.getRecords().size()); + Assert.assertEquals("token1", response.getRecords().get(0).getToken()); + Assert.assertNull(response.getRecords().get(0).getError()); + Assert.assertEquals(Integer.valueOf(200), response.getRecords().get(0).getHttpCode()); + } + + @Test + public void testBulkDeleteTokens_nullRequestThrowsSkyflowException() throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + VaultController controller = createControllerWithMock(mockApi); + try { + controller.bulkDeleteTokens(null); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testBulkDeleteTokensAsync_success() throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + RawFlowserviceClient mockRaw = mockRawFlowservice(mockApi); + + V1DeleteTokenResponseObject record = V1DeleteTokenResponseObject.builder().value("token1").build(); + V1FlowDeleteTokenResponse body = V1FlowDeleteTokenResponse.builder() + .tokens(Collections.singletonList(record)).build(); + ApiClientHttpResponse httpResp = new ApiClientHttpResponse<>(body, buildOkHttpResponse()); + when(mockRaw.deletetoken(any(), any())).thenReturn(httpResp); + + VaultController controller = createControllerWithMock(mockApi); + + BulkDeleteTokensRequest request = BulkDeleteTokensRequest.builder() + .tokens(Collections.singletonList("token1")) + .build(); + + BulkDeleteTokensResponse response = controller.bulkDeleteTokensAsync(request).get(5, TimeUnit.SECONDS); + Assert.assertNotNull(INVALID_EXCEPTION_THROWN, response); + Assert.assertEquals(1, response.getRecords().size()); + Assert.assertNull(response.getRecords().get(0).getError()); + } + + // ── bulkTokenize ────────────────────────────────────────────────────────── + + @Test + public void testBulkTokenize_success() throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + RawFlowserviceClient mockRaw = mockRawFlowservice(mockApi); + + FlowTokenizeResponseObjectToken token = FlowTokenizeResponseObjectToken.builder() + .tokenGroupName("group1").token("tok-abc").build(); + V1FlowTokenizeResponseObject responseObject = V1FlowTokenizeResponseObject.builder() + .value("value1").tokens(Collections.singletonList(token)).build(); + V1FlowTokenizeResponse body = V1FlowTokenizeResponse.builder() + .response(Collections.singletonList(responseObject)).build(); + ApiClientHttpResponse httpResp = new ApiClientHttpResponse<>(body, buildOkHttpResponse()); + when(mockRaw.tokenize(any(), any())).thenReturn(httpResp); + + VaultController controller = createControllerWithMock(mockApi); + + List records = Collections.singletonList( + BulkTokenizeRequestRecord.builder().value("value1") + .tokenGroupNames(Collections.singletonList("group1")).build()); + BulkTokenizeRequest request = BulkTokenizeRequest.builder().records(records).build(); + + BulkTokenizeResponse response = controller.bulkTokenize(request); + Assert.assertNotNull(INVALID_EXCEPTION_THROWN, response); + Assert.assertEquals(1, response.getRecords().size()); + Assert.assertEquals(0, response.getRecords().get(0).getIndex()); + Assert.assertEquals("tok-abc", response.getRecords().get(0).getTokens().get(0).getToken()); + Assert.assertNull(response.getRecords().get(0).getTokens().get(0).getError()); + } + + @Test + public void testBulkTokenize_nullRequestThrowsSkyflowException() throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + VaultController controller = createControllerWithMock(mockApi); + try { + controller.bulkTokenize(null); + Assert.fail(EXCEPTION_NOT_THROWN); + } catch (SkyflowException e) { + Assert.assertNotNull(e.getMessage()); + } + } + + @Test + public void testBulkTokenizeAsync_success() throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + RawFlowserviceClient mockRaw = mockRawFlowservice(mockApi); + + FlowTokenizeResponseObjectToken token = FlowTokenizeResponseObjectToken.builder() + .tokenGroupName("group1").token("tok-abc").build(); + V1FlowTokenizeResponseObject responseObject = V1FlowTokenizeResponseObject.builder() + .value("value1").tokens(Collections.singletonList(token)).build(); + V1FlowTokenizeResponse body = V1FlowTokenizeResponse.builder() + .response(Collections.singletonList(responseObject)).build(); + ApiClientHttpResponse httpResp = new ApiClientHttpResponse<>(body, buildOkHttpResponse()); + when(mockRaw.tokenize(any(), any())).thenReturn(httpResp); + + VaultController controller = createControllerWithMock(mockApi); + + List records = Collections.singletonList( + BulkTokenizeRequestRecord.builder().value("value1") + .tokenGroupNames(Collections.singletonList("group1")).build()); + BulkTokenizeRequest request = BulkTokenizeRequest.builder().records(records).build(); + + BulkTokenizeResponse response = controller.bulkTokenizeAsync(request).get(5, TimeUnit.SECONDS); + Assert.assertNotNull(INVALID_EXCEPTION_THROWN, response); + Assert.assertEquals(1, response.getRecords().size()); + Assert.assertNull(response.getRecords().get(0).getTokens().get(0).getError()); + } + + // ── additional bulk API-error coverage ─────────────────────────────────── + // + // Note: unlike the singular insert/detokenize/tokenize/deleteTokens calls, the bulk* + // entry points recover a per-batch ApiClientApiException into the response's errors list + // (via Utils.handleBulkXxxBatchException, invoked from the CompletableFuture + // .exceptionally()/.handle() callbacks inside VaultController's *BatchFutures helpers) + // instead of letting a SkyflowException escape the call. That is a deliberate resilience + // design (a single failed batch shouldn't fail an entire bulk request), so these tests + // assert the errors-list outcome rather than a thrown exception. + + @Test + public void testBulkInsertAsync_apiErrorCapturedInErrors() throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + RawFlowserviceClient mockRaw = mockRawFlowservice(mockApi); + when(mockRaw.insert(any(), any())) + .thenThrow(new ApiClientApiException("insert failed", 401, "unauthorized")); + + VaultController controller = createControllerWithMock(mockApi); + + Map data = new HashMap<>(); + data.put("name", "john"); + ArrayList records = new ArrayList<>(); + records.add(BulkInsertRequestRecord.builder().tableName("table1").data(data).build()); + BulkInsertRequest request = BulkInsertRequest.builder().records(records).build(); + + BulkInsertResponse response = controller.bulkInsertAsync(request).get(5, TimeUnit.SECONDS); + Assert.assertNotNull(INVALID_EXCEPTION_THROWN, response); + Assert.assertEquals(1, response.getRecords().size()); + Assert.assertEquals(401, response.getRecords().get(0).getHttpCode()); + Assert.assertNotNull(response.getRecords().get(0).getError()); + Assert.assertNull(response.getRecords().get(0).getSkyflowId()); + } + + @Test + public void testBulkDeleteTokens_apiErrorCapturedInErrors() throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + RawFlowserviceClient mockRaw = mockRawFlowservice(mockApi); + when(mockRaw.deletetoken(any(), any())) + .thenThrow(new ApiClientApiException("delete failed", 404, "not found")); + + VaultController controller = createControllerWithMock(mockApi); + + BulkDeleteTokensRequest request = BulkDeleteTokensRequest.builder() + .tokens(Collections.singletonList("token1")) + .build(); + + BulkDeleteTokensResponse response = controller.bulkDeleteTokens(request); + Assert.assertNotNull(INVALID_EXCEPTION_THROWN, response); + Assert.assertEquals(1, response.getRecords().size()); + Assert.assertEquals(Integer.valueOf(404), response.getRecords().get(0).getHttpCode()); + Assert.assertNotNull(response.getRecords().get(0).getError()); + Assert.assertEquals("token1", response.getRecords().get(0).getToken()); + } + + @Test + public void testBulkDeleteTokensAsync_apiErrorCapturedInErrors() throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + RawFlowserviceClient mockRaw = mockRawFlowservice(mockApi); + when(mockRaw.deletetoken(any(), any())) + .thenThrow(new ApiClientApiException("delete failed", 404, "not found")); + + VaultController controller = createControllerWithMock(mockApi); + + BulkDeleteTokensRequest request = BulkDeleteTokensRequest.builder() + .tokens(Collections.singletonList("token1")) + .build(); + + BulkDeleteTokensResponse response = controller.bulkDeleteTokensAsync(request).get(5, TimeUnit.SECONDS); + Assert.assertNotNull(INVALID_EXCEPTION_THROWN, response); + Assert.assertEquals(1, response.getRecords().size()); + Assert.assertEquals(Integer.valueOf(404), response.getRecords().get(0).getHttpCode()); + Assert.assertNotNull(response.getRecords().get(0).getError()); + Assert.assertEquals("token1", response.getRecords().get(0).getToken()); + } + + @Test + public void testBulkTokenize_apiErrorCapturedInErrors() throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + RawFlowserviceClient mockRaw = mockRawFlowservice(mockApi); + when(mockRaw.tokenize(any(), any())) + .thenThrow(new ApiClientApiException("tokenize failed", 400, "bad request")); + + VaultController controller = createControllerWithMock(mockApi); + + List records = Collections.singletonList( + BulkTokenizeRequestRecord.builder().value("value1") + .tokenGroupNames(Collections.singletonList("group1")).build()); + BulkTokenizeRequest request = BulkTokenizeRequest.builder().records(records).build(); + + BulkTokenizeResponse response = controller.bulkTokenize(request); + Assert.assertNotNull(INVALID_EXCEPTION_THROWN, response); + Assert.assertEquals(1, response.getRecords().size()); + Assert.assertEquals(0, response.getRecords().get(0).getIndex()); + Assert.assertEquals(Integer.valueOf(400), + response.getRecords().get(0).getTokens().get(0).getHttpCode()); + Assert.assertNotNull(response.getRecords().get(0).getTokens().get(0).getError()); + } + + @Test + public void testBulkTokenizeAsync_apiErrorCapturedInErrors() throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + RawFlowserviceClient mockRaw = mockRawFlowservice(mockApi); + when(mockRaw.tokenize(any(), any())) + .thenThrow(new ApiClientApiException("tokenize failed", 400, "bad request")); + + VaultController controller = createControllerWithMock(mockApi); + + List records = Collections.singletonList( + BulkTokenizeRequestRecord.builder().value("value1") + .tokenGroupNames(Collections.singletonList("group1")).build()); + BulkTokenizeRequest request = BulkTokenizeRequest.builder().records(records).build(); + + BulkTokenizeResponse response = controller.bulkTokenizeAsync(request).get(5, TimeUnit.SECONDS); + Assert.assertNotNull(INVALID_EXCEPTION_THROWN, response); + Assert.assertEquals(1, response.getRecords().size()); + Assert.assertEquals(0, response.getRecords().get(0).getIndex()); + Assert.assertEquals(Integer.valueOf(400), + response.getRecords().get(0).getTokens().get(0).getHttpCode()); + Assert.assertNotNull(response.getRecords().get(0).getTokens().get(0).getError()); + } + + @Test + public void testBulkDetokenizeAsync_apiErrorCapturedInErrors() throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + RawFlowserviceClient mockRaw = mockRawFlowservice(mockApi); + when(mockRaw.detokenize(any(), any())) + .thenThrow(new ApiClientApiException("detokenize failed", 401, "unauthorized")); + + VaultController controller = createControllerWithMock(mockApi); + + BulkDetokenizeRequest request = BulkDetokenizeRequest.builder() + .tokens(Collections.singletonList("token1")) + .build(); + + BulkDetokenizeResponse response = controller.bulkDetokenizeAsync(request).get(5, TimeUnit.SECONDS); + Assert.assertNotNull(INVALID_EXCEPTION_THROWN, response); + Assert.assertEquals(1, response.getRecords().size()); + Assert.assertEquals(401, response.getRecords().get(0).getHttpCode()); + Assert.assertNotNull(response.getRecords().get(0).getError()); + Assert.assertEquals(1, response.getSummary().getTotalFailed()); + Assert.assertEquals(0, response.getSummary().getTotalDetokenized()); + } + + // ── multi-batch aggregation / partial-batch failure ────────────────────── + + @Test + public void testBulkInsert_multiBatchAggregatesAcrossBatches() throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + RawFlowserviceClient mockRaw = mockRawFlowservice(mockApi); + + Map tokens1 = new HashMap<>(); + tokens1.put("name", "tok-batch1"); + V1RecordResponseObject record1 = V1RecordResponseObject.builder().skyflowId("sky-id-batch1").tokens(tokens1).build(); + V1InsertResponse body1 = V1InsertResponse.builder().records(Collections.singletonList(record1)).build(); + ApiClientHttpResponse httpResp1 = new ApiClientHttpResponse<>(body1, buildOkHttpResponse()); + + Map tokens2 = new HashMap<>(); + tokens2.put("name", "tok-batch2"); + V1RecordResponseObject record2 = V1RecordResponseObject.builder().skyflowId("sky-id-batch2").tokens(tokens2).build(); + V1InsertResponse body2 = V1InsertResponse.builder().records(Collections.singletonList(record2)).build(); + ApiClientHttpResponse httpResp2 = new ApiClientHttpResponse<>(body2, buildOkHttpResponse()); + + when(mockRaw.insert(any(), any())).thenReturn(httpResp1).thenReturn(httpResp2); + + VaultController controller = createControllerWithMock(mockApi); + + // Constants.INSERT_BATCH_SIZE defaults to 50, so 75 records forces exactly two batches + // (50 + 25) — processBulkInsertSync/insertBatchFutures must merge the per-batch record + // lists collected from more than one CompletableFuture into a single BulkInsertResponse. + ArrayList records = new ArrayList<>(); + for (int i = 0; i < 75; i++) { + Map data = new HashMap<>(); + data.put("name", "john" + i); + records.add(BulkInsertRequestRecord.builder().tableName("table1").data(data).build()); + } + BulkInsertRequest request = BulkInsertRequest.builder().records(records).build(); + + BulkInsertResponse response = controller.bulkInsert(request); + Assert.assertNotNull(INVALID_EXCEPTION_THROWN, response); + Mockito.verify(mockRaw, Mockito.times(2)).insert(any(), any()); + Assert.assertEquals(2, response.getRecords().size()); + Assert.assertTrue(response.getRecords().stream().allMatch(r -> r.getError() == null)); + Assert.assertEquals(75, response.getSummary().getTotalRecords()); + Assert.assertEquals(2, response.getSummary().getTotalInserted()); + Assert.assertEquals(0, response.getSummary().getTotalFailed()); + + BulkInsertResponseRecord batch1Success = response.getRecords().stream() + .filter(s -> "sky-id-batch1".equals(s.getSkyflowId())).findFirst().orElse(null); + BulkInsertResponseRecord batch2Success = response.getRecords().stream() + .filter(s -> "sky-id-batch2".equals(s.getSkyflowId())).findFirst().orElse(null); + Assert.assertNotNull(batch1Success); + Assert.assertNotNull(batch2Success); + // Index offsets prove the batch-number * batchSize aggregation math in + // Utils.formatBulkInsertResponse: batch 0 starts at index 0, batch 1 at index 50. + Assert.assertEquals(0, batch1Success.getIndex()); + Assert.assertEquals(50, batch2Success.getIndex()); + } + + @Test + public void testBulkInsert_partialBatchFailureKeepsBatch1SuccessAndBatch2Error() throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + RawFlowserviceClient mockRaw = mockRawFlowservice(mockApi); + + Map tokens1 = new HashMap<>(); + tokens1.put("name", "tok-batch1"); + V1RecordResponseObject record1 = V1RecordResponseObject.builder().skyflowId("sky-id-batch1").tokens(tokens1).build(); + V1InsertResponse body1 = V1InsertResponse.builder().records(Collections.singletonList(record1)).build(); + ApiClientHttpResponse httpResp1 = new ApiClientHttpResponse<>(body1, buildOkHttpResponse()); + + when(mockRaw.insert(any(), any())) + .thenReturn(httpResp1) + .thenThrow(new ApiClientApiException("insert failed", 500, "server error")); + + VaultController controller = createControllerWithMock(mockApi); + + // 51 records -> batch 1 has 50 records (succeeds), batch 2 has 1 record (fails). + ArrayList records = new ArrayList<>(); + for (int i = 0; i < 51; i++) { + Map data = new HashMap<>(); + data.put("name", "john" + i); + records.add(BulkInsertRequestRecord.builder().tableName("table1").data(data).build()); + } + BulkInsertRequest request = BulkInsertRequest.builder().records(records).build(); + + BulkInsertResponse response = controller.bulkInsert(request); + Assert.assertNotNull(INVALID_EXCEPTION_THROWN, response); + Mockito.verify(mockRaw, Mockito.times(2)).insert(any(), any()); + + Assert.assertEquals(2, response.getRecords().size()); + + BulkInsertResponseRecord inserted = response.getRecords().stream() + .filter(r -> r.getError() == null).findFirst().orElse(null); + Assert.assertNotNull(inserted); + Assert.assertEquals("sky-id-batch1", inserted.getSkyflowId()); + Assert.assertEquals(0, inserted.getIndex()); + + BulkInsertResponseRecord failed = response.getRecords().stream() + .filter(r -> r.getError() != null).findFirst().orElse(null); + Assert.assertNotNull(failed); + Assert.assertEquals(50, failed.getIndex()); + Assert.assertEquals(500, failed.getHttpCode()); + + Assert.assertEquals(51, response.getSummary().getTotalRecords()); + Assert.assertEquals(1, response.getSummary().getTotalInserted()); + Assert.assertEquals(1, response.getSummary().getTotalFailed()); + } + + // ── formatBulkInsertResponse: List token shape ────────────────────── + + @Test + public void testBulkInsert_successWithListOfMapsTokenShape() throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + RawFlowserviceClient mockRaw = mockRawFlowservice(mockApi); + + Map tokenEntry = new HashMap<>(); + tokenEntry.put("token", "tok-xyz"); + tokenEntry.put("tokenGroupName", "group1"); + List> tokenList = new ArrayList<>(); + tokenList.add(tokenEntry); + + Map tokens = new HashMap<>(); + tokens.put("field1", tokenList); + + V1RecordResponseObject record = V1RecordResponseObject.builder().skyflowId("sky-id-1").tokens(tokens).build(); + V1InsertResponse body = V1InsertResponse.builder().records(Collections.singletonList(record)).build(); + ApiClientHttpResponse httpResp = new ApiClientHttpResponse<>(body, buildOkHttpResponse()); + when(mockRaw.insert(any(), any())).thenReturn(httpResp); + + VaultController controller = createControllerWithMock(mockApi); + + Map data = new HashMap<>(); + data.put("name", "john"); + ArrayList records = new ArrayList<>(); + records.add(BulkInsertRequestRecord.builder().tableName("table1").data(data).build()); + BulkInsertRequest request = BulkInsertRequest.builder().records(records).build(); + + BulkInsertResponse response = controller.bulkInsert(request); + Assert.assertNotNull(INVALID_EXCEPTION_THROWN, response); + Assert.assertEquals(1, response.getRecords().size()); + + BulkInsertResponseRecord inserted = response.getRecords().get(0); + Assert.assertNotNull(inserted.getFields()); + // The token map is surfaced verbatim as `fields`, so a List token shape survives intact. + Object field1Tokens = inserted.getFields().get("field1"); + Assert.assertTrue(field1Tokens instanceof List); + Assert.assertEquals(1, ((List) field1Tokens).size()); + Map field1Token = (Map) ((List) field1Tokens).get(0); + Assert.assertEquals("tok-xyz", field1Token.get("token")); + Assert.assertEquals("group1", field1Token.get("tokenGroupName")); + } + + // Tests for the unary query / get controller methods were removed: VaultController is bulk-only now. + + // ───────────────────────────────────────────────────────────────────────── + // Request fidelity through batch dispatch + // + // Constants.INSERT/DETOKENIZE/TOKENIZE/DELETE_TOKENS_BATCH_SIZE all default to 50, so a + // 120-item request produces exactly three batches (50 + 50 + 20). These tests assert that + // (a) non-batched fields are re-applied to EVERY outgoing batch request, (b) item order is + // preserved end to end, (c) the SDK-assigned response index equals the item's position in + // the ORIGINAL user list, and (d) a registered interceptor runs once per batch. + // ───────────────────────────────────────────────────────────────────────── + + private static final int MULTI_BATCH_ITEM_COUNT = 120; + private static final int EXPECTED_BATCH_COUNT = 3; + + /** Interceptor that records each RequestContext it is handed and stamps a per-batch header. */ + private static final class CountingInterceptor implements RequestInterceptor { + private final List contexts = + java.util.Collections.synchronizedList(new ArrayList<>()); + + @Override + public void intercept(com.skyflow.vault.data.RequestContext context) { + int callNumber; + synchronized (contexts) { + callNumber = contexts.size(); + contexts.add(context); + } + context.addHeader(CustomHeaderKey.SkyflowAccountId, "batch-" + callNumber); + } + + int callCount() { + return contexts.size(); + } + + List contexts() { + return contexts; + } + } + + private static void assertInterceptorRanOncePerBatch(CountingInterceptor interceptor, + List capturedOptions) { + Assert.assertEquals(EXPECTED_BATCH_COUNT, interceptor.callCount()); + // Each batch must get its own RequestContext — never a shared/reused one. + java.util.Set identities = new java.util.HashSet<>(); + for (com.skyflow.vault.data.RequestContext ctx : interceptor.contexts()) { + identities.add(System.identityHashCode(ctx)); + } + Assert.assertEquals(EXPECTED_BATCH_COUNT, identities.size()); + + // The header the interceptor set on each context must reach that batch's RequestOptions. + Assert.assertEquals(EXPECTED_BATCH_COUNT, capturedOptions.size()); + java.util.Set headerValues = new java.util.HashSet<>(); + for (RequestOptions options : capturedOptions) { + String value = options.getHeaders().get(CustomHeaderKey.SkyflowAccountId.toString()); + Assert.assertNotNull("Interceptor header missing on a batch", value); + headerValues.add(value); + } + Assert.assertEquals( + new java.util.HashSet<>(java.util.Arrays.asList("batch-0", "batch-1", "batch-2")), + headerValues); + } + + private static ArrayList multiBatchInsertRecords() { + ArrayList records = new ArrayList<>(); + for (int i = 0; i < MULTI_BATCH_ITEM_COUNT; i++) { + Map data = new HashMap<>(); + data.put("pos", String.valueOf(i)); + records.add(BulkInsertRequestRecord.builder().data(data).build()); + } + return records; + } + + private static List multiBatchTokens() { + List tokens = new ArrayList<>(); + for (int i = 0; i < MULTI_BATCH_ITEM_COUNT; i++) { + tokens.add("token-" + i); + } + return tokens; + } + + /** Echoes each request record back as a response record whose skyflowId encodes its "pos". */ + private static void stubInsertEcho(RawFlowserviceClient mockRaw) { + when(mockRaw.insert(any(), any())).thenAnswer(invocation -> { + com.skyflow.generated.rest.resources.flowservice.requests.V1InsertRequest req = + invocation.getArgument(0); + List responseRecords = new ArrayList<>(); + for (com.skyflow.generated.rest.types.V1InsertRecordData record : req.getRecords().get()) { + responseRecords.add(V1RecordResponseObject.builder() + .skyflowId("sky-" + record.getData().get().get("pos")) + .tableName(record.getTableName().orElse(null)) + .build()); + } + V1InsertResponse body = V1InsertResponse.builder().records(responseRecords).build(); + return new ApiClientHttpResponse<>(body, buildOkHttpResponse()); + }); + } + + /** Echoes each requested token back as a detokenize response record. */ + private static void stubDetokenizeEcho(RawFlowserviceClient mockRaw) { + when(mockRaw.detokenize(any(), any())).thenAnswer(invocation -> { + com.skyflow.generated.rest.resources.flowservice.requests.V1FlowDetokenizeRequest req = + invocation.getArgument(0); + List responseRecords = new ArrayList<>(); + for (String token : req.getTokens().get()) { + responseRecords.add(V1FlowDetokenizeResponseObject.builder().token(token).build()); + } + V1FlowDetokenizeResponse body = V1FlowDetokenizeResponse.builder() + .response(responseRecords).build(); + return new ApiClientHttpResponse<>(body, buildOkHttpResponse()); + }); + } + + /** Echoes each requested token back as a delete-token response record. */ + private static void stubDeleteTokensEcho(RawFlowserviceClient mockRaw) { + when(mockRaw.deletetoken(any(), any())).thenAnswer(invocation -> { + com.skyflow.generated.rest.resources.flowservice.requests.V1FlowDeleteTokenRequest req = + invocation.getArgument(0); + List responseRecords = new ArrayList<>(); + for (String token : req.getTokens().get()) { + responseRecords.add(V1DeleteTokenResponseObject.builder().value(token).build()); + } + V1FlowDeleteTokenResponse body = V1FlowDeleteTokenResponse.builder() + .tokens(responseRecords).build(); + return new ApiClientHttpResponse<>(body, buildOkHttpResponse()); + }); + } + + /** Echoes each requested tokenize value back with one token per requested group name. */ + private static void stubTokenizeEcho(RawFlowserviceClient mockRaw) { + when(mockRaw.tokenize(any(), any())).thenAnswer(invocation -> { + com.skyflow.generated.rest.resources.flowservice.requests.V1FlowTokenizeRequest req = + invocation.getArgument(0); + List responseRecords = new ArrayList<>(); + for (com.skyflow.generated.rest.types.V1FlowTokenizeRequestObject obj : req.getData().get()) { + FlowTokenizeResponseObjectToken token = FlowTokenizeResponseObjectToken.builder() + .tokenGroupName("group1") + .token("tok-" + obj.getValue().get()) + .build(); + responseRecords.add(V1FlowTokenizeResponseObject.builder() + .value(obj.getValue().get()) + .tokens(Collections.singletonList(token)) + .build()); + } + V1FlowTokenizeResponse body = V1FlowTokenizeResponse.builder().response(responseRecords).build(); + return new ApiClientHttpResponse<>(body, buildOkHttpResponse()); + }); + } + + // ── bulk insert: batch dispatch fidelity ───────────────────────────────── + + @Test + public void testBulkInsert_tableNameAndVaultIdReAppliedOnEveryBatch() throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + RawFlowserviceClient mockRaw = mockRawFlowservice(mockApi); + stubInsertEcho(mockRaw); + + VaultController controller = createControllerWithMock(mockApi); + BulkInsertRequest request = BulkInsertRequest.builder() + .tableName("cards") + .records(multiBatchInsertRecords()) + .build(); + + controller.bulkInsert(request); + + ArgumentCaptor captor = + ArgumentCaptor.forClass(com.skyflow.generated.rest.resources.flowservice.requests.V1InsertRequest.class); + Mockito.verify(mockRaw, Mockito.times(EXPECTED_BATCH_COUNT)).insert(captor.capture(), any()); + + int expectedPos = 0; + for (com.skyflow.generated.rest.resources.flowservice.requests.V1InsertRequest sent : captor.getAllValues()) { + // insertBatch rebuilds the request per batch, so tableName/vaultId must be re-applied. + Assert.assertEquals("cards", sent.getTableName().get()); + Assert.assertEquals("vault123", sent.getVaultId().get()); + for (com.skyflow.generated.rest.types.V1InsertRecordData record : sent.getRecords().get()) { + // The name rides the envelope only — duplicating it per record is rejected by the vault. + Assert.assertFalse(record.getTableName().isPresent()); + Assert.assertEquals(String.valueOf(expectedPos), record.getData().get().get("pos")); + expectedPos++; + } + } + Assert.assertEquals(MULTI_BATCH_ITEM_COUNT, expectedPos); + } + + @Test + public void testBulkInsert_responseIndexMapsToOriginalInputPosition_acrossBatches() throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + RawFlowserviceClient mockRaw = mockRawFlowservice(mockApi); + stubInsertEcho(mockRaw); + + VaultController controller = createControllerWithMock(mockApi); + BulkInsertRequest request = BulkInsertRequest.builder() + .tableName("cards") + .records(multiBatchInsertRecords()) + .build(); + + BulkInsertResponse response = controller.bulkInsert(request); + + Assert.assertEquals(MULTI_BATCH_ITEM_COUNT, response.getRecords().size()); + for (int i = 0; i < MULTI_BATCH_ITEM_COUNT; i++) { + BulkInsertResponseRecord record = response.getRecords().get(i); + Assert.assertEquals(i, record.getIndex()); + // skyflowId encodes the input record's position, so this proves index -> input position. + Assert.assertEquals("sky-" + i, record.getSkyflowId()); + } + } + + @Test + public void testBulkInsert_interceptorInvokedOncePerBatchWithDistinctContext() throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + RawFlowserviceClient mockRaw = mockRawFlowservice(mockApi); + stubInsertEcho(mockRaw); + + VaultController controller = createControllerWithMock(mockApi); + BulkInsertRequest request = BulkInsertRequest.builder() + .tableName("cards") + .records(multiBatchInsertRecords()) + .build(); + + CountingInterceptor interceptor = new CountingInterceptor(); + controller.bulkInsert(request, BulkInsertOptions.builder().interceptor(interceptor).build()); + + ArgumentCaptor captor = ArgumentCaptor.forClass(RequestOptions.class); + Mockito.verify(mockRaw, Mockito.times(EXPECTED_BATCH_COUNT)).insert(any(), captor.capture()); + assertInterceptorRanOncePerBatch(interceptor, captor.getAllValues()); + } + + // ── bulk detokenize: batch dispatch fidelity ───────────────────────────── + + @Test + public void testBulkDetokenize_vaultIdAndRedactionsReachEveryBatchAndTokenOrderPreserved() throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + RawFlowserviceClient mockRaw = mockRawFlowservice(mockApi); + stubDetokenizeEcho(mockRaw); + + VaultController controller = createControllerWithMock(mockApi); + List tokens = multiBatchTokens(); + BulkDetokenizeRequest request = BulkDetokenizeRequest.builder() + .tokens(tokens) + .tokenGroupRedactions(Collections.singletonList( + TokenGroupRedactions.builder().tokenGroupName("group one").redaction("MASKED").build())) + .build(); + + controller.bulkDetokenize(request); + + ArgumentCaptor captor = + ArgumentCaptor.forClass(com.skyflow.generated.rest.resources.flowservice.requests.V1FlowDetokenizeRequest.class); + Mockito.verify(mockRaw, Mockito.times(EXPECTED_BATCH_COUNT)).detokenize(captor.capture(), any()); + + List flattened = new ArrayList<>(); + for (com.skyflow.generated.rest.resources.flowservice.requests.V1FlowDetokenizeRequest sent : captor.getAllValues()) { + Assert.assertEquals("vault123", sent.getVaultId().get()); + Assert.assertTrue(sent.getTokenGroupRedactions().isPresent()); + Assert.assertEquals("group one", sent.getTokenGroupRedactions().get().get(0).getTokenGroupName().get()); + Assert.assertEquals("MASKED", sent.getTokenGroupRedactions().get().get(0).getRedaction().get()); + flattened.addAll(sent.getTokens().get()); + } + Assert.assertEquals(tokens, flattened); + } + + @Test + public void testBulkDetokenize_responseIndexMapsToOriginalInputPosition_acrossBatches() throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + RawFlowserviceClient mockRaw = mockRawFlowservice(mockApi); + stubDetokenizeEcho(mockRaw); + + VaultController controller = createControllerWithMock(mockApi); + List tokens = multiBatchTokens(); + BulkDetokenizeRequest request = BulkDetokenizeRequest.builder().tokens(tokens).build(); + + BulkDetokenizeResponse response = controller.bulkDetokenize(request); + + Assert.assertEquals(MULTI_BATCH_ITEM_COUNT, response.getRecords().size()); + for (int i = 0; i < MULTI_BATCH_ITEM_COUNT; i++) { + Assert.assertEquals(i, response.getRecords().get(i).getIndex()); + Assert.assertEquals(tokens.get(i), response.getRecords().get(i).getToken()); + } + } + + @Test + public void testBulkDetokenize_interceptorInvokedOncePerBatchWithDistinctContext() throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + RawFlowserviceClient mockRaw = mockRawFlowservice(mockApi); + stubDetokenizeEcho(mockRaw); + + VaultController controller = createControllerWithMock(mockApi); + BulkDetokenizeRequest request = BulkDetokenizeRequest.builder().tokens(multiBatchTokens()).build(); + + CountingInterceptor interceptor = new CountingInterceptor(); + controller.bulkDetokenize(request, BulkDetokenizeOptions.builder().interceptor(interceptor).build()); + + ArgumentCaptor captor = ArgumentCaptor.forClass(RequestOptions.class); + Mockito.verify(mockRaw, Mockito.times(EXPECTED_BATCH_COUNT)).detokenize(any(), captor.capture()); + assertInterceptorRanOncePerBatch(interceptor, captor.getAllValues()); + } + + // ── bulk delete tokens: batch dispatch fidelity ────────────────────────── + + @Test + public void testBulkDeleteTokens_vaultIdOnEveryBatchAndTokenOrderPreserved() throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + RawFlowserviceClient mockRaw = mockRawFlowservice(mockApi); + stubDeleteTokensEcho(mockRaw); + + VaultController controller = createControllerWithMock(mockApi); + List tokens = multiBatchTokens(); + BulkDeleteTokensRequest request = BulkDeleteTokensRequest.builder().tokens(tokens).build(); + + BulkDeleteTokensResponse response = controller.bulkDeleteTokens(request); + + ArgumentCaptor captor = + ArgumentCaptor.forClass(com.skyflow.generated.rest.resources.flowservice.requests.V1FlowDeleteTokenRequest.class); + Mockito.verify(mockRaw, Mockito.times(EXPECTED_BATCH_COUNT)).deletetoken(captor.capture(), any()); + + List flattened = new ArrayList<>(); + for (com.skyflow.generated.rest.resources.flowservice.requests.V1FlowDeleteTokenRequest sent : captor.getAllValues()) { + Assert.assertEquals("vault123", sent.getVaultId().get()); + flattened.addAll(sent.getTokens().get()); + } + Assert.assertEquals(tokens, flattened); + + Assert.assertEquals(MULTI_BATCH_ITEM_COUNT, response.getRecords().size()); + for (int i = 0; i < MULTI_BATCH_ITEM_COUNT; i++) { + Assert.assertEquals(i, response.getRecords().get(i).getIndex()); + Assert.assertEquals(tokens.get(i), response.getRecords().get(i).getToken()); + } + } + + @Test + public void testBulkDeleteTokens_interceptorInvokedOncePerBatchWithDistinctContext() throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + RawFlowserviceClient mockRaw = mockRawFlowservice(mockApi); + stubDeleteTokensEcho(mockRaw); + + VaultController controller = createControllerWithMock(mockApi); + BulkDeleteTokensRequest request = BulkDeleteTokensRequest.builder().tokens(multiBatchTokens()).build(); + + CountingInterceptor interceptor = new CountingInterceptor(); + controller.bulkDeleteTokens(request, BulkDeleteTokensOptions.builder().interceptor(interceptor).build()); + + ArgumentCaptor captor = ArgumentCaptor.forClass(RequestOptions.class); + Mockito.verify(mockRaw, Mockito.times(EXPECTED_BATCH_COUNT)).deletetoken(any(), captor.capture()); + assertInterceptorRanOncePerBatch(interceptor, captor.getAllValues()); + } + + // ── bulk tokenize: batch dispatch fidelity ─────────────────────────────── + + @Test + public void testBulkTokenize_vaultIdOnEveryBatchAndValueOrderPreserved() throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + RawFlowserviceClient mockRaw = mockRawFlowservice(mockApi); + stubTokenizeEcho(mockRaw); + + VaultController controller = createControllerWithMock(mockApi); + ArrayList records = new ArrayList<>(); + for (int i = 0; i < MULTI_BATCH_ITEM_COUNT; i++) { + records.add(BulkTokenizeRequestRecord.builder() + .value("value-" + i) + .tokenGroupNames(Collections.singletonList("group1")) + .build()); + } + BulkTokenizeRequest request = BulkTokenizeRequest.builder().records(records).build(); + + controller.bulkTokenize(request); + + ArgumentCaptor captor = + ArgumentCaptor.forClass(com.skyflow.generated.rest.resources.flowservice.requests.V1FlowTokenizeRequest.class); + Mockito.verify(mockRaw, Mockito.times(EXPECTED_BATCH_COUNT)).tokenize(captor.capture(), any()); + + List flattened = new ArrayList<>(); + for (com.skyflow.generated.rest.resources.flowservice.requests.V1FlowTokenizeRequest sent : captor.getAllValues()) { + Assert.assertEquals("vault123", sent.getVaultId().get()); + for (com.skyflow.generated.rest.types.V1FlowTokenizeRequestObject obj : sent.getData().get()) { + Assert.assertEquals(Collections.singletonList("group1"), obj.getTokenGroupNames().get()); + flattened.add(obj.getValue().get()); + } + } + Assert.assertEquals(MULTI_BATCH_ITEM_COUNT, flattened.size()); + for (int i = 0; i < MULTI_BATCH_ITEM_COUNT; i++) { + Assert.assertEquals("value-" + i, flattened.get(i)); + } + } + + @Test + public void testBulkTokenize_interceptorInvokedOncePerBatchWithDistinctContext() throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + RawFlowserviceClient mockRaw = mockRawFlowservice(mockApi); + stubTokenizeEcho(mockRaw); + + VaultController controller = createControllerWithMock(mockApi); + ArrayList records = new ArrayList<>(); + for (int i = 0; i < MULTI_BATCH_ITEM_COUNT; i++) { + records.add(BulkTokenizeRequestRecord.builder().value("value-" + i).build()); + } + BulkTokenizeRequest request = BulkTokenizeRequest.builder().records(records).build(); + + CountingInterceptor interceptor = new CountingInterceptor(); + controller.bulkTokenize(request, BulkTokenizeOptions.builder().interceptor(interceptor).build()); + + ArgumentCaptor captor = ArgumentCaptor.forClass(RequestOptions.class); + Mockito.verify(mockRaw, Mockito.times(EXPECTED_BATCH_COUNT)).tokenize(any(), captor.capture()); + assertInterceptorRanOncePerBatch(interceptor, captor.getAllValues()); + } +} diff --git a/flowvault/src/test/java/com/skyflow/vault/data/BulkResponseTests.java b/flowvault/src/test/java/com/skyflow/vault/data/BulkResponseTests.java new file mode 100644 index 00000000..fc6b4e65 --- /dev/null +++ b/flowvault/src/test/java/com/skyflow/vault/data/BulkResponseTests.java @@ -0,0 +1,538 @@ +package com.skyflow.vault.data; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Tests for the Bulk*Response classes: {@link BulkInsertResponse}, {@link BulkDetokenizeResponse}, + * {@link BulkDeleteTokensResponse} and {@link BulkTokenizeResponse}. + * + *

BulkDeleteTokensResponse/BulkTokenizeResponse each have a 2-arg constructor (success/errors + * only, summary and originalPayload-derived fields stay null) and a 3-arg constructor (adds + * originalPayload and computes a summary). BulkInsertResponse and BulkDetokenizeResponse instead + * carry a single unified {@code records} list, with a 1-arg (per-batch) and a 2-arg (final, + * summary-computing) constructor. Both additionally derive a retry list from the originalPayload, + * filtering on a "5xx except 529" retryable-status rule. + */ +public class BulkResponseTests { + + // ── BulkInsertResponse ─────────────────────────────────────────────────── + + @Test + public void testBulkInsertResponse_oneArgConstructorLeavesSummaryAndRetryDependenciesNull() { + List records = Collections.emptyList(); + + BulkInsertResponse response = new BulkInsertResponse(records); + + Assert.assertNull(response.getSummary()); + Assert.assertEquals(records, response.getRecords()); + // No failed records, so the retry list is empty and originalPayload (null) is never dereferenced. + Assert.assertTrue(response.getRecordsToRetry().isEmpty()); + } + + @Test + public void testBulkInsertResponse_twoArgConstructorComputesSummary() { + List records = Arrays.asList( + new BulkInsertResponseRecord(0, "table1", "id-1", null, null, 200, null, null), + new BulkInsertResponseRecord(1, null, null, null, null, 400, "failed", null)); + List originalPayload = new ArrayList<>(Arrays.asList( + BulkInsertRequestRecord.builder().tableName("table1").build(), + BulkInsertRequestRecord.builder().tableName("table1").build())); + + BulkInsertResponse response = new BulkInsertResponse(records, originalPayload); + + Assert.assertNotNull(response.getSummary()); + Assert.assertEquals(2, response.getSummary().getTotalRecords()); + Assert.assertEquals(1, response.getSummary().getTotalInserted()); + Assert.assertEquals(1, response.getSummary().getTotalFailed()); + } + + @Test + public void testBulkInsertResponse_recordsPreserveIndexAndInheritedFields() { + Map fields = new HashMap<>(); + fields.put("name", "token-name"); + Map hashedData = new HashMap<>(); + hashedData.put("name", "hashed-name"); + + BulkInsertResponseRecord record = new BulkInsertResponseRecord( + 7, "table1", "id-1", fields, hashedData, 200, null, null); + + BulkInsertResponse response = new BulkInsertResponse(Collections.singletonList(record)); + + BulkInsertResponseRecord actual = response.getRecords().get(0); + Assert.assertEquals(7, actual.getIndex()); + Assert.assertEquals("table1", actual.getTableName()); + Assert.assertEquals("id-1", actual.getSkyflowId()); + Assert.assertEquals(fields, actual.getFields()); + Assert.assertEquals(hashedData, actual.getHashedData()); + Assert.assertEquals(200, actual.getHttpCode()); + Assert.assertNull(actual.getError()); + } + + @Test + public void testBulkInsertResponse_getRecordsToRetryFiltersRetryableStatusCodesOnly() { + BulkInsertRequestRecord record0 = BulkInsertRequestRecord.builder().tableName("t0").build(); + BulkInsertRequestRecord record1 = BulkInsertRequestRecord.builder().tableName("t1").build(); + BulkInsertRequestRecord record2 = BulkInsertRequestRecord.builder().tableName("t2").build(); + BulkInsertRequestRecord record3 = BulkInsertRequestRecord.builder().tableName("t3").build(); + List originalPayload = new ArrayList<>( + Arrays.asList(record0, record1, record2, record3)); + + List records = Arrays.asList( + new BulkInsertResponseRecord(0, null, null, null, null, 500, "server error", null), // retryable (lower bound) + new BulkInsertResponseRecord(1, null, null, null, null, 400, "bad request", null), // not retryable + new BulkInsertResponseRecord(2, null, null, null, null, 599, "server error", null), // retryable (upper bound) + new BulkInsertResponseRecord(3, null, null, null, null, 529, "special case", null)); // explicitly excluded + + BulkInsertResponse response = new BulkInsertResponse(records, originalPayload); + + List recordsToRetry = response.getRecordsToRetry(); + + Assert.assertEquals(2, recordsToRetry.size()); + Assert.assertTrue(recordsToRetry.contains(record0)); + Assert.assertTrue(recordsToRetry.contains(record2)); + Assert.assertFalse(recordsToRetry.contains(record1)); + Assert.assertFalse(recordsToRetry.contains(record3)); + } + + @Test + public void testBulkInsertResponse_toStringNotNull() { + BulkInsertResponse response = new BulkInsertResponse(Collections.emptyList()); + Assert.assertNotNull(response.toString()); + } + + @Test + public void testBulkInsertResponse_toStringSerializesSummaryAndRecordsButNotInternals() { + List records = Collections.singletonList( + new BulkInsertResponseRecord(0, "table1", "id-1", null, null, 200, null, null)); + List originalPayload = new ArrayList( + Collections.singletonList(BulkInsertRequestRecord.builder().tableName("table1").build())); + + BulkInsertResponse response = new BulkInsertResponse(records, originalPayload); + // Populate the lazily-derived internal so we can prove it is still excluded. + response.getRecordsToRetry(); + String json = response.toString(); + + Assert.assertTrue(json.contains("summary")); + Assert.assertTrue(json.contains("records")); + // serializeNulls() spells out the nulls on each record. + Assert.assertTrue(json.contains("\"error\":null")); + // transient internals stay out of the JSON. + Assert.assertFalse(json.contains("originalPayload")); + Assert.assertFalse(json.contains("recordsToRetry")); + } + + @Test + public void testBulkInsertResponse_getRecordsToRetryOnPerBatchResponseDoesNotThrow() { + // The 1-arg constructor leaves originalPayload null. A 5xx record must not NPE here. + List records = Collections.singletonList( + new BulkInsertResponseRecord(0, null, null, null, null, 500, "server error", null)); + + BulkInsertResponse response = new BulkInsertResponse(records); + + Assert.assertTrue(response.getRecordsToRetry().isEmpty()); + } + + // ── BulkDetokenizeResponse ─────────────────────────────────────────────── + + @Test + public void testBulkDetokenizeResponse_oneArgConstructorLeavesSummaryAndRetryDependenciesNull() { + List records = Collections.emptyList(); + + BulkDetokenizeResponse response = new BulkDetokenizeResponse(records); + + Assert.assertNull(response.getSummary()); + Assert.assertEquals(records, response.getRecords()); + // No retryable records, so originalPayload (null) is never dereferenced. + Assert.assertTrue(response.getTokensToRetry().isEmpty()); + } + + @Test + public void testBulkDetokenizeResponse_twoArgConstructorComputesSummary() { + List records = Arrays.asList( + new BulkDetokenizeResponseRecord(0, "tok-1", "secret-value", "group1", null, 200, null, null), + new BulkDetokenizeResponseRecord(1, "tok-2", null, null, null, 404, "failed", null)); + List originalPayload = Arrays.asList("tok-1", "tok-2"); + + BulkDetokenizeResponse response = new BulkDetokenizeResponse(records, originalPayload); + + Assert.assertNotNull(response.getSummary()); + Assert.assertEquals(2, response.getSummary().getTotalTokens()); + Assert.assertEquals(1, response.getSummary().getTotalDetokenized()); + Assert.assertEquals(1, response.getSummary().getTotalFailed()); + Assert.assertEquals(records, response.getRecords()); + } + + @Test + public void testBulkDetokenizeResponse_summaryTotalTokensComesFromOriginalPayloadNotRecords() { + // Only one of the three submitted tokens came back, so totalTokens tracks the payload size. + List records = Collections.singletonList( + new BulkDetokenizeResponseRecord(0, "tok-0", "plain-0", "group1", null, 200, null, null)); + List originalPayload = Arrays.asList("tok-0", "tok-1", "tok-2"); + + BulkDetokenizeResponse response = new BulkDetokenizeResponse(records, originalPayload); + + Assert.assertEquals(3, response.getSummary().getTotalTokens()); + Assert.assertEquals(1, response.getSummary().getTotalDetokenized()); + Assert.assertEquals(0, response.getSummary().getTotalFailed()); + } + + @Test + public void testBulkDetokenizeResponse_getTokensToRetryFiltersRetryableStatusCodesOnly() { + List originalPayload = Arrays.asList("tok-0", "tok-1", "tok-2", "tok-3"); + List records = Arrays.asList( + new BulkDetokenizeResponseRecord(0, null, null, null, null, 500, "server error", null), + new BulkDetokenizeResponseRecord(1, null, null, null, null, 400, "bad request", null), + new BulkDetokenizeResponseRecord(2, null, null, null, null, 599, "server error", null), + new BulkDetokenizeResponseRecord(3, null, null, null, null, 529, "special case", null)); + + BulkDetokenizeResponse response = new BulkDetokenizeResponse(records, originalPayload); + + List tokensToRetry = response.getTokensToRetry(); + + Assert.assertEquals(2, tokensToRetry.size()); + Assert.assertTrue(tokensToRetry.contains("tok-0")); + Assert.assertTrue(tokensToRetry.contains("tok-2")); + Assert.assertFalse(tokensToRetry.contains("tok-1")); + Assert.assertFalse(tokensToRetry.contains("tok-3")); + } + + @Test + public void testBulkDetokenizeResponse_toStringNotNull() { + BulkDetokenizeResponse response = new BulkDetokenizeResponse(Collections.emptyList()); + Assert.assertNotNull(response.toString()); + } + + @Test + public void testBulkDetokenizeResponse_toStringSerializesSummaryAndRecordsButNotInternals() { + List records = Collections.singletonList( + new BulkDetokenizeResponseRecord(0, "tok-0", "plain-0", "group1", null, 200, null, null)); + List originalPayload = Collections.singletonList("tok-0"); + + BulkDetokenizeResponse response = new BulkDetokenizeResponse(records, originalPayload); + // Populate the lazily-derived internal so we can prove it is still excluded. + response.getTokensToRetry(); + String json = response.toString(); + + Assert.assertTrue(json.contains("summary")); + Assert.assertTrue(json.contains("records")); + // serializeNulls() spells out the nulls on each record. + Assert.assertTrue(json.contains("\"error\":null")); + // transient internals stay out of the JSON. + Assert.assertFalse(json.contains("originalPayload")); + Assert.assertFalse(json.contains("tokensToRetry")); + } + + @Test + public void testBulkDetokenizeResponse_getTokensToRetryOnPerBatchResponseDoesNotThrow() { + List records = Collections.singletonList( + new BulkDetokenizeResponseRecord(0, "tok-0", null, null, null, 500, "server error", null)); + + BulkDetokenizeResponse response = new BulkDetokenizeResponse(records); + + Assert.assertTrue(response.getTokensToRetry().isEmpty()); + } + + @Test + public void testBulkDetokenizeResponse_recordsExposeDetokenizedValue() { + // value is passed through verbatim from the generated response object. + List records = Collections.singletonList( + new BulkDetokenizeResponseRecord(0, "tok-0", "john@example.com", "group1", null, 200, null, null)); + + BulkDetokenizeResponse response = new BulkDetokenizeResponse(records); + + Assert.assertEquals("john@example.com", response.getRecords().get(0).getValue()); + } + + // ── BulkDeleteTokensResponse ───────────────────────────────────────────── + + @Test + public void testBulkDeleteTokensResponse_singleArgConstructorLeavesSummaryNull() { + List records = Collections.emptyList(); + + BulkDeleteTokensResponse response = new BulkDeleteTokensResponse(records); + + Assert.assertNull(response.getSummary()); + Assert.assertEquals(records, response.getRecords()); + } + + @Test + public void testBulkDeleteTokensResponse_twoArgConstructorComputesSummary() { + List records = Arrays.asList( + new BulkDeleteTokensResponseRecord(0, "tok-1", 200, null), + new BulkDeleteTokensResponseRecord(1, "tok-2", 404, "Token not found")); + List originalPayload = Arrays.asList("tok-1", "tok-2"); + + BulkDeleteTokensResponse response = new BulkDeleteTokensResponse(records, originalPayload); + + Assert.assertNotNull(response.getSummary()); + Assert.assertEquals(2, response.getSummary().getTotalTokens()); + Assert.assertEquals(1, response.getSummary().getTotalDeleted()); + Assert.assertEquals(1, response.getSummary().getTotalFailed()); + Assert.assertEquals(records, response.getRecords()); + } + + @Test + public void testBulkDeleteTokensResponse_getTokensToRetryFiltersRetryableStatusCodesOnly() { + List records = Arrays.asList( + new BulkDeleteTokensResponseRecord(0, "tok-0", 500, "server error"), // retryable (lower bound) + new BulkDeleteTokensResponseRecord(1, "tok-1", 404, "not found"), // not retryable + new BulkDeleteTokensResponseRecord(2, "tok-2", 599, "server error"), // retryable (upper bound) + new BulkDeleteTokensResponseRecord(3, "tok-3", 529, "special case"), // explicitly excluded + new BulkDeleteTokensResponseRecord(4, "tok-4", 200, null)); // succeeded + + BulkDeleteTokensResponse response = new BulkDeleteTokensResponse( + records, Arrays.asList("tok-0", "tok-1", "tok-2", "tok-3", "tok-4")); + + List tokensToRetry = response.getTokensToRetry(); + + Assert.assertEquals(2, tokensToRetry.size()); + Assert.assertTrue(tokensToRetry.contains("tok-0")); + Assert.assertTrue(tokensToRetry.contains("tok-2")); + Assert.assertFalse(tokensToRetry.contains("tok-1")); + Assert.assertFalse(tokensToRetry.contains("tok-3")); + Assert.assertFalse(tokensToRetry.contains("tok-4")); + } + + @Test + public void testBulkDeleteTokensResponse_getTokensToRetryEmptyWhenAllSucceeded() { + List records = Collections.singletonList( + new BulkDeleteTokensResponseRecord(0, "tok-0", 200, null)); + + BulkDeleteTokensResponse response = + new BulkDeleteTokensResponse(records, Collections.singletonList("tok-0")); + + Assert.assertTrue(response.getTokensToRetry().isEmpty()); + } + + @Test + public void testBulkDeleteTokensResponse_tokensToRetryNotSerialized() { + List records = Collections.singletonList( + new BulkDeleteTokensResponseRecord(0, "tok-0", 500, "server error")); + BulkDeleteTokensResponse response = + new BulkDeleteTokensResponse(records, Collections.singletonList("tok-0")); + + response.getTokensToRetry(); // populate the lazily-derived field + + Assert.assertFalse(response.toString().contains("tokensToRetry")); + } + + @Test + public void testBulkDeleteTokensResponse_toStringMatchesContractShape() { + List records = Arrays.asList( + new BulkDeleteTokensResponseRecord(0, "a1b2c3d4", 200, null), + new BulkDeleteTokensResponseRecord(1, "z9y8x7", 404, "Token not found")); + BulkDeleteTokensResponse response = + new BulkDeleteTokensResponse(records, Arrays.asList("a1b2c3d4", "z9y8x7")); + + String json = response.toString(); + + Assert.assertNotNull(json); + // summary + records only; originalPayload is internal and must never be serialized + Assert.assertTrue(json.contains("\"summary\"")); + Assert.assertTrue(json.contains("\"records\"")); + Assert.assertFalse(json.contains("originalPayload")); + // nulls are serialized so a success record still reports its error field + Assert.assertTrue(json.contains("\"error\":null")); + Assert.assertTrue(json.contains("\"index\":0")); + Assert.assertTrue(json.contains("\"httpCode\":200")); + } + + // ── BulkTokenizeResponse ───────────────────────────────────────────────── + + private static BulkTokenizeResponseRecord tokenizeRecord(int index, Object value, TokenizeResponseToken... tokens) { + return new BulkTokenizeResponseRecord(index, value, Arrays.asList(tokens)); + } + + private static TokenizeResponseToken okToken(String group, String token) { + return new TokenizeResponseToken(group, token, 200, null); + } + + private static TokenizeResponseToken failedToken(String group, String error) { + return new TokenizeResponseToken(group, null, 400, error); + } + + private static List payloadOf(int size) { + List payload = new ArrayList<>(); + for (int i = 0; i < size; i++) { + payload.add(BulkTokenizeRequestRecord.builder().value("v" + i).build()); + } + return payload; + } + + @Test + public void testBulkTokenizeResponse_singleArgConstructorLeavesSummaryNull() { + List records = Collections.emptyList(); + + BulkTokenizeResponse response = new BulkTokenizeResponse(records); + + Assert.assertNull(response.getSummary()); + Assert.assertEquals(records, response.getRecords()); + } + + @Test + public void testBulkTokenizeResponse_twoArgConstructorClassifiesEachValue() { + // index 0: all groups ok -> totalTokenized + // index 1: some ok, some failed -> totalPartial + // index 2: all groups failed -> totalFailed + List records = Arrays.asList( + tokenizeRecord(0, "v0", okToken("g1", "tok-0")), + tokenizeRecord(1, "v1", okToken("g1", "tok-1"), failedToken("g2", "partial failure")), + tokenizeRecord(2, "v2", failedToken("g1", "full failure"))); + + BulkTokenizeResponse response = new BulkTokenizeResponse(records, payloadOf(3)); + + TokenizeSummary summary = response.getSummary(); + Assert.assertNotNull(summary); + // totalTokens counts input values submitted, not output token entries + Assert.assertEquals(3, summary.getTotalTokens()); + Assert.assertEquals(1, summary.getTotalTokenized()); + Assert.assertEquals(1, summary.getTotalPartial()); + Assert.assertEquals(1, summary.getTotalFailed()); + } + + @Test + public void testBulkTokenizeResponse_recordWithNoTokensCountsAsFailed() { + List records = Collections.singletonList( + new BulkTokenizeResponseRecord(0, "v0", Collections.emptyList())); + + BulkTokenizeResponse response = new BulkTokenizeResponse(records, payloadOf(1)); + + Assert.assertEquals(0, response.getSummary().getTotalTokenized()); + Assert.assertEquals(1, response.getSummary().getTotalFailed()); + } + + @Test + public void testBulkTokenizeResponse_getRecordsToRetryReturnsCallerRecordUnchanged() { + // one value, four groups: only the 503 is retryable, but the whole record comes back + List records = Collections.singletonList( + tokenizeRecord(0, "9999999999", + okToken("phone_group", "p1q2r3s4"), + new TokenizeResponseToken("phone_group_2", null, 503, "unavailable"), + new TokenizeResponseToken("phone_group_3", null, 400, "bad group"), + new TokenizeResponseToken("phone_group_4", null, 529, "special case"))); + BulkTokenizeRequestRecord requested = BulkTokenizeRequestRecord.builder().value("9999999999") + .tokenGroupNames(Arrays.asList( + "phone_group", "phone_group_2", "phone_group_3", "phone_group_4")) + .build(); + + List retry = + new BulkTokenizeResponse(records, Collections.singletonList(requested)).getRecordsToRetry(); + + Assert.assertEquals(1, retry.size()); + // the caller's own object is handed straight back, groups and all + Assert.assertSame(requested, retry.get(0)); + Assert.assertEquals("9999999999", retry.get(0).getValue()); + Assert.assertEquals(Arrays.asList( + "phone_group", "phone_group_2", "phone_group_3", "phone_group_4"), + retry.get(0).getTokenGroupNames()); + } + + @Test + public void testBulkTokenizeResponse_getRecordsToRetryOmitsValuesWithoutRetryableFailures() { + List records = Arrays.asList( + tokenizeRecord(0, "v0", okToken("g1", "tok-0")), + tokenizeRecord(1, "v1", failedToken("g1", "bad group")), // 400, not retryable + tokenizeRecord(2, "v2", new TokenizeResponseToken("g1", null, 500, "server error"))); + + List retry = + new BulkTokenizeResponse(records, payloadOf(3)).getRecordsToRetry(); + + Assert.assertEquals(1, retry.size()); + Assert.assertEquals("v2", retry.get(0).getValue()); + } + + @Test + public void testBulkTokenizeResponse_getRecordsToRetryLooksUpByIndexNotResponseOrder() { + // batches finish out of order, so the failing record is not first in the response + List records = Arrays.asList( + tokenizeRecord(2, "v2", new TokenizeResponseToken("g1", null, 500, "server error")), + tokenizeRecord(0, "v0", okToken("g1", "tok-0"))); + + List retry = + new BulkTokenizeResponse(records, payloadOf(3)).getRecordsToRetry(); + + Assert.assertEquals(1, retry.size()); + Assert.assertEquals("v2", retry.get(0).getValue()); + } + + @Test + public void testBulkTokenizeResponse_getRecordsToRetrySkipsIndexOutsidePayload() { + // a malformed index must not blow up with IndexOutOfBoundsException + List records = Arrays.asList( + tokenizeRecord(9, "stray", new TokenizeResponseToken("g1", null, 500, "server error")), + tokenizeRecord(-1, "stray", new TokenizeResponseToken("g1", null, 500, "server error")), + tokenizeRecord(0, "v0", new TokenizeResponseToken("g1", null, 500, "server error"))); + + List retry = + new BulkTokenizeResponse(records, payloadOf(1)).getRecordsToRetry(); + + Assert.assertEquals(1, retry.size()); + Assert.assertEquals("v0", retry.get(0).getValue()); + } + + @Test + public void testBulkTokenizeResponse_getRecordsToRetryCarriesByotToken() { + List records = Collections.singletonList( + tokenizeRecord(0, "v0", new TokenizeResponseToken("g1", null, 500, "server error"))); + List payload = Collections.singletonList( + BulkTokenizeRequestRecord.builder().value("v0").token("my-own-token") + .tokenGroupNames(Collections.singletonList("g1")).build()); + + List retry = + new BulkTokenizeResponse(records, payload).getRecordsToRetry(); + + Assert.assertEquals("my-own-token", retry.get(0).getToken()); + } + + @Test + public void testBulkTokenizeResponse_getRecordsToRetryKeepsRequestedGroupsOnBatchFailure() { + // a batch-level failure reports no group name on the token entry + List records = Collections.singletonList( + tokenizeRecord(0, "v0", new TokenizeResponseToken(null, null, 500, "server error"))); + List payload = Collections.singletonList( + BulkTokenizeRequestRecord.builder().value("v0") + .tokenGroupNames(Arrays.asList("g1", "g2")).build()); + + List retry = + new BulkTokenizeResponse(records, payload).getRecordsToRetry(); + + Assert.assertEquals(Arrays.asList("g1", "g2"), retry.get(0).getTokenGroupNames()); + } + + @Test + public void testBulkTokenizeResponse_recordsToRetryNotSerialized() { + List records = Collections.singletonList( + tokenizeRecord(0, "v0", new TokenizeResponseToken("g1", null, 500, "server error"))); + BulkTokenizeResponse response = new BulkTokenizeResponse(records, payloadOf(1)); + + response.getRecordsToRetry(); // populate the lazily-derived field + + Assert.assertFalse(response.toString().contains("recordsToRetry")); + } + + @Test + public void testBulkTokenizeResponse_toStringMatchesContractShape() { + List records = Arrays.asList( + tokenizeRecord(0, "john@example.com", okToken("email_group", "a1b2c3d4")), + tokenizeRecord(1, "9999999999", + okToken("phone_group", "p1q2r3s4"), + failedToken("phone_group_2", "Invalid token group configuration"))); + + String json = new BulkTokenizeResponse(records, payloadOf(2)).toString(); + + Assert.assertTrue(json.contains("\"summary\"")); + Assert.assertTrue(json.contains("\"records\"")); + Assert.assertFalse(json.contains("originalPayload")); + Assert.assertTrue(json.contains("\"error\":null")); + Assert.assertTrue(json.contains("\"index\":0")); + Assert.assertTrue(json.contains("\"tokenGroupName\":\"email_group\"")); + } +} diff --git a/flowvault/src/test/java/com/skyflow/vault/data/BulkRetryAndSummaryTests.java b/flowvault/src/test/java/com/skyflow/vault/data/BulkRetryAndSummaryTests.java new file mode 100644 index 00000000..d8a1437a --- /dev/null +++ b/flowvault/src/test/java/com/skyflow/vault/data/BulkRetryAndSummaryTests.java @@ -0,0 +1,322 @@ +package com.skyflow.vault.data; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * Summary classification and retry-list derivation on the bulk responses. + * + *

Retryable is defined as "has an error AND a 5xx status other than 529", so each conjunct is + * exercised separately — a 4xx failure, a 5xx with no error, a null status, and the 529 carve-out + * must all stay out of the retry list. + */ +public class BulkRetryAndSummaryTests { + + private static TokenizeResponseToken token(String group, String value, Integer httpCode, String error) { + return new TokenizeResponseToken(group, value, httpCode, error); + } + + private static BulkTokenizeResponseRecord tokenizeRecord(int index, TokenizeResponseToken... tokens) { + return new BulkTokenizeResponseRecord(index, "value" + index, Arrays.asList(tokens)); + } + + private static BulkTokenizeRequestRecord requestRecord(String value) { + return (BulkTokenizeRequestRecord) BulkTokenizeRequestRecord.builder().value(value).build(); + } + + // ── BulkTokenizeResponse.buildSummary ───────────────────────────────────── + + @Test + public void testTokenizeSummary_allGroupsSucceededCountsAsTokenized() { + List records = Collections.singletonList( + tokenizeRecord(0, token("g1", "t1", 200, null), token("g2", "t2", 200, null))); + + TokenizeSummary summary = new BulkTokenizeResponse(records, Collections.singletonList(requestRecord("a"))) + .getSummary(); + + Assert.assertEquals(1, summary.getTotalTokens()); + Assert.assertEquals(1, summary.getTotalTokenized()); + Assert.assertEquals(0, summary.getTotalPartial()); + Assert.assertEquals(0, summary.getTotalFailed()); + } + + @Test + public void testTokenizeSummary_someGroupsFailedCountsAsPartial() { + List records = Collections.singletonList( + tokenizeRecord(0, token("g1", "t1", 200, null), token("g2", null, 400, "bad group"))); + + TokenizeSummary summary = new BulkTokenizeResponse(records, Collections.singletonList(requestRecord("a"))) + .getSummary(); + + Assert.assertEquals(0, summary.getTotalTokenized()); + Assert.assertEquals(1, summary.getTotalPartial()); + Assert.assertEquals(0, summary.getTotalFailed()); + } + + @Test + public void testTokenizeSummary_everyGroupFailedCountsAsFailed() { + List records = Collections.singletonList( + tokenizeRecord(0, token("g1", null, 500, "boom"), token("g2", null, 500, "boom"))); + + TokenizeSummary summary = new BulkTokenizeResponse(records, Collections.singletonList(requestRecord("a"))) + .getSummary(); + + Assert.assertEquals(0, summary.getTotalTokenized()); + Assert.assertEquals(0, summary.getTotalPartial()); + Assert.assertEquals(1, summary.getTotalFailed()); + } + + @Test + public void testTokenizeSummary_noTokensAtAllCountsAsFailed() { + // A record that came back with no token groups is a failure, not a success. + List records = Collections.singletonList( + new BulkTokenizeResponseRecord(0, "value0", null)); + + TokenizeSummary summary = new BulkTokenizeResponse(records, Collections.singletonList(requestRecord("a"))) + .getSummary(); + + Assert.assertEquals(1, summary.getTotalFailed()); + } + + @Test + public void testTokenizeSummary_emptyTokenListCountsAsFailed() { + List records = Collections.singletonList( + new BulkTokenizeResponseRecord(0, "value0", new ArrayList<>())); + + Assert.assertEquals(1, new BulkTokenizeResponse(records, Collections.singletonList(requestRecord("a"))) + .getSummary().getTotalFailed()); + } + + @Test + public void testTokenizeSummary_totalTokensComesFromTheSubmittedPayload() { + // Two values submitted, only one came back — totalTokens must reflect what was sent. + List records = Collections.singletonList( + tokenizeRecord(0, token("g1", "t1", 200, null))); + + TokenizeSummary summary = new BulkTokenizeResponse( + records, Arrays.asList(requestRecord("a"), requestRecord("b"))).getSummary(); + + Assert.assertEquals(2, summary.getTotalTokens()); + Assert.assertEquals(1, summary.getTotalTokenized()); + } + + @Test + public void testTokenizeSummary_nullRecordsYieldsZeroes() { + TokenizeSummary summary = new BulkTokenizeResponse(null, new ArrayList<>()).getSummary(); + + Assert.assertEquals(0, summary.getTotalTokens()); + Assert.assertEquals(0, summary.getTotalTokenized()); + Assert.assertEquals(0, summary.getTotalPartial()); + Assert.assertEquals(0, summary.getTotalFailed()); + } + + @Test + public void testTokenizeSummary_isNullWhenNoPayloadWasSupplied() { + // The single-arg constructor is the "no summary" path used before batching completes. + Assert.assertNull(new BulkTokenizeResponse(new ArrayList<>()).getSummary()); + } + + // ── BulkTokenizeResponse.getRecordsToRetry ──────────────────────────────── + + @Test + public void testTokenizeRetry_only5xxFailuresAreReturned() { + List records = Arrays.asList( + tokenizeRecord(0, token("g", null, 500, "server")), + tokenizeRecord(1, token("g", null, 400, "client")), + tokenizeRecord(2, token("g", "t", 200, null))); + List payload = + Arrays.asList(requestRecord("a"), requestRecord("b"), requestRecord("c")); + + List retry = new BulkTokenizeResponse(records, payload).getRecordsToRetry(); + + Assert.assertEquals(1, retry.size()); + Assert.assertSame("must return the caller's own record object", payload.get(0), retry.get(0)); + } + + @Test + public void testTokenizeRetry_529IsNotRetried() { + List records = Collections.singletonList( + tokenizeRecord(0, token("g", null, 529, "site frozen"))); + + Assert.assertTrue(new BulkTokenizeResponse(records, Collections.singletonList(requestRecord("a"))) + .getRecordsToRetry().isEmpty()); + } + + @Test + public void testTokenizeRetry_599IsRetriedAnd600IsNot() { + Assert.assertEquals(1, new BulkTokenizeResponse( + Collections.singletonList(tokenizeRecord(0, token("g", null, 599, "edge"))), + Collections.singletonList(requestRecord("a"))).getRecordsToRetry().size()); + Assert.assertEquals(0, new BulkTokenizeResponse( + Collections.singletonList(tokenizeRecord(0, token("g", null, 600, "edge"))), + Collections.singletonList(requestRecord("a"))).getRecordsToRetry().size()); + } + + @Test + public void testTokenizeRetry_5xxWithoutAnErrorIsNotRetried() { + List records = Collections.singletonList( + tokenizeRecord(0, token("g", "t", 500, null))); + + Assert.assertTrue(new BulkTokenizeResponse(records, Collections.singletonList(requestRecord("a"))) + .getRecordsToRetry().isEmpty()); + } + + @Test + public void testTokenizeRetry_nullHttpCodeIsNotRetried() { + List records = Collections.singletonList( + tokenizeRecord(0, token("g", null, null, "no status"))); + + Assert.assertTrue(new BulkTokenizeResponse(records, Collections.singletonList(requestRecord("a"))) + .getRecordsToRetry().isEmpty()); + } + + @Test + public void testTokenizeRetry_partialFailureRetriesTheWholeRecord() { + // One group failed retryably, another succeeded — the value still needs resubmitting. + List records = Collections.singletonList( + tokenizeRecord(0, token("g1", "t1", 200, null), token("g2", null, 503, "down"))); + + Assert.assertEquals(1, new BulkTokenizeResponse(records, Collections.singletonList(requestRecord("a"))) + .getRecordsToRetry().size()); + } + + @Test + public void testTokenizeRetry_recordWithNullTokensIsNotRetried() { + List records = Collections.singletonList( + new BulkTokenizeResponseRecord(0, "value0", null)); + + Assert.assertTrue(new BulkTokenizeResponse(records, Collections.singletonList(requestRecord("a"))) + .getRecordsToRetry().isEmpty()); + } + + @Test + public void testTokenizeRetry_outOfRangeIndexIsSkippedRatherThanThrowing() { + List records = Arrays.asList( + tokenizeRecord(5, token("g", null, 500, "server")), + tokenizeRecord(-1, token("g", null, 500, "server"))); + + Assert.assertTrue(new BulkTokenizeResponse(records, Collections.singletonList(requestRecord("a"))) + .getRecordsToRetry().isEmpty()); + } + + @Test + public void testTokenizeRetry_withoutOriginalPayloadReturnsEmpty() { + List records = Collections.singletonList( + tokenizeRecord(0, token("g", null, 500, "server"))); + + Assert.assertTrue(new BulkTokenizeResponse(records).getRecordsToRetry().isEmpty()); + } + + @Test + public void testTokenizeRetry_isMemoisedAcrossCalls() { + List records = Collections.singletonList( + tokenizeRecord(0, token("g", null, 500, "server"))); + BulkTokenizeResponse response = + new BulkTokenizeResponse(records, Collections.singletonList(requestRecord("a"))); + + Assert.assertSame(response.getRecordsToRetry(), response.getRecordsToRetry()); + } + + // ── BulkDeleteTokensResponse ────────────────────────────────────────────── + + private static BulkDeleteTokensResponseRecord deleteRecord(int index, String token, Integer code, String error) { + return new BulkDeleteTokensResponseRecord(index, token, code, error); + } + + @Test + public void testDeleteTokensSummary_countsDeletedAndFailed() { + List records = Arrays.asList( + deleteRecord(0, "t1", 200, null), + deleteRecord(1, "t2", 404, "not found")); + + DeleteTokensSummary summary = + new BulkDeleteTokensResponse(records, Arrays.asList("t1", "t2")).getSummary(); + + Assert.assertEquals(2, summary.getTotalTokens()); + Assert.assertEquals(1, summary.getTotalDeleted()); + Assert.assertEquals(1, summary.getTotalFailed()); + } + + @Test + public void testDeleteTokensSummary_totalFallsBackToRecordCountWithoutPayload() { + List records = Arrays.asList( + deleteRecord(0, "t1", 200, null), + deleteRecord(1, "t2", 500, "boom")); + + DeleteTokensSummary summary = new BulkDeleteTokensResponse(records, null).getSummary(); + + Assert.assertEquals(2, summary.getTotalTokens()); + } + + @Test + public void testDeleteTokensSummary_nullRecordsYieldsZeroes() { + DeleteTokensSummary summary = new BulkDeleteTokensResponse(null, new ArrayList<>()).getSummary(); + + Assert.assertEquals(0, summary.getTotalTokens()); + Assert.assertEquals(0, summary.getTotalDeleted()); + Assert.assertEquals(0, summary.getTotalFailed()); + } + + @Test + public void testDeleteTokensRetry_only5xxFailuresAreReturned() { + List records = Arrays.asList( + deleteRecord(0, "t1", 500, "server"), + deleteRecord(1, "t2", 400, "client"), + deleteRecord(2, "t3", 200, null)); + + List retry = new BulkDeleteTokensResponse(records, Arrays.asList("t1", "t2", "t3")) + .getTokensToRetry(); + + Assert.assertEquals(Collections.singletonList("t1"), retry); + } + + @Test + public void testDeleteTokensRetry_529IsNotRetried() { + List records = + Collections.singletonList(deleteRecord(0, "t1", 529, "site frozen")); + + Assert.assertTrue(new BulkDeleteTokensResponse(records, Collections.singletonList("t1")) + .getTokensToRetry().isEmpty()); + } + + @Test + public void testDeleteTokensRetry_5xxWithoutErrorOrNullCodeIsNotRetried() { + Assert.assertTrue(new BulkDeleteTokensResponse( + Collections.singletonList(deleteRecord(0, "t1", 500, null)), + Collections.singletonList("t1")).getTokensToRetry().isEmpty()); + Assert.assertTrue(new BulkDeleteTokensResponse( + Collections.singletonList(deleteRecord(0, "t1", null, "no status")), + Collections.singletonList("t1")).getTokensToRetry().isEmpty()); + } + + @Test + public void testDeleteTokensRetry_nullTokenIsSkipped() { + // A failed record with no token echoed back cannot be resubmitted. + List records = + Collections.singletonList(deleteRecord(0, null, 500, "server")); + + Assert.assertTrue(new BulkDeleteTokensResponse(records, Collections.singletonList("t1")) + .getTokensToRetry().isEmpty()); + } + + @Test + public void testDeleteTokensRetry_isMemoisedAcrossCalls() { + List records = + Collections.singletonList(deleteRecord(0, "t1", 500, "server")); + BulkDeleteTokensResponse response = + new BulkDeleteTokensResponse(records, Collections.singletonList("t1")); + + Assert.assertSame(response.getTokensToRetry(), response.getTokensToRetry()); + } + + @Test + public void testDeleteTokensRetry_withNullRecordsReturnsEmpty() { + Assert.assertTrue(new BulkDeleteTokensResponse(null, Collections.singletonList("t1")) + .getTokensToRetry().isEmpty()); + } +} diff --git a/flowvault/src/test/java/com/skyflow/vault/data/OptionsTests.java b/flowvault/src/test/java/com/skyflow/vault/data/OptionsTests.java new file mode 100644 index 00000000..83b54775 --- /dev/null +++ b/flowvault/src/test/java/com/skyflow/vault/data/OptionsTests.java @@ -0,0 +1,159 @@ +package com.skyflow.vault.data; + +import org.junit.Assert; +import org.junit.Test; +public class OptionsTests { + + private static final RequestInterceptor INTERCEPTOR = context -> { + }; + + // ── InsertOptions ──────────────────────────────────────────────────────── + + @Test + public void testInsertOptions_withInterceptor() { + InsertOptions options = InsertOptions.builder().interceptor(INTERCEPTOR).build(); + Assert.assertSame(INTERCEPTOR, options.getInterceptor()); + } + + @Test + public void testInsertOptions_withoutInterceptor() { + InsertOptions options = InsertOptions.builder().build(); + Assert.assertNull(options.getInterceptor()); + } + + // ── DetokenizeOptions ──────────────────────────────────────────────────── + + @Test + public void testDetokenizeOptions_withInterceptor() { + DetokenizeOptions options = DetokenizeOptions.builder().interceptor(INTERCEPTOR).build(); + Assert.assertSame(INTERCEPTOR, options.getInterceptor()); + } + + @Test + public void testDetokenizeOptions_withoutInterceptor() { + DetokenizeOptions options = DetokenizeOptions.builder().build(); + Assert.assertNull(options.getInterceptor()); + } + + // ── BulkInsertOptions ──────────────────────────────────────────────────── + + @Test + public void testBulkInsertOptions_withInterceptor() { + BulkInsertOptions options = BulkInsertOptions.builder().interceptor(INTERCEPTOR).build(); + Assert.assertSame(INTERCEPTOR, options.getInterceptor()); + // BulkInsertOptions is a specialization of InsertOptions, so it flows anywhere the base does + Assert.assertTrue(options instanceof InsertOptions); + } + + @Test + public void testBulkInsertOptions_withoutInterceptor() { + BulkInsertOptions options = BulkInsertOptions.builder().build(); + Assert.assertNull(options.getInterceptor()); + } + + // ── BulkDetokenizeOptions ──────────────────────────────────────────────── + + @Test + public void testBulkDetokenizeOptions_withInterceptor() { + BulkDetokenizeOptions options = BulkDetokenizeOptions.builder().interceptor(INTERCEPTOR).build(); + Assert.assertSame(INTERCEPTOR, options.getInterceptor()); + Assert.assertTrue(options instanceof DetokenizeOptions); + } + + @Test + public void testBulkDetokenizeOptions_withoutInterceptor() { + BulkDetokenizeOptions options = BulkDetokenizeOptions.builder().build(); + Assert.assertNull(options.getInterceptor()); + } + + // ── TokenizeOptions ────────────────────────────────────────────────────── + + @Test + public void testTokenizeOptions_withInterceptor() { + TokenizeOptions options = TokenizeOptions.builder().interceptor(INTERCEPTOR).build(); + Assert.assertSame(INTERCEPTOR, options.getInterceptor()); + } + + @Test + public void testTokenizeOptions_withoutInterceptor() { + TokenizeOptions options = TokenizeOptions.builder().build(); + Assert.assertNull(options.getInterceptor()); + } + + // ── DeleteTokensOptions ────────────────────────────────────────────────── + + @Test + public void testDeleteTokensOptions_withInterceptor() { + DeleteTokensOptions options = DeleteTokensOptions.builder().interceptor(INTERCEPTOR).build(); + Assert.assertSame(INTERCEPTOR, options.getInterceptor()); + } + + @Test + public void testDeleteTokensOptions_withoutInterceptor() { + DeleteTokensOptions options = DeleteTokensOptions.builder().build(); + Assert.assertNull(options.getInterceptor()); + } + + // ── BulkTokenizeOptions ────────────────────────────────────────────────── + + @Test + public void testBulkTokenizeOptions_withInterceptor() { + BulkTokenizeOptions options = BulkTokenizeOptions.builder().interceptor(INTERCEPTOR).build(); + Assert.assertSame(INTERCEPTOR, options.getInterceptor()); + } + + @Test + public void testBulkTokenizeOptions_withoutInterceptor() { + BulkTokenizeOptions options = BulkTokenizeOptions.builder().build(); + Assert.assertNull(options.getInterceptor()); + } + + @Test + public void testBulkTokenizeOptions_isATokenizeOptions() { + BulkTokenizeOptions options = BulkTokenizeOptions.builder().interceptor(INTERCEPTOR).build(); + Assert.assertTrue(options instanceof TokenizeOptions); + // the inherited accessor sees the same interceptor + Assert.assertSame(INTERCEPTOR, ((TokenizeOptions) options).getInterceptor()); + } + + @Test + public void testBulkTokenizeOptions_builderStaysBulkTypedWhileChaining() { + // builder() hides the parent's, so the override must return the bulk builder for + // chaining to keep compiling without a cast + BulkTokenizeOptions.BulkTokenizeOptionsBuilder builder = + BulkTokenizeOptions.builder().interceptor(INTERCEPTOR); + BulkTokenizeOptions options = builder.build(); + Assert.assertSame(INTERCEPTOR, options.getInterceptor()); + } + + // ── BulkDeleteTokensOptions ────────────────────────────────────────────── + + @Test + public void testBulkDeleteTokensOptions_withInterceptor() { + BulkDeleteTokensOptions options = + BulkDeleteTokensOptions.builder().interceptor(INTERCEPTOR).build(); + Assert.assertSame(INTERCEPTOR, options.getInterceptor()); + } + + @Test + public void testBulkDeleteTokensOptions_withoutInterceptor() { + BulkDeleteTokensOptions options = BulkDeleteTokensOptions.builder().build(); + Assert.assertNull(options.getInterceptor()); + } + + @Test + public void testBulkDeleteTokensOptions_isADeleteTokensOptions() { + BulkDeleteTokensOptions options = + BulkDeleteTokensOptions.builder().interceptor(INTERCEPTOR).build(); + Assert.assertTrue(options instanceof DeleteTokensOptions); + Assert.assertSame(INTERCEPTOR, ((DeleteTokensOptions) options).getInterceptor()); + } + + @Test + public void testBulkDeleteTokensOptions_builderStaysBulkTypedWhileChaining() { + BulkDeleteTokensOptions.BulkDeleteTokensOptionsBuilder builder = + BulkDeleteTokensOptions.builder().interceptor(INTERCEPTOR); + BulkDeleteTokensOptions options = builder.build(); + Assert.assertSame(INTERCEPTOR, options.getInterceptor()); + } +} diff --git a/flowvault/src/test/java/com/skyflow/vault/data/RecordAndRedactionTests.java b/flowvault/src/test/java/com/skyflow/vault/data/RecordAndRedactionTests.java new file mode 100644 index 00000000..52cbf10c --- /dev/null +++ b/flowvault/src/test/java/com/skyflow/vault/data/RecordAndRedactionTests.java @@ -0,0 +1,165 @@ +package com.skyflow.vault.data; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Tests for the plain record/redaction data holders: {@link TokenGroupRedactions}, + * {@link InsertRequestRecord}, {@link BulkInsertRequestRecord} + * and {@link BulkTokenizeRequestRecord}. None of these classes perform + * validation in their builders, so coverage here is builder-construction plus getters. + */ +public class RecordAndRedactionTests { + + // ── TokenGroupRedactions ───────────────────────────────────────────────── + + @Test + public void testTokenGroupRedactions_gettersReturnBuilderValues() { + TokenGroupRedactions redaction = TokenGroupRedactions.builder() + .tokenGroupName("group1") + .redaction("MASK") + .build(); + + Assert.assertEquals("group1", redaction.getTokenGroupName()); + Assert.assertEquals("MASK", redaction.getRedaction()); + } + + @Test + public void testTokenGroupRedactions_defaultsAreNull() { + TokenGroupRedactions redaction = TokenGroupRedactions.builder().build(); + Assert.assertNull(redaction.getTokenGroupName()); + Assert.assertNull(redaction.getRedaction()); + } + + // BulkTokenGroupRedactions tests removed: the class was deleted; bulk detokenize now reuses + // TokenGroupRedactions (covered above). + + // ── InsertRequestRecord ───────────────────────────────────────────────────────── + + @Test + public void testInsertRequestRecord_gettersReturnBuilderValues() { + Map data = new HashMap<>(); + data.put("name", "John"); + Map tokens = new HashMap<>(); + tokens.put("name", "token-value"); + UpsertOptions upsert = UpsertOptions.builder() + .uniqueColumns(Arrays.asList("id")) + .updateType("UPDATE") + .build(); + + InsertRequestRecord record = InsertRequestRecord.builder() + .tableName("persons") + .data(data) + .tokens(tokens) + .upsert(upsert) + .build(); + + Assert.assertEquals("persons", record.getTableName()); + Assert.assertEquals(data, record.getData()); + Assert.assertEquals(tokens, record.getTokens()); + Assert.assertEquals(upsert, record.getUpsert()); + } + + @Test + public void testInsertRequestRecord_defaultsAreNull() { + InsertRequestRecord record = InsertRequestRecord.builder().build(); + Assert.assertNull(record.getTableName()); + Assert.assertNull(record.getData()); + Assert.assertNull(record.getTokens()); + Assert.assertNull(record.getUpsert()); + } + + // ── BulkInsertRequestRecord ────────────────────────────────────────────── + + @Test + public void testBulkInsertRequestRecord_gettersReturnBuilderValues() { + Map data = new HashMap<>(); + data.put("name", "Jane"); + Map tokens = new HashMap<>(); + tokens.put("name", "token-1"); + UpsertOptions upsert = UpsertOptions.builder() + .updateType("REPLACE") + .uniqueColumns(Arrays.asList("id")) + .build(); + + BulkInsertRequestRecord record = BulkInsertRequestRecord.builder() + .tableName("persons") + .data(data) + .tokens(tokens) + .upsert(upsert) + .build(); + + Assert.assertEquals("persons", record.getTableName()); + Assert.assertEquals(data, record.getData()); + Assert.assertEquals(tokens, record.getTokens()); + Assert.assertEquals(upsert, record.getUpsert()); + Assert.assertEquals("REPLACE", record.getUpsert().getUpdateType()); + Assert.assertEquals(Arrays.asList("id"), record.getUpsert().getUniqueColumns()); + } + + @Test + public void testBulkInsertRequestRecord_defaultsAreNull() { + BulkInsertRequestRecord record = BulkInsertRequestRecord.builder().build(); + Assert.assertNull(record.getTableName()); + Assert.assertNull(record.getData()); + Assert.assertNull(record.getTokens()); + Assert.assertNull(record.getUpsert()); + } + + // ── TokenizeRequestRecord ─────────────────────────────────────────────────────── + + @Test + public void testTokenizeRecord_gettersReturnBuilderValues() { + List groups = Arrays.asList("group1", "group2"); + TokenizeRequestRecord record = TokenizeRequestRecord.builder() + .value("secret-value") + .tokenGroupNames(groups) + .build(); + + Assert.assertEquals("secret-value", record.getValue()); + Assert.assertEquals(groups, record.getTokenGroupNames()); + } + + @Test + public void testTokenizeRecord_defaultsAreNull() { + TokenizeRequestRecord record = TokenizeRequestRecord.builder().build(); + Assert.assertNull(record.getValue()); + Assert.assertNull(record.getTokenGroupNames()); + } + + // ── BulkTokenizeRequestRecord ─────────────────────────────────────────────────── + + @Test + public void testBulkTokenizeRequestRecord_gettersReturnBuilderValues() { + List groups = Arrays.asList("group3"); + BulkTokenizeRequestRecord record = BulkTokenizeRequestRecord.builder() + .value(12345) + .token("byot-token") + .tokenGroupNames(groups) + .build(); + + Assert.assertEquals(12345, record.getValue()); + Assert.assertEquals("byot-token", record.getToken()); + Assert.assertEquals(groups, record.getTokenGroupNames()); + } + + @Test + public void testBulkTokenizeRequestRecord_defaultsAreNull() { + BulkTokenizeRequestRecord record = BulkTokenizeRequestRecord.builder().build(); + Assert.assertNull(record.getValue()); + Assert.assertNull(record.getToken()); + Assert.assertNull(record.getTokenGroupNames()); + } + + @Test + public void testBulkTokenizeRequestRecord_isATokenizeRequestRecord() { + // the bulk record adds nothing today; it must still satisfy the parent contract + BulkTokenizeRequestRecord record = BulkTokenizeRequestRecord.builder().value("v1").build(); + Assert.assertTrue(record instanceof TokenizeRequestRecord); + } +} diff --git a/flowvault/src/test/java/com/skyflow/vault/data/RequestResponseWrapperTests.java b/flowvault/src/test/java/com/skyflow/vault/data/RequestResponseWrapperTests.java new file mode 100644 index 00000000..393a98d1 --- /dev/null +++ b/flowvault/src/test/java/com/skyflow/vault/data/RequestResponseWrapperTests.java @@ -0,0 +1,300 @@ +package com.skyflow.vault.data; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Tests for the simple request/response wrapper classes: {@link InsertRequest}, + * {@link InsertResponseRecord}, {@link DetokenizeRequest}, {@link DetokenizeResponseRecord}, + * {@link BulkDeleteTokensRequest}, {@link BulkDetokenizeRequest}, {@link BulkTokenizeRequest} + * and {@link BulkInsertRequest}. + */ +public class RequestResponseWrapperTests { + + // Tests for DeleteTokensRequest were removed: the class no longer exists (bulk-only module). + + // ── InsertRequest ──────────────────────────────────────────────────────── + + @Test + public void testInsertRequest_gettersReturnBuilderValues() { + ArrayList records = new ArrayList<>(Collections.singletonList( + InsertRequestRecord.builder().tableName("persons").build())); + UpsertOptions upsert = UpsertOptions.builder() + .uniqueColumns(Arrays.asList("id")) + .updateType("UPDATE") + .build(); + + InsertRequest request = InsertRequest.builder() + .upsert(upsert) + .records(records) + .build(); + + Assert.assertEquals(upsert, request.getUpsert()); + Assert.assertEquals(records, request.getRecords()); + } + + @Test + public void testInsertRequest_tableNameGetterReturnsBuilderValue() { + InsertRequest request = InsertRequest.builder().tableName("persons").build(); + Assert.assertEquals("persons", request.getTableName()); + } + + @Test + public void testInsertRequest_defaultsAreNull() { + InsertRequest request = InsertRequest.builder().build(); + Assert.assertNull(request.getUpsert()); + Assert.assertNull(request.getRecords()); + Assert.assertNull(request.getTableName()); + } + + // ── InsertRequestRecord ───────────────────────────────────────────────────────── + + @Test + public void testInsertRequestRecord_gettersReturnBuilderValues() { + Map data = new HashMap<>(); + data.put("name", "john"); + Map tokens = new HashMap<>(); + tokens.put("name", "tok-abc"); + UpsertOptions upsert = UpsertOptions.builder().uniqueColumns(Arrays.asList("id")).build(); + + InsertRequestRecord record = InsertRequestRecord.builder() + .tableName("persons") + .data(data) + .tokens(tokens) + .upsert(upsert) + .build(); + + Assert.assertEquals("persons", record.getTableName()); + Assert.assertEquals(data, record.getData()); + Assert.assertEquals(tokens, record.getTokens()); + Assert.assertEquals(upsert, record.getUpsert()); + } + + // ── UpsertOptions ──────────────────────────────────────────────────────── + + @Test + public void testTokenizeRequest_getterReturnsBuilderValue() { + List records = Collections.singletonList( + TokenizeRequestRecord.builder().value("v1").build()); + TokenizeRequest request = TokenizeRequest.builder().records(records).build(); + Assert.assertEquals(records, request.getRecords()); + } + + @Test + public void testBulkTokenizeRequest_isATokenizeRequest() { + BulkTokenizeRequest request = BulkTokenizeRequest.builder() + .records(Collections.singletonList( + BulkTokenizeRequestRecord.builder().value("v1").build())) + .build(); + Assert.assertTrue(request instanceof TokenizeRequest); + // the inherited accessor sees the same records, widened + Assert.assertEquals(1, ((TokenizeRequest) request).getRecords().size()); + } + + @Test + public void testTokenizeRequest_defaultIsNull() { + TokenizeRequest request = TokenizeRequest.builder().build(); + Assert.assertNull(request.getRecords()); + } + + // ── TokenizeResponse ───────────────────────────────────────────────────── + + @Test + public void testTokenizeResponse_gettersReturnConstructorValues() { + List records = Collections.singletonList( + new TokenizeResponseRecord("value1", Collections.singletonList( + new TokenizeResponseToken("group1", "tok-abc", 200, null)))); + + TokenizeResponse response = new TokenizeResponse(records); + + Assert.assertEquals(records, response.getResponse()); + Assert.assertEquals("value1", response.getResponse().get(0).getValue()); + Assert.assertEquals("tok-abc", response.getResponse().get(0).getTokens().get(0).getToken()); + Assert.assertNull(response.getResponse().get(0).getTokens().get(0).getError()); + } + + @Test + public void testTokenizeResponse_toStringSerializesNulls() { + TokenizeResponse response = new TokenizeResponse(Collections.singletonList( + new TokenizeResponseRecord("value1", Collections.singletonList( + new TokenizeResponseToken("group1", "tok-abc", 200, null))))); + Assert.assertTrue(response.toString().contains("\"error\":null")); + } + + // ── DetokenizeRequest ──────────────────────────────────────────────────── + + @Test + public void testDetokenizeRequest_gettersReturnBuilderValues() { + List tokens = Collections.singletonList("tok-1"); + List redactions = Collections.singletonList( + TokenGroupRedactions.builder().tokenGroupName("group1").redaction("MASK").build()); + + DetokenizeRequest request = DetokenizeRequest.builder() + .tokens(tokens) + .tokenGroupRedactions(redactions) + .build(); + + Assert.assertEquals(tokens, request.getTokens()); + Assert.assertEquals(redactions, request.getTokenGroupRedactions()); + } + + @Test + public void testDetokenizeRequest_defaultsAreNull() { + DetokenizeRequest request = DetokenizeRequest.builder().build(); + Assert.assertNull(request.getTokens()); + Assert.assertNull(request.getTokenGroupRedactions()); + } + + // Tests for DetokenizeResponse were removed: the class no longer exists (bulk-only module). + + // ── DetokenizeResponseRecord ───────────────────────────────────────────── + + @Test + public void testDetokenizeResponseRecord_gettersReturnConstructorValues() { + Map metadata = new HashMap<>(); + metadata.put("key", "value"); + + DetokenizeResponseRecord response = new DetokenizeResponseRecord( + "tok-1", "secret-value", "group1", metadata, 200, null); + + Assert.assertEquals("tok-1", response.getToken()); + Assert.assertNull(response.getError()); + Assert.assertEquals("group1", response.getTokenGroupName()); + Assert.assertEquals(metadata, response.getMetadata()); + Assert.assertEquals(200, response.getHttpCode()); + } + + @Test + public void testDetokenizeResponseRecord_errorCase() { + DetokenizeResponseRecord response = new DetokenizeResponseRecord( + "tok-2", null, null, null, 404, "Token not found"); + + Assert.assertEquals("tok-2", response.getToken()); + Assert.assertEquals("Token not found", response.getError()); + Assert.assertNull(response.getTokenGroupName()); + Assert.assertNull(response.getMetadata()); + Assert.assertEquals(404, response.getHttpCode()); + } + + // ── BulkDeleteTokensRequest ────────────────────────────────────────────── + + @Test + public void testBulkDeleteTokensRequest_getterReturnsBuilderValue() { + List tokens = Arrays.asList("tok-1", "tok-2"); + BulkDeleteTokensRequest request = BulkDeleteTokensRequest.builder().tokens(tokens).build(); + Assert.assertEquals(tokens, request.getTokens()); + } + + @Test + public void testBulkDeleteTokensRequest_defaultIsNull() { + BulkDeleteTokensRequest request = BulkDeleteTokensRequest.builder().build(); + Assert.assertNull(request.getTokens()); + } + + // ── BulkDetokenizeRequest ──────────────────────────────────────────────── + + @Test + public void testBulkDetokenizeRequest_gettersReturnBuilderValues() { + List tokens = Arrays.asList("tok-1", "tok-2"); + List redactions = Collections.singletonList( + TokenGroupRedactions.builder().tokenGroupName("group1").redaction("MASK").build()); + + BulkDetokenizeRequest request = BulkDetokenizeRequest.builder() + .tokens(tokens) + .tokenGroupRedactions(redactions) + .build(); + + Assert.assertEquals(tokens, request.getTokens()); + Assert.assertEquals(redactions, request.getTokenGroupRedactions()); + } + + @Test + public void testBulkDetokenizeRequest_defaultsAreNull() { + BulkDetokenizeRequest request = BulkDetokenizeRequest.builder().build(); + Assert.assertNull(request.getTokens()); + Assert.assertNull(request.getTokenGroupRedactions()); + } + + @Test + public void testBulkDetokenizeRequest_isADetokenizeRequest() { + // Bulk detokenize now shares the unary request contract; all state is inherited. + BulkDetokenizeRequest request = BulkDetokenizeRequest.builder() + .tokens(Collections.singletonList("tok-1")) + .build(); + DetokenizeRequest asUnary = request; + Assert.assertEquals(Collections.singletonList("tok-1"), asUnary.getTokens()); + } + + // ── BulkTokenizeRequest ────────────────────────────────────────────────── + + @Test + public void testBulkTokenizeRequest_getterReturnsBuilderValue() { + List records = Collections.singletonList( + BulkTokenizeRequestRecord.builder().value("v1").build()); + BulkTokenizeRequest request = BulkTokenizeRequest.builder().records(records).build(); + Assert.assertEquals(records, request.getRecords()); + } + + @Test + public void testBulkTokenizeRequest_defaultIsNull() { + BulkTokenizeRequest request = BulkTokenizeRequest.builder().build(); + Assert.assertNull(request.getRecords()); + } + + // ── BulkInsertRequest ──────────────────────────────────────────────────── + + @Test + public void testBulkInsertRequest_gettersReturnBuilderValues() { + List records = new ArrayList<>(Collections.singletonList( + BulkInsertRequestRecord.builder().tableName("persons").build())); + UpsertOptions upsert = UpsertOptions.builder() + .updateType("REPLACE") + .uniqueColumns(Arrays.asList("id")) + .build(); + + BulkInsertRequest request = BulkInsertRequest.builder() + .tableName("persons") + .upsert(upsert) + .records(records) + .build(); + + Assert.assertEquals("persons", request.getTableName()); + Assert.assertEquals(upsert, request.getUpsert()); + Assert.assertEquals("REPLACE", request.getUpsert().getUpdateType()); + Assert.assertEquals(Arrays.asList("id"), request.getUpsert().getUniqueColumns()); + Assert.assertEquals(records, request.getRecords()); + } + + @Test + public void testBulkInsertRequest_defaultsAreNull() { + BulkInsertRequest request = BulkInsertRequest.builder().build(); + Assert.assertNull(request.getTableName()); + Assert.assertNull(request.getUpsert()); + Assert.assertNull(request.getRecords()); + } + + @Test + public void testDeleteTokensResponse_gettersReturnConstructorValues() { + List records = Arrays.asList( + new DeleteTokensRecord("tok-1", 200, null), + new DeleteTokensRecord("tok-2", 404, "Token not found")); + + DeleteTokensResponse response = new DeleteTokensResponse(records); + + Assert.assertEquals(records, response.getRecords()); + Assert.assertEquals("tok-1", response.getRecords().get(0).getToken()); + Assert.assertNull(response.getRecords().get(0).getError()); + Assert.assertEquals("Token not found", response.getRecords().get(1).getError()); + Assert.assertEquals(Integer.valueOf(404), response.getRecords().get(1).getHttpCode()); + } + + // Tests for DeleteTokensResponse were removed: the class no longer exists (bulk-only module). + +} diff --git a/flowvault/src/test/java/com/skyflow/vault/data/ResponseComponentTests.java b/flowvault/src/test/java/com/skyflow/vault/data/ResponseComponentTests.java new file mode 100644 index 00000000..273c4c85 --- /dev/null +++ b/flowvault/src/test/java/com/skyflow/vault/data/ResponseComponentTests.java @@ -0,0 +1,332 @@ +package com.skyflow.vault.data; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Tests for the response/success/summary building-block classes that carry real + * constructor logic or toString() serialization: {@link Success}, {@link Summary}, + * {@link Token}, {@link TokenizeResponseToken}, {@link TokenizeResponseRecord}, + * {@link BulkTokenizeResponseRecord}, {@link TokenizeSummary}, + * {@link DeleteTokensRecord}, {@link BulkDeleteTokensResponseRecord}, + * {@link DeleteTokensSummary}, {@link DetokenizeSummary}, + * {@link ErrorRecord} and {@link DetokenizeResponseObject}. + */ +public class ResponseComponentTests { + + // Tests for Success, Summary and Token were removed: the bulk insert response contract + // replaced those classes with BulkInsertResponseRecord / BulkSummary, covered below. + + // ── BulkInsertResponseRecord ───────────────────────────────────────────── + + @Test + public void testBulkInsertResponseRecord_gettersReturnConstructorValues() { + Map fields = new HashMap<>(); + fields.put("name", "tok-1"); + Map hashedData = new HashMap<>(); + hashedData.put("name", "hashed-1"); + + BulkInsertResponseRecord record = new BulkInsertResponseRecord( + 2, "persons", "skyflow-id-1", fields, hashedData, 200, null, null); + + Assert.assertEquals(2, record.getIndex()); + Assert.assertEquals("persons", record.getTableName()); + Assert.assertEquals("skyflow-id-1", record.getSkyflowId()); + Assert.assertEquals(fields, record.getFields()); + Assert.assertEquals(hashedData, record.getHashedData()); + Assert.assertEquals(200, record.getHttpCode()); + Assert.assertNull(record.getError()); + } + + @Test + public void testBulkInsertResponseRecord_errorCase() { + BulkInsertResponseRecord record = new BulkInsertResponseRecord( + 3, null, null, null, null, 500, "Internal Server Error", null); + + Assert.assertEquals(3, record.getIndex()); + Assert.assertEquals(500, record.getHttpCode()); + Assert.assertEquals("Internal Server Error", record.getError()); + Assert.assertNull(record.getTableName()); + Assert.assertNull(record.getSkyflowId()); + Assert.assertNull(record.getFields()); + Assert.assertNull(record.getHashedData()); + } + + @Test + public void testBulkInsertResponseRecord_toStringSerializesNulls() { + BulkInsertResponseRecord record = new BulkInsertResponseRecord( + 0, "persons", "skyflow-id-2", null, null, 200, null, null); + String json = record.toString(); + Assert.assertNotNull(json); + Assert.assertTrue(json.contains("skyflow-id-2")); + Assert.assertTrue(json.contains("\"index\":0")); + Assert.assertTrue(json.contains("\"fields\":null")); + } + + // ── BulkSummary ────────────────────────────────────────────────────────── + + @Test + public void testBulkSummary_noArgConstructorDefaultsToZero() { + BulkSummary summary = new BulkSummary(); + Assert.assertEquals(0, summary.getTotalRecords()); + Assert.assertEquals(0, summary.getTotalInserted()); + Assert.assertEquals(0, summary.getTotalFailed()); + } + + @Test + public void testBulkSummary_allArgConstructor() { + BulkSummary summary = new BulkSummary(10, 8, 2); + Assert.assertEquals(10, summary.getTotalRecords()); + Assert.assertEquals(8, summary.getTotalInserted()); + Assert.assertEquals(2, summary.getTotalFailed()); + } + + @Test + public void testBulkSummary_toStringNotNull() { + Assert.assertNotNull(new BulkSummary(1, 1, 0).toString()); + } + + + // ── TokenizeResponseToken ──────────────────────────────────────────────── + + @Test + public void testTokenizeResponseToken_successValues() { + TokenizeResponseToken token = new TokenizeResponseToken("group1", "tok-abc", 200, null); + Assert.assertEquals("group1", token.getTokenGroupName()); + Assert.assertEquals("tok-abc", token.getToken()); + Assert.assertEquals(Integer.valueOf(200), token.getHttpCode()); + Assert.assertNull(token.getError()); + } + + @Test + public void testTokenizeResponseToken_errorValues() { + TokenizeResponseToken token = new TokenizeResponseToken("group2", null, 400, "bad group"); + Assert.assertNull(token.getToken()); + Assert.assertEquals("bad group", token.getError()); + Assert.assertEquals(Integer.valueOf(400), token.getHttpCode()); + } + + @Test + public void testTokenizeResponseToken_toStringSerializesNulls() { + Assert.assertTrue(new TokenizeResponseToken("group1", "tok-abc", 200, null) + .toString().contains("\"error\":null")); + } + + // ── TokenizeResponseRecord / BulkTokenizeResponseRecord ────────────────── + + @Test + public void testTokenizeResponseRecord_gettersReturnConstructorValues() { + List tokens = Collections.singletonList( + new TokenizeResponseToken("group1", "tok-abc", 200, null)); + TokenizeResponseRecord record = new TokenizeResponseRecord("value1", tokens); + Assert.assertEquals("value1", record.getValue()); + Assert.assertEquals(tokens, record.getTokens()); + } + + @Test + public void testBulkTokenizeResponseRecord_carriesIndexAndIsATokenizeResponseRecord() { + BulkTokenizeResponseRecord record = new BulkTokenizeResponseRecord(7, "value1", + Collections.singletonList(new TokenizeResponseToken("group1", "tok-abc", 200, null))); + Assert.assertEquals(7, record.getIndex()); + Assert.assertEquals("value1", record.getValue()); + Assert.assertTrue(record instanceof TokenizeResponseRecord); + Assert.assertNotNull(record.toString()); + } + + // ── TokenizeSummary ────────────────────────────────────────────────────── + + @Test + public void testTokenizeSummary_noArgConstructorDefaultsToZero() { + TokenizeSummary summary = new TokenizeSummary(); + Assert.assertEquals(0, summary.getTotalTokens()); + Assert.assertEquals(0, summary.getTotalTokenized()); + Assert.assertEquals(0, summary.getTotalPartial()); + Assert.assertEquals(0, summary.getTotalFailed()); + } + + @Test + public void testTokenizeSummary_allArgConstructor() { + TokenizeSummary summary = new TokenizeSummary(10, 5, 3, 2); + Assert.assertEquals(10, summary.getTotalTokens()); + Assert.assertEquals(5, summary.getTotalTokenized()); + Assert.assertEquals(3, summary.getTotalPartial()); + Assert.assertEquals(2, summary.getTotalFailed()); + } + + @Test + public void testTokenizeSummary_toStringNotNull() { + Assert.assertNotNull(new TokenizeSummary(1, 1, 0, 0).toString()); + } + + // ── DeleteTokensRecord ─────────────────────────────────────────────────── + + @Test + public void testDeleteTokensRecord_gettersReturnConstructorValues() { + DeleteTokensRecord record = new DeleteTokensRecord("tok-1", 200, null); + Assert.assertEquals("tok-1", record.getToken()); + Assert.assertEquals(Integer.valueOf(200), record.getHttpCode()); + Assert.assertNull(record.getError()); + } + + @Test + public void testDeleteTokensRecord_carriesErrorDetails() { + DeleteTokensRecord record = new DeleteTokensRecord("tok-2", 404, "Token not found"); + Assert.assertEquals("tok-2", record.getToken()); + Assert.assertEquals(Integer.valueOf(404), record.getHttpCode()); + Assert.assertEquals("Token not found", record.getError()); + } + + @Test + public void testDeleteTokensRecord_toStringSerializesNulls() { + Assert.assertTrue(new DeleteTokensRecord("tok-1", 200, null).toString().contains("\"error\":null")); + } + + // ── BulkDeleteTokensResponseRecord ─────────────────────────────────────── + + @Test + public void testBulkDeleteTokensResponseRecord_gettersReturnConstructorValues() { + BulkDeleteTokensResponseRecord record = new BulkDeleteTokensResponseRecord(1, "tok-1", 200, null); + Assert.assertEquals(1, record.getIndex()); + Assert.assertEquals("tok-1", record.getToken()); + Assert.assertEquals(Integer.valueOf(200), record.getHttpCode()); + Assert.assertNull(record.getError()); + } + + @Test + public void testBulkDeleteTokensResponseRecord_isADeleteTokensRecord() { + Assert.assertTrue(new BulkDeleteTokensResponseRecord(0, "tok-1", 200, null) instanceof DeleteTokensRecord); + } + + @Test + public void testBulkDeleteTokensResponseRecord_toStringNotNull() { + Assert.assertNotNull(new BulkDeleteTokensResponseRecord(0, "tok-1", 200, null).toString()); + } + + // ── DeleteTokensSummary ────────────────────────────────────────────────── + + @Test + public void testDeleteTokensSummary_noArgConstructorDefaultsToZero() { + DeleteTokensSummary summary = new DeleteTokensSummary(); + Assert.assertEquals(0, summary.getTotalTokens()); + Assert.assertEquals(0, summary.getTotalDeleted()); + Assert.assertEquals(0, summary.getTotalFailed()); + } + + @Test + public void testDeleteTokensSummary_allArgConstructor() { + DeleteTokensSummary summary = new DeleteTokensSummary(5, 4, 1); + Assert.assertEquals(5, summary.getTotalTokens()); + Assert.assertEquals(4, summary.getTotalDeleted()); + Assert.assertEquals(1, summary.getTotalFailed()); + } + + @Test + public void testDeleteTokensSummary_toStringNotNull() { + Assert.assertNotNull(new DeleteTokensSummary(1, 1, 0).toString()); + } + + // ── DetokenizeSummary ──────────────────────────────────────────────────── + + @Test + public void testDetokenizeSummary_noArgConstructorDefaultsToZero() { + DetokenizeSummary summary = new DetokenizeSummary(); + Assert.assertEquals(0, summary.getTotalTokens()); + Assert.assertEquals(0, summary.getTotalDetokenized()); + Assert.assertEquals(0, summary.getTotalFailed()); + } + + @Test + public void testDetokenizeSummary_allArgConstructor() { + DetokenizeSummary summary = new DetokenizeSummary(6, 5, 1); + Assert.assertEquals(6, summary.getTotalTokens()); + Assert.assertEquals(5, summary.getTotalDetokenized()); + Assert.assertEquals(1, summary.getTotalFailed()); + } + + @Test + public void testDetokenizeSummary_toStringNotNull() { + Assert.assertNotNull(new DetokenizeSummary(1, 1, 0).toString()); + } + + // ── ErrorRecord ────────────────────────────────────────────────────────── + + @Test + public void testErrorRecord_threeArgConstructorLeavesRequestIdNull() { + ErrorRecord error = new ErrorRecord(0, "Not Found", 404); + Assert.assertEquals(0, error.getIndex()); + Assert.assertEquals("Not Found", error.getError()); + Assert.assertEquals(404, error.getCode()); + Assert.assertNull(error.getRequestId()); + } + + @Test + public void testErrorRecord_fourArgConstructorSetsRequestId() { + ErrorRecord error = new ErrorRecord(1, "Server Error", 500, "req-123"); + Assert.assertEquals(1, error.getIndex()); + Assert.assertEquals("Server Error", error.getError()); + Assert.assertEquals(500, error.getCode()); + Assert.assertEquals("req-123", error.getRequestId()); + } + + @Test + public void testErrorRecord_toStringNotNull() { + Assert.assertNotNull(new ErrorRecord(0, "err", 400).toString()); + } + + // DetokenizeResponseObject tests removed: the class was deleted; the bulk detokenize response + // contract replaced it with BulkDetokenizeResponseRecord, covered below. + + // ── BulkDetokenizeResponseRecord ───────────────────────────────────────── + + @Test + public void testBulkDetokenizeResponseRecord_gettersReturnConstructorValues() { + Map metadata = new HashMap<>(); + metadata.put("key", "value"); + + BulkDetokenizeResponseRecord record = new BulkDetokenizeResponseRecord( + 4, "tok-1", "secret-value", "group1", metadata, 200, null, null); + + Assert.assertEquals(4, record.getIndex()); + Assert.assertEquals("tok-1", record.getToken()); + Assert.assertEquals("group1", record.getTokenGroupName()); + Assert.assertEquals(metadata, record.getMetadata()); + Assert.assertEquals(200, record.getHttpCode()); + Assert.assertNull(record.getError()); + } + + @Test + public void testBulkDetokenizeResponseRecord_errorCase() { + BulkDetokenizeResponseRecord record = new BulkDetokenizeResponseRecord( + 0, "tok-2", null, null, null, 404, "Token not found", null); + + Assert.assertEquals(0, record.getIndex()); + Assert.assertEquals("Token not found", record.getError()); + Assert.assertEquals(404, record.getHttpCode()); + Assert.assertNull(record.getTokenGroupName()); + Assert.assertNull(record.getMetadata()); + } + + @Test + public void testBulkDetokenizeResponseRecord_isADetokenizeResponseRecord() { + BulkDetokenizeResponseRecord record = new BulkDetokenizeResponseRecord( + 1, "tok", "plain", "group", null, 200, null, null); + Assert.assertTrue(record instanceof DetokenizeResponseRecord); + } + + @Test + public void testBulkDetokenizeResponseRecord_toStringSerializesNulls() { + BulkDetokenizeResponseRecord record = new BulkDetokenizeResponseRecord( + 2, "tok", null, null, null, 200, null, null); + String json = record.toString(); + + Assert.assertNotNull(json); + Assert.assertTrue(json.contains("\"index\":2")); + Assert.assertTrue(json.contains("\"token\":\"tok\"")); + Assert.assertTrue(json.contains("\"error\":null")); + } +} diff --git a/pom.xml b/pom.xml index 205b5067..50d2382e 100644 --- a/pom.xml +++ b/pom.xml @@ -5,9 +5,9 @@ 4.0.0 com.skyflow - skyflow-java - 2.1.1 - jar + skyflow + 1.0.0 + pom ${project.groupId}:${project.artifactId} Skyflow SDK for the Java programming language @@ -25,6 +25,11 @@ skyflow + + common + skyvault + flowvault + scm:git:git://github.com:skyflowapi/skyflow-java.git scm:git:ssh://github.com:skyflowapi/skyflow-java.git @@ -32,20 +37,22 @@ + true 8 8 4.12.0 2.10.1 UTF-8 4.13.2 - ${project.version} + 2.3.1 + 1.3.5 com.fasterxml.jackson.core jackson-databind - 2.18.6 + 2.17.2 compile @@ -64,7 +71,7 @@ io.github.cdimascio dotenv-java - 3.2.0 + 2.2.0 com.google.code.gson @@ -89,7 +96,6 @@ 4.13.2 test - org.powermock powermock-module-junit4 @@ -105,12 +111,6 @@ - - - src/main/resources - true - - org.apache.maven.plugins @@ -131,7 +131,8 @@ 3.2.0 - com.skyflow.generated.rest.* + com.skyflow.generated.rest.*: + com.skyflow.generated.auth.*: @@ -140,6 +141,44 @@ jar + + + + + + + org.apache.maven.plugins + maven-resources-plugin + 3.3.1 + + + copy-root-dotenv-for-tests + process-test-resources + + copy-resources + + + ${project.basedir} + true + + + ${maven.multiModuleProjectDirectory} + + .env + + false + + + @@ -163,28 +202,6 @@ - - org.apache.maven.plugins - maven-checkstyle-plugin - 3.3.1 - - checkstyle.xml - - false - true - - **/generated/** - - - - validate - validate - - check - - - - org.jacoco jacoco-maven-plugin @@ -255,50 +272,4 @@ https://repo.maven.apache.org/maven2/ - - - - maven-central - - - central - https://central.sonatype.com/api/v1/publisher/upload - - - central-snapshots - https://central.sonatype.com/api/v1/publisher/upload - - - - - - org.sonatype.central - central-publishing-maven-plugin - 0.4.0 - true - - central - true - true - - - - - - - jfrog - - - central - prekarilabs.jfrog.io-releases - https://prekarilabs.jfrog.io/artifactory/skyflow-java - - - snapshots - prekarilabs.jfrog.io-snapshots - https://prekarilabs.jfrog.io/artifactory/skyflow-java - - - - - + \ No newline at end of file diff --git a/scripts/bump_version.sh b/scripts/bump_version.sh index d729ea63..ccb538ba 100755 --- a/scripts/bump_version.sh +++ b/scripts/bump_version.sh @@ -1,7 +1,8 @@ # Input Arguments Version=$1 CommitHash=$2 -PomFile="$GITHUB_WORKSPACE/pom.xml" +Module=$3 +PomFile="$GITHUB_WORKSPACE/$Module/pom.xml" if [ -z "$Version" ]; then echo "Error: Version argument is required." @@ -14,27 +15,29 @@ if [ -z "$CommitHash" ]; then awk -v version="$Version" ' BEGIN { updated = 0 } + //,/<\/parent>/ { print; next } // && updated == 0 { sub(/.*<\/version>/, "" version "") updated = 1 } { print } - ' "$PomFile" > tempfile && cat tempfile > "$PomFile" && rm -f tempfile + ' "$PomFile" > tempfile && cat tempfile > "$PomFile" && rm -f tempfile echo "--------------------------" echo "Done. Main project version now at $Version" else echo "Bumping main project version to $Version-dev-$CommitHash" - awk -v version="$Version-dev.$CommitHash" ' - BEGIN { updated = 0 } - // && updated == 0 { - sub(/.*<\/version>/, "" version "") - updated = 1 - } - { print } - ' "$PomFile" > tempfile && cat tempfile > "$PomFile" && rm -f tempfile + awk -v version="$Version${CommitHash:+-dev.$CommitHash}" ' + BEGIN { updated = 0 } + //,/<\/parent>/ { print; next } + // && updated == 0 { + sub(/.*<\/version>/, "" version "") + updated = 1 + } + { print } + ' "$PomFile" > tempfile && cat tempfile > "$PomFile" && rm -f tempfile - echo "--------------------------" - echo "Done. Main project version now at $Version-dev.$CommitHash" +echo "--------------------------" +echo "Done. $Module module version now at $Version${CommitHash:+-dev.$CommitHash}" fi diff --git a/scripts/contract-snapshot-update.sh b/scripts/contract-snapshot-update.sh new file mode 100755 index 00000000..bd33397b --- /dev/null +++ b/scripts/contract-snapshot-update.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# Regenerates the skyvault contract-testing baseline (api-report/skyflow-java.baseline.jar) +# from the CURRENT working tree and overwrites the committed snapshot. +# +# Run this after an intentional public API change to v2, review the resulting +# git diff on the jar (a new binary blob) alongside your code change, and commit +# both together. This is the only way the committed baseline should ever change - +# japicmp never pulls a published version for this comparison. +set -euo pipefail + +cd "$(dirname "$0")/.." + +mvn -B package -pl common,v2 -am -DskipTests -Dmaven.javadoc.skip=true -Dgpg.skip=true + +SHADED_JAR=$(ls skyvault/target/skyflow-java-*-with-common.jar | head -n1) + +if [ -z "$SHADED_JAR" ]; then + echo "Error: could not find a built skyvault/target/skyflow-java-*-with-common.jar. Did the build succeed?" + exit 1 +fi + +mkdir -p skyvault/api-report +cp "$SHADED_JAR" skyvault/api-report/skyflow-java.baseline.jar + +echo "--------------------------" +echo "Updated skyvault/api-report/skyflow-java.baseline.jar from $SHADED_JAR" +echo "Review the diff and commit this file alongside your API change." diff --git a/skyvault/api-report/skyflow-java.baseline.jar b/skyvault/api-report/skyflow-java.baseline.jar new file mode 100644 index 00000000..6911c011 Binary files /dev/null and b/skyvault/api-report/skyflow-java.baseline.jar differ diff --git a/skyvault/pom.xml b/skyvault/pom.xml new file mode 100644 index 00000000..5e632019 --- /dev/null +++ b/skyvault/pom.xml @@ -0,0 +1,187 @@ + + + 4.0.0 + + com.skyflow + skyflow + 1.0.0 + ../pom.xml + + + skyflow-java + 3.0.0-beta.13-dev.f842cd2d + com.skyflow + + + false + 8 + 8 + UTF-8 + ${project.version} + + + + + com.skyflow + common + 1.0.0 + + + + + + + + src/main/resources + true + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.6.0 + + + package + + shade + + + + + com.skyflow:common + + + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.6.0 + + + shade-for-japicmp + package + + shade + + + true + with-common + + + com.skyflow:common + + + + + + + + com.github.siom79.japicmp + japicmp-maven-plugin + 0.26.0 + + + + + ${project.basedir}/api-report/skyflow-java.baseline.jar + + + + + ${project.build.directory}/${project.build.finalName}-with-common.jar + + + + protected + true + false + true + true + + com.skyflow.generated.* + com.skyflow.utils.* + + false + false + + + + + default-cli + verify + + cmp + + + + + + + + + jfrog + + + central + prekarilabs.jfrog.io-releases + https://prekarilabs.jfrog.io/artifactory/skyflow-java + + + snapshots + prekarilabs.jfrog.io-snapshots + https://prekarilabs.jfrog.io/artifactory/skyflow-java + + + + + maven-central + + + central + https://central.sonatype.com/api/v1/publisher/upload + + + central-snapshots + https://central.sonatype.com/api/v1/publisher/upload + + + + + + org.sonatype.central + central-publishing-maven-plugin + 0.4.0 + true + + central + true + true + + + + + + + diff --git a/skyvault/samples/README.md b/skyvault/samples/README.md new file mode 100644 index 00000000..666b2186 --- /dev/null +++ b/skyvault/samples/README.md @@ -0,0 +1,84 @@ +# Java SDK samples +Test the SDK by adding `VAULT-ID`, `VAULT-URL`, and `SERVICE-ACCOUNT` details in the required places for each sample. + +## Prerequisites +- A Skyflow account. If you don't have one, register for one on the [Try Skyflow](https://skyflow.com/try-skyflow) page. +- Java 1.8 or higher. + +### Create the vault +1. In a browser, sign in to Skyflow Studio. +2. Create a vault by clicking **Create Vault** > **Start With a Template** > **Quickstart vault**. +3. Once the vault is ready, click the gear icon and select **Edit Vault Details**. +4. Note your **Vault URL** and **Vault ID** values, then click **Cancel**. You'll need these later. + +### Create a service account +1. In the side navigation click, **IAM** > **Service Accounts** > **New Service Account**. +2. For **Name**, enter "SDK Sample". For **Roles**, choose **Vault Editor**. +3. Click **Create**. Your browser downloads a **credentials.json** file. Keep this file secure, as You'll need it for each of the samples. + +## The samples +### Detokenize +Detokenize a data token from the vault. Make sure the specified token is for data that exists in the vault. If you need a valid token, use [InsertExample.java](src/main/java/com/example/InsertExample.java) to insert the data, then use this data's token for detokenization. +#### [Configure](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/DetokenizeExample.java) +1. Replace **** with **VAULT ID** +2. Replace **** with **VAULT URL** +3. Replace **** with **Data Token**. +4. Replace **** with relative path of **SERVICE ACCOUNT CREDENTIAL FILE**.See #Create a service account. +#### Run the sample + + javac DetokenizeExample.java + java DetokenizeExample +### Get a record by ID +Get data using skyflow id. +#### Configure +1. Replace **** with **VAULT ID** +2. Replace **** with **VAULT URL**. +3. Replace **** with **Skyflow id**. +4. Replace **** with relative path of **SERVICE ACCOUNT CREDENTIAL FILE**. See #Create a Service Account. +5. Replace **** with **credit_cards**. +#### Run the sample + + javac GetByIdExample.java + java GetByIdExample +### Insert data into a vault +Insert data in the vault. +#### [Configure](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/InsertExample.java) +1. Replace **** with **VAULT ID**. +2. Replace **** with **VAULT URL**. +3. Replace **** with relative path of **SERVICE ACCOUNT CREDENTIAL FILE**. +4. Replace **** with **credit_cards**. +5. Replace **** with **column name**. +6. Replace **** with **valid value corresponding to column name**. +#### Run the sample + + javac InsertExample.java + java InsertExample +### Invoke a connection +Skyflow Connections is a gateway service that uses Skyflow's underlying tokenization capabilities to securely connect to first-party and third-party services. This way, your infrastructure is never directly exposed to sensitive data, and you offload security and compliance requirements to Skyflow. +#### Configure +1. Replace **** with **VAULT ID**. +2. Replace **** with **VAULT URL**. +3. Replace **** with relative path of **SERVICE ACCOUNT CREDENTIAL FILE**. +4. Replace **** with **Connection url**. +5. Replace **** with **Path param key**. +6. Replace **** with **Path param value**. +7. Replace **** with **Query param key**. +8. Replace **** with **Query param value**. +9. Replace **** with **Request header key**. +10. Replace **** with **Request header value**. +11. Replace **** with **Request body key**. +12. Replace **** with **Request body value**. +#### Run the sample + + javac InvokeConnectionExample.java + java InvokeConnectionExample + +### Generate a service account bearer token +Generates a bearer token using a file path and content of a service account credentials file. +#### [Configure](https://github.com/skyflowapi/skyflow-java/blob/main/samples/src/main/java/com/example/TokenGenerationExample.java) +1. Replace **** with relative path of **SERVICE ACCOUNT CREDENTIAL FILE PATH**. See #Create a service account. +2. Replace **<>** with relative path of **SERVICE ACCOUNT CREDENTIAL FILE CONTENT AS STRING**. +#### Run the sample + + javac TokenGenerationExample.java + java TokenGenerationExample diff --git a/samples/pom.xml b/skyvault/samples/pom.xml similarity index 100% rename from samples/pom.xml rename to skyvault/samples/pom.xml diff --git a/samples/src/main/java/com/example/connection/InvokeConnectionExample.java b/skyvault/samples/src/main/java/com/example/connection/InvokeConnectionExample.java similarity index 100% rename from samples/src/main/java/com/example/connection/InvokeConnectionExample.java rename to skyvault/samples/src/main/java/com/example/connection/InvokeConnectionExample.java diff --git a/samples/src/main/java/com/example/detect/DeidentifyFileExample.java b/skyvault/samples/src/main/java/com/example/detect/DeidentifyFileExample.java similarity index 100% rename from samples/src/main/java/com/example/detect/DeidentifyFileExample.java rename to skyvault/samples/src/main/java/com/example/detect/DeidentifyFileExample.java diff --git a/samples/src/main/java/com/example/detect/DeidentifyFileExampleAsync.java b/skyvault/samples/src/main/java/com/example/detect/DeidentifyFileExampleAsync.java similarity index 100% rename from samples/src/main/java/com/example/detect/DeidentifyFileExampleAsync.java rename to skyvault/samples/src/main/java/com/example/detect/DeidentifyFileExampleAsync.java diff --git a/samples/src/main/java/com/example/detect/DeidentifyTextExample.java b/skyvault/samples/src/main/java/com/example/detect/DeidentifyTextExample.java similarity index 100% rename from samples/src/main/java/com/example/detect/DeidentifyTextExample.java rename to skyvault/samples/src/main/java/com/example/detect/DeidentifyTextExample.java diff --git a/samples/src/main/java/com/example/detect/GetDetectRunExample.java b/skyvault/samples/src/main/java/com/example/detect/GetDetectRunExample.java similarity index 100% rename from samples/src/main/java/com/example/detect/GetDetectRunExample.java rename to skyvault/samples/src/main/java/com/example/detect/GetDetectRunExample.java diff --git a/samples/src/main/java/com/example/detect/ReidentifyTextExample.java b/skyvault/samples/src/main/java/com/example/detect/ReidentifyTextExample.java similarity index 100% rename from samples/src/main/java/com/example/detect/ReidentifyTextExample.java rename to skyvault/samples/src/main/java/com/example/detect/ReidentifyTextExample.java diff --git a/skyvault/samples/src/main/java/com/example/serviceaccount/BearerTokenExpiryExample.java b/skyvault/samples/src/main/java/com/example/serviceaccount/BearerTokenExpiryExample.java new file mode 100644 index 00000000..48889218 --- /dev/null +++ b/skyvault/samples/src/main/java/com/example/serviceaccount/BearerTokenExpiryExample.java @@ -0,0 +1,88 @@ +package com.example.serviceaccount; + +import com.skyflow.Skyflow; +import com.skyflow.config.Credentials; +import com.skyflow.config.VaultConfig; +import com.skyflow.enums.Env; +import com.skyflow.enums.LogLevel; +import com.skyflow.enums.RedactionType; +import com.skyflow.errors.SkyflowException; +import com.skyflow.vault.tokens.DetokenizeData; +import com.skyflow.vault.tokens.DetokenizeRequest; +import com.skyflow.vault.tokens.DetokenizeResponse; +import io.github.cdimascio.dotenv.Dotenv; +import java.util.ArrayList; + +/** + * This example demonstrates how to configure and use the Skyflow SDK + * to detokenize sensitive data stored in a Skyflow vault. + * It includes setting up credentials, configuring the vault, and + * making a detokenization request. The code also implements a retry + * mechanism to handle unauthorized access errors (HTTP 401). + */ +public class BearerTokenExpiryExample { + public static void main(String[] args) { + try { + // Setting up credentials for accessing the Skyflow vault + Credentials vaultCredentials = new Credentials(); + vaultCredentials.setCredentialsString(""); + + // Configuring the Skyflow vault with necessary details + VaultConfig vaultConfig = new VaultConfig(); + vaultConfig.setVaultId(""); // Vault ID + vaultConfig.setClusterId(""); // Cluster ID + vaultConfig.setEnv(Env.PROD); // Environment (e.g., DEV, PROD) + vaultConfig.setCredentials(vaultCredentials); // Setting credentials + + // Creating a Skyflow client instance with the configured vault + Skyflow skyflowClient = Skyflow.builder() + .setLogLevel(LogLevel.ERROR) // Setting log level to ERROR + .addVaultConfig(vaultConfig) // Adding vault configuration + .build(); + + // Attempting to detokenize data using the Skyflow client + try { + detokenizeData(skyflowClient); + } catch (SkyflowException e) { + // Retry detokenization if the error is due to unauthorized access (HTTP 401) + if (e.getHttpCode() == 401) { + detokenizeData(skyflowClient); + } else { + // Rethrow the exception for other error codes + throw e; + } + } + } catch (SkyflowException e) { + // Handling any exceptions that occur during the process + System.out.println("An error occurred: " + e.getMessage()); + } + } + + /** + * Method to detokenize data using the Skyflow client. + * It sends a detokenization request with a list of tokens and prints the response. + * + * @param skyflowClient The Skyflow client instance used for detokenization. + * @throws SkyflowException If an error occurs during the detokenization process. + */ + public static void detokenizeData(Skyflow skyflowClient) throws SkyflowException { + // Creating a list of tokens to be detokenized + DetokenizeData detokenizeDataToken1 = new DetokenizeData("", RedactionType.PLAIN_TEXT); + DetokenizeData detokenizeDataToken2 = new DetokenizeData(""); + ArrayList detokenizeDataList = new ArrayList<>(); + detokenizeDataList.add(detokenizeDataToken1); // First token + detokenizeDataList.add(detokenizeDataToken2); // Second token + + // Building a detokenization request with the token list and configuration + DetokenizeRequest detokenizeRequest = DetokenizeRequest.builder() + .detokenizeData(detokenizeDataList) // Adding tokens to the request + .continueOnError(false) // Stop on error + .build(); + + // Sending the detokenization request and receiving the response + DetokenizeResponse detokenizeResponse = skyflowClient.vault().detokenize(detokenizeRequest); + + // Printing the detokenized response + System.out.println(detokenizeResponse); + } +} diff --git a/skyvault/samples/src/main/java/com/example/serviceaccount/BearerTokenGenerationExample.java b/skyvault/samples/src/main/java/com/example/serviceaccount/BearerTokenGenerationExample.java new file mode 100644 index 00000000..c4578545 --- /dev/null +++ b/skyvault/samples/src/main/java/com/example/serviceaccount/BearerTokenGenerationExample.java @@ -0,0 +1,66 @@ +package com.example.serviceaccount; + +import com.skyflow.errors.SkyflowException; +import com.skyflow.serviceaccount.util.BearerToken; +import com.skyflow.serviceaccount.util.Token; + +import java.io.File; + +/** + * Example program to generate a Bearer Token using Skyflow's BearerToken utility. + * The token can be generated in two ways: + * 1. Using the file path to a credentials.json file. + * 2. Using the JSON content of the credentials file as a string. + */ +public class BearerTokenGenerationExample { + public static void main(String[] args) { + // Variable to store the generated token + String token = null; + + // Example 1: Generate Bearer Token using a credentials.json file + try { + // Specify the full file path to the credentials.json file + String filePath = ""; + + // Check if the token is either not initialized or has expired + if (Token.isExpired(token)) { + // Create a BearerToken object using the credentials file + BearerToken bearerToken = BearerToken.builder() + .setCredentials(new File(filePath)) // Set credentials from the file path + .build(); + + // Generate a new Bearer Token + token = bearerToken.getBearerToken(); + } + + // Print the generated Bearer Token to the console + System.out.println("Generated Bearer Token (from file): " + token); + } catch (SkyflowException e) { + // Handle any exceptions encountered during the token generation process + e.printStackTrace(); + } + + // Example 2: Generate Bearer Token using the credentials JSON as a string + try { + // Provide the credentials JSON content as a string + String fileContents = ""; + + // Check if the token is either not initialized or has expired + if (Token.isExpired(token)) { + // Create a BearerToken object using the credentials string + BearerToken bearerToken = BearerToken.builder() + .setCredentials(fileContents) // Set credentials from the string + .build(); + + // Generate a new Bearer Token + token = bearerToken.getBearerToken(); + } + + // Print the generated Bearer Token to the console + System.out.println("Generated Bearer Token (from string): " + token); + } catch (SkyflowException e) { + // Handle any exceptions encountered during the token generation process + e.printStackTrace(); + } + } +} diff --git a/skyvault/samples/src/main/java/com/example/serviceaccount/BearerTokenGenerationUsingThreadsExample.java b/skyvault/samples/src/main/java/com/example/serviceaccount/BearerTokenGenerationUsingThreadsExample.java new file mode 100644 index 00000000..30d8f9ba --- /dev/null +++ b/skyvault/samples/src/main/java/com/example/serviceaccount/BearerTokenGenerationUsingThreadsExample.java @@ -0,0 +1,72 @@ +package com.example.serviceaccount; + +import com.skyflow.errors.SkyflowException; +import com.skyflow.serviceaccount.util.BearerToken; + +import java.io.File; + +/** + * This example demonstrates how to generate Bearer tokens in two different ways: + * 1. Using a credentials file specified by its file path. + * 2. Using the credentials as a string. + *

+ * The code also showcases multithreaded token generation with a shared context (`ctx`), + * where each thread generates and prints tokens repeatedly. + */ +public class BearerTokenGenerationUsingThreadsExample { + public static void main(String[] args) { + // Example 1: Generate Bearer token using a credentials file path + try { + // Step 1: Specify the path to the credentials file + String filePath = ""; // Replace with the actual file path + + // Step 2: Create a BearerToken object using the file path + final BearerToken bearerToken = BearerToken.builder() + .setCredentials(new File(filePath)) // Provide the credentials file + .setCtx("abc") // Specify a context string ("abc" in this case) + .build(); + + // Step 3: Create and start a thread to repeatedly generate and print tokens + Thread t = new Thread(() -> { + for (int i = 0; i < 5; i++) { // Loop to generate tokens 5 times + try { + System.out.println(bearerToken.getBearerToken()); // Print the Bearer token + } catch (SkyflowException e) { // Handle exceptions during token generation + Thread.currentThread().interrupt(); // Interrupt the thread on error + throw new RuntimeException(e); // Wrap and propagate the exception + } + } + }); + t.start(); // Start the thread + } catch (Exception e) { // Handle exceptions during BearerToken creation + e.printStackTrace(); + } + + // Example 2: Generate Bearer token using credentials as a string + try { + // Step 1: Specify the credentials as a string (file contents) + String fileContents = ""; // Replace with actual file contents + + // Step 2: Create a BearerToken object using the credentials string + final BearerToken bearerToken = BearerToken.builder() + .setCredentials(fileContents) // Provide the credentials as a string + .setCtx("abc") // Specify a context string ("abc" in this case) + .build(); + + // Step 3: Create and start a thread to repeatedly generate and print tokens + Thread t = new Thread(() -> { + for (int i = 0; i < 5; i++) { // Loop to generate tokens 5 times + try { + System.out.println(bearerToken.getBearerToken()); // Print the Bearer token + } catch (SkyflowException e) { // Handle exceptions during token generation + Thread.currentThread().interrupt(); // Interrupt the thread on error + throw new RuntimeException(e); // Wrap and propagate the exception + } + } + }); + t.start(); // Start the thread + } catch (Exception e) { // Handle exceptions during BearerToken creation + e.printStackTrace(); + } + } +} diff --git a/skyvault/samples/src/main/java/com/example/serviceaccount/BearerTokenGenerationWithContextExample.java b/skyvault/samples/src/main/java/com/example/serviceaccount/BearerTokenGenerationWithContextExample.java new file mode 100644 index 00000000..fcb2a407 --- /dev/null +++ b/skyvault/samples/src/main/java/com/example/serviceaccount/BearerTokenGenerationWithContextExample.java @@ -0,0 +1,73 @@ +package com.example.serviceaccount; + +import com.skyflow.errors.SkyflowException; +import com.skyflow.serviceaccount.util.BearerToken; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +/** + * Example program to generate a Bearer Token using Skyflow's BearerToken utility. + * The token is generated using three approaches: + * 1. By providing a string context. + * 2. By providing a JSON object context (Map) for conditional data access policies. + * 3. By providing the credentials as a string with context. + */ +public class BearerTokenGenerationWithContextExample { + public static void main(String[] args) { + String bearerToken = null; + + // Approach 1: Bearer token with string context + // Use a simple string identifier when your policy references a single context value. + try { + String filePath = ""; + BearerToken token = BearerToken.builder() + .setCredentials(new File(filePath)) + .setCtx("user_12345") + .build(); + + bearerToken = token.getBearerToken(); + System.out.println("Bearer token (string context): " + bearerToken); + } catch (SkyflowException e) { + e.printStackTrace(); + } + + // Approach 2: Bearer token with JSON object context + // Use a structured Map when your policy needs multiple context values. + // Each key maps to a Skyflow CEL policy variable under request.context.* + // For example, the map below enables policies like: + // request.context.role == "admin" && request.context.department == "finance" + try { + String filePath = ""; + Map ctx = new HashMap<>(); + ctx.put("role", "admin"); + ctx.put("department", "finance"); + ctx.put("user_id", "user_12345"); + + BearerToken token = BearerToken.builder() + .setCredentials(new File(filePath)) + .setCtx(ctx) + .build(); + + bearerToken = token.getBearerToken(); + System.out.println("Bearer token (object context): " + bearerToken); + } catch (SkyflowException e) { + e.printStackTrace(); + } + + // Approach 3: Bearer token with string context from credentials string + try { + String fileContents = ""; + BearerToken token = BearerToken.builder() + .setCredentials(fileContents) + .setCtx("user_12345") + .build(); + + bearerToken = token.getBearerToken(); + System.out.println("Bearer token (creds string): " + bearerToken); + } catch (SkyflowException e) { + e.printStackTrace(); + } + } +} diff --git a/skyvault/samples/src/main/java/com/example/serviceaccount/ScopedTokenGenerationExample.java b/skyvault/samples/src/main/java/com/example/serviceaccount/ScopedTokenGenerationExample.java new file mode 100644 index 00000000..3cba8bd5 --- /dev/null +++ b/skyvault/samples/src/main/java/com/example/serviceaccount/ScopedTokenGenerationExample.java @@ -0,0 +1,66 @@ +package com.example.serviceaccount; + +import com.skyflow.errors.SkyflowException; +import com.skyflow.serviceaccount.util.BearerToken; + +import java.io.File; +import java.util.ArrayList; + +/** + * This example demonstrates how to generate a Scoped Bearer Token in two ways: + * 1. Using a credentials file specified by its file path. + * 2. Using the credentials as a string. + *

+ * Scoped tokens are generated by assigning specific roles for access control. + */ +public class ScopedTokenGenerationExample { + public static void main(String[] args) { + String scopedToken = null; // Variable to store the generated Scoped Bearer Token + + // Example 1: Generate Scoped Token using a credentials file path + try { + // Step 1: Specify the roles required for the scoped token + ArrayList roles = new ArrayList<>(); + roles.add("YOUR_ROLE_ID"); // Replace with your actual role ID + + // Step 2: Specify the path to the credentials file + String filePath = ""; // Replace with the actual file path + + // Step 3: Create a BearerToken object using the file path and roles + BearerToken bearerToken = BearerToken.builder() + .setCredentials(new File(filePath)) // Provide the credentials file + .setRoles(roles) // Set the roles for the scoped token + .build(); + + // Step 4: Generate and print the Scoped Bearer Token + scopedToken = bearerToken.getBearerToken(); + System.out.println("Scoped Token (using file path): " + scopedToken); + } catch (SkyflowException e) { // Handle exceptions during token generation + System.out.println("Error occurred while generating Scoped Token using file path:"); + e.printStackTrace(); + } + + // Example 2: Generate Scoped Token using credentials as a string + try { + // Step 1: Specify the roles required for the scoped token + ArrayList roles = new ArrayList<>(); + roles.add("YOUR_ROLE_ID"); // Replace with your actual role ID + + // Step 2: Specify the credentials as a string (file contents) + String fileContents = ""; // Replace with actual file contents + + // Step 3: Create a BearerToken object using the credentials string and roles + BearerToken bearerToken = BearerToken.builder() + .setCredentials(fileContents) // Provide the credentials as a string + .setRoles(roles) // Set the roles for the scoped token + .build(); + + // Step 4: Generate and print the Scoped Bearer Token + scopedToken = bearerToken.getBearerToken(); + System.out.println("Scoped Token (using credentials string): " + scopedToken); + } catch (SkyflowException e) { // Handle exceptions during token generation + System.out.println("Error occurred while generating Scoped Token using credentials string:"); + e.printStackTrace(); + } + } +} diff --git a/skyvault/samples/src/main/java/com/example/serviceaccount/SignedTokenGenerationExample.java b/skyvault/samples/src/main/java/com/example/serviceaccount/SignedTokenGenerationExample.java new file mode 100644 index 00000000..517d5cc8 --- /dev/null +++ b/skyvault/samples/src/main/java/com/example/serviceaccount/SignedTokenGenerationExample.java @@ -0,0 +1,89 @@ +package com.example.serviceaccount; + +import com.skyflow.errors.SkyflowException; +import com.skyflow.serviceaccount.util.SignedDataTokenResponse; +import com.skyflow.serviceaccount.util.SignedDataTokens; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * This example demonstrates how to generate Signed Data Tokens using: + * 1. String context. + * 2. JSON object context (Map) for conditional data access policies. + * 3. Credentials string with context. + */ +public class SignedTokenGenerationExample { + public static void main(String[] args) { + List signedTokenValues; + + // Example 1: Signed data tokens with string context + try { + String filePath = ""; + String context = "user_12345"; + ArrayList dataTokens = new ArrayList<>(); + dataTokens.add("YOUR_DATA_TOKEN_1"); + + SignedDataTokens signedToken = SignedDataTokens.builder() + .setCredentials(new File(filePath)) + .setCtx(context) + .setTimeToLive(30) + .setDataTokens(dataTokens) + .build(); + + signedTokenValues = signedToken.getSignedDataTokens(); + System.out.println("Signed Tokens (string context): " + signedTokenValues); + } catch (SkyflowException e) { + e.printStackTrace(); + } + + // Example 2: Signed data tokens with JSON object context + // Each key maps to a Skyflow CEL policy variable under request.context.* + // For example: request.context.role == "analyst" && request.context.department == "research" + try { + String filePath = ""; + Map ctx = new HashMap<>(); + ctx.put("role", "analyst"); + ctx.put("department", "research"); + ctx.put("user_id", "user_67890"); + + ArrayList dataTokens = new ArrayList<>(); + dataTokens.add("YOUR_DATA_TOKEN_1"); + + SignedDataTokens signedToken = SignedDataTokens.builder() + .setCredentials(new File(filePath)) + .setCtx(ctx) + .setTimeToLive(30) + .setDataTokens(dataTokens) + .build(); + + signedTokenValues = signedToken.getSignedDataTokens(); + System.out.println("Signed Tokens (object context): " + signedTokenValues); + } catch (SkyflowException e) { + e.printStackTrace(); + } + + // Example 3: Signed data tokens from credentials string + try { + String fileContents = ""; + String context = "user_12345"; + ArrayList dataTokens = new ArrayList<>(); + dataTokens.add("YOUR_DATA_TOKEN_1"); + + SignedDataTokens signedToken = SignedDataTokens.builder() + .setCredentials(fileContents) + .setCtx(context) + .setTimeToLive(30) + .setDataTokens(dataTokens) + .build(); + + signedTokenValues = signedToken.getSignedDataTokens(); + System.out.println("Signed Tokens (creds string): " + signedTokenValues); + } catch (SkyflowException e) { + e.printStackTrace(); + } + } +} diff --git a/samples/src/main/java/com/example/vault/ClientOperations.java b/skyvault/samples/src/main/java/com/example/vault/ClientOperations.java similarity index 100% rename from samples/src/main/java/com/example/vault/ClientOperations.java rename to skyvault/samples/src/main/java/com/example/vault/ClientOperations.java diff --git a/samples/src/main/java/com/example/vault/CredentialsOptions.java b/skyvault/samples/src/main/java/com/example/vault/CredentialsOptions.java similarity index 100% rename from samples/src/main/java/com/example/vault/CredentialsOptions.java rename to skyvault/samples/src/main/java/com/example/vault/CredentialsOptions.java diff --git a/samples/src/main/java/com/example/vault/DeleteExample.java b/skyvault/samples/src/main/java/com/example/vault/DeleteExample.java similarity index 100% rename from samples/src/main/java/com/example/vault/DeleteExample.java rename to skyvault/samples/src/main/java/com/example/vault/DeleteExample.java diff --git a/samples/src/main/java/com/example/vault/DetokenizeExample.java b/skyvault/samples/src/main/java/com/example/vault/DetokenizeExample.java similarity index 100% rename from samples/src/main/java/com/example/vault/DetokenizeExample.java rename to skyvault/samples/src/main/java/com/example/vault/DetokenizeExample.java diff --git a/samples/src/main/java/com/example/vault/FileUploadExample.java b/skyvault/samples/src/main/java/com/example/vault/FileUploadExample.java similarity index 100% rename from samples/src/main/java/com/example/vault/FileUploadExample.java rename to skyvault/samples/src/main/java/com/example/vault/FileUploadExample.java diff --git a/samples/src/main/java/com/example/vault/GetExample.java b/skyvault/samples/src/main/java/com/example/vault/GetExample.java similarity index 100% rename from samples/src/main/java/com/example/vault/GetExample.java rename to skyvault/samples/src/main/java/com/example/vault/GetExample.java diff --git a/samples/src/main/java/com/example/vault/InsertExample.java b/skyvault/samples/src/main/java/com/example/vault/InsertExample.java similarity index 100% rename from samples/src/main/java/com/example/vault/InsertExample.java rename to skyvault/samples/src/main/java/com/example/vault/InsertExample.java diff --git a/samples/src/main/java/com/example/vault/QueryExample.java b/skyvault/samples/src/main/java/com/example/vault/QueryExample.java similarity index 100% rename from samples/src/main/java/com/example/vault/QueryExample.java rename to skyvault/samples/src/main/java/com/example/vault/QueryExample.java diff --git a/samples/src/main/java/com/example/vault/TokenizeExample.java b/skyvault/samples/src/main/java/com/example/vault/TokenizeExample.java similarity index 100% rename from samples/src/main/java/com/example/vault/TokenizeExample.java rename to skyvault/samples/src/main/java/com/example/vault/TokenizeExample.java diff --git a/samples/src/main/java/com/example/vault/UpdateExample.java b/skyvault/samples/src/main/java/com/example/vault/UpdateExample.java similarity index 100% rename from samples/src/main/java/com/example/vault/UpdateExample.java rename to skyvault/samples/src/main/java/com/example/vault/UpdateExample.java diff --git a/samples/src/main/java/com/example/vault/deprecated/DetokenizeExample.java b/skyvault/samples/src/main/java/com/example/vault/deprecated/DetokenizeExample.java similarity index 100% rename from samples/src/main/java/com/example/vault/deprecated/DetokenizeExample.java rename to skyvault/samples/src/main/java/com/example/vault/deprecated/DetokenizeExample.java diff --git a/samples/src/main/java/com/example/vault/deprecated/GetExample.java b/skyvault/samples/src/main/java/com/example/vault/deprecated/GetExample.java similarity index 100% rename from samples/src/main/java/com/example/vault/deprecated/GetExample.java rename to skyvault/samples/src/main/java/com/example/vault/deprecated/GetExample.java diff --git a/samples/src/main/java/com/example/vault/deprecated/UpdateExample.java b/skyvault/samples/src/main/java/com/example/vault/deprecated/UpdateExample.java similarity index 100% rename from samples/src/main/java/com/example/vault/deprecated/UpdateExample.java rename to skyvault/samples/src/main/java/com/example/vault/deprecated/UpdateExample.java diff --git a/src/main/java/com/skyflow/ConnectionClient.java b/skyvault/src/main/java/com/skyflow/ConnectionClient.java similarity index 100% rename from src/main/java/com/skyflow/ConnectionClient.java rename to skyvault/src/main/java/com/skyflow/ConnectionClient.java diff --git a/skyvault/src/main/java/com/skyflow/Skyflow.java b/skyvault/src/main/java/com/skyflow/Skyflow.java new file mode 100644 index 00000000..66640f0e --- /dev/null +++ b/skyvault/src/main/java/com/skyflow/Skyflow.java @@ -0,0 +1,227 @@ +package com.skyflow; + +import com.skyflow.config.ConnectionConfig; +import com.skyflow.config.Credentials; +import com.skyflow.config.VaultConfig; +import com.skyflow.enums.LogLevel; +import com.skyflow.errors.ErrorCode; +import com.skyflow.errors.ErrorMessage; +import com.skyflow.errors.SkyflowException; +import com.skyflow.logs.ErrorLogs; +import com.skyflow.logs.InfoLogs; +import com.skyflow.utils.Utils; +import com.skyflow.utils.logger.LogUtil; +import com.skyflow.utils.validations.Validations; +import com.skyflow.vault.controller.ConnectionController; +import com.skyflow.vault.controller.DetectController; +import com.skyflow.vault.controller.VaultController; + +import java.util.LinkedHashMap; + +public final class Skyflow extends BaseSkyflow { + private final SkyflowClientBuilder builder; + + private Skyflow(SkyflowClientBuilder builder) { + super(builder); + this.builder = builder; + } + + @Override + protected Skyflow self() { + return this; + } + + public static SkyflowClientBuilder builder() { + return new SkyflowClientBuilder(); + } + + public Skyflow addConnectionConfig(ConnectionConfig connectionConfig) throws SkyflowException { + this.builder.addConnectionConfig(connectionConfig); + return this; + } + + public ConnectionConfig getConnectionConfig(String connectionId) { + return this.builder.connectionConfigMap.get(connectionId); + } + + public Skyflow updateConnectionConfig(ConnectionConfig connectionConfig) throws SkyflowException { + this.builder.updateConnectionConfig(connectionConfig); + return this; + } + + public Skyflow removeConnectionConfig(String connectionId) throws SkyflowException { + this.builder.removeConnectionConfig(connectionId); + return this; + } + + /** @deprecated Use {@link #setLogLevel(LogLevel)} instead. */ + @Deprecated(since = "2.1", forRemoval = true) + public Skyflow updateLogLevel(LogLevel logLevel) { + LogUtil.printWarningLog(InfoLogs.DEPRECATED_UPDATE_LOG_LEVEL.getLog()); + return setLogLevel(logLevel); + } + + public VaultController vault() throws SkyflowException { + return resolveOrThrow(this.builder.vaultClientsMap, null, ErrorLogs.VAULT_CONFIG_DOES_NOT_EXIST, ErrorMessage.VaultIdNotInConfigList); + } + + public VaultController vault(String vaultId) throws SkyflowException { + return resolveOrThrow(this.builder.vaultClientsMap, vaultId, ErrorLogs.VAULT_CONFIG_DOES_NOT_EXIST, ErrorMessage.VaultIdNotInConfigList); + } + + public ConnectionController connection() throws SkyflowException { + return resolveOrThrow(this.builder.connectionsMap, null, ErrorLogs.CONNECTION_CONFIG_DOES_NOT_EXIST, ErrorMessage.ConnectionIdNotInConfigList); + } + + public ConnectionController connection(String connectionId) throws SkyflowException { + return resolveOrThrow(this.builder.connectionsMap, connectionId, ErrorLogs.CONNECTION_CONFIG_DOES_NOT_EXIST, ErrorMessage.ConnectionIdNotInConfigList); + } + + public DetectController detect() throws SkyflowException { + return resolveOrThrow(this.builder.detectClientsMap, null, ErrorLogs.VAULT_CONFIG_DOES_NOT_EXIST, ErrorMessage.VaultIdNotInConfigList); + } + + public DetectController detect(String vaultId) throws SkyflowException { + return resolveOrThrow(this.builder.detectClientsMap, vaultId, ErrorLogs.VAULT_CONFIG_DOES_NOT_EXIST, ErrorMessage.VaultIdNotInConfigList); + } + + public static final class SkyflowClientBuilder extends BaseSkyflowClientBuilder { + private final LinkedHashMap vaultClientsMap = new LinkedHashMap<>(); + private final LinkedHashMap connectionsMap = new LinkedHashMap<>(); + private final LinkedHashMap detectClientsMap = new LinkedHashMap<>(); + private final LinkedHashMap connectionConfigMap = new LinkedHashMap<>(); + + @Override + protected void validateVaultConfig(VaultConfig vaultConfig) throws SkyflowException { + Validations.validateVaultConfig(vaultConfig); + } + + @Override + protected void onVaultConfigAdded(VaultConfig vaultConfig) throws SkyflowException { + this.vaultClientsMap.put(vaultConfig.getVaultId(), new VaultController(vaultConfig, this.skyflowCredentials)); + this.detectClientsMap.put(vaultConfig.getVaultId(), new DetectController(vaultConfig, this.skyflowCredentials)); + LogUtil.printInfoLog(Utils.parameterizedString(InfoLogs.VAULT_CONTROLLER_INITIALIZED.getLog(), vaultConfig.getVaultId())); + LogUtil.printInfoLog(Utils.parameterizedString(InfoLogs.DETECT_CONTROLLER_INITIALIZED.getLog(), vaultConfig.getVaultId())); + } + + @Override + protected void onVaultConfigUpdated(VaultConfig updatedConfig) throws SkyflowException { + this.vaultClientsMap.get(updatedConfig.getVaultId()).updateVaultConfig(updatedConfig); + } + + @Override + protected void onVaultConfigRemoved(String vaultId) throws SkyflowException { + this.vaultClientsMap.remove(vaultId); + } + + @Override + protected boolean hasVaultClient(String vaultId) { + return this.vaultClientsMap.containsKey(vaultId); + } + + @Override + protected void onCredentialsUpdated(Credentials credentials) throws SkyflowException { + for (VaultController vault : this.vaultClientsMap.values()) { + vault.setCommonCredentials(credentials); + } + for (DetectController detect : this.detectClientsMap.values()) { + detect.setCommonCredentials(credentials); + } + for (ConnectionController connection : this.connectionsMap.values()) { + connection.setCommonCredentials(credentials); + } + } + + public SkyflowClientBuilder addConnectionConfig(ConnectionConfig connectionConfig) throws SkyflowException { + LogUtil.printInfoLog(InfoLogs.VALIDATING_CONNECTION_CONFIG.getLog()); + Validations.validateConnectionConfig(connectionConfig); + if (this.connectionsMap.containsKey(connectionConfig.getConnectionId())) { + LogUtil.printErrorLog(Utils.parameterizedString( + ErrorLogs.CONNECTION_CONFIG_EXISTS.getLog(), connectionConfig.getConnectionId() + )); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), + ErrorMessage.ConnectionIdAlreadyInConfigList.getMessage()); + } else { + this.connectionConfigMap.put(connectionConfig.getConnectionId(), connectionConfig); + ConnectionController controller = new ConnectionController(connectionConfig, this.skyflowCredentials); + this.connectionsMap.put(connectionConfig.getConnectionId(), controller); + LogUtil.printInfoLog(Utils.parameterizedString( + InfoLogs.CONNECTION_CONTROLLER_INITIALIZED.getLog(), connectionConfig.getConnectionId())); + } + return this; + } + + public SkyflowClientBuilder updateConnectionConfig(ConnectionConfig connectionConfig) throws SkyflowException { + LogUtil.printInfoLog(InfoLogs.VALIDATING_CONNECTION_CONFIG.getLog()); + Validations.validateConnectionConfig(connectionConfig); + if (this.connectionsMap.containsKey(connectionConfig.getConnectionId())) { + ConnectionConfig updatedConfig = findAndUpdateConnectionConfig(connectionConfig); + this.connectionsMap.get(updatedConfig.getConnectionId()).updateConnectionConfig(connectionConfig); + } else { + LogUtil.printErrorLog(Utils.parameterizedString( + ErrorLogs.CONNECTION_CONFIG_DOES_NOT_EXIST.getLog(), connectionConfig.getConnectionId() + )); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), + ErrorMessage.ConnectionIdNotInConfigList.getMessage()); + } + return this; + } + + public SkyflowClientBuilder removeConnectionConfig(String connectionId) throws SkyflowException { + if (this.connectionsMap.containsKey(connectionId)) { + this.connectionsMap.remove(connectionId); + this.connectionConfigMap.remove(connectionId); + } else { + LogUtil.printErrorLog(Utils.parameterizedString( + ErrorLogs.CONNECTION_CONFIG_DOES_NOT_EXIST.getLog(), connectionId + )); + throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), + ErrorMessage.ConnectionIdNotInConfigList.getMessage()); + } + return this; + } + + @Override + public SkyflowClientBuilder addVaultConfig(VaultConfig vaultConfig) throws SkyflowException { + super.addVaultConfig(vaultConfig); + return this; + } + + @Override + public SkyflowClientBuilder updateVaultConfig(VaultConfig vaultConfig) throws SkyflowException { + super.updateVaultConfig(vaultConfig); + return this; + } + + @Override + public SkyflowClientBuilder removeVaultConfig(String vaultId) throws SkyflowException { + super.removeVaultConfig(vaultId); + return this; + } + + @Override + public SkyflowClientBuilder addSkyflowCredentials(Credentials credentials) throws SkyflowException { + super.addSkyflowCredentials(credentials); + return this; + } + + @Override + public SkyflowClientBuilder setLogLevel(LogLevel logLevel) { + super.setLogLevel(logLevel); + return this; + } + + public Skyflow build() { + return new Skyflow(this); + } + + private ConnectionConfig findAndUpdateConnectionConfig(ConnectionConfig connectionConfig) { + ConnectionConfig previousConfig = this.connectionConfigMap.get(connectionConfig.getConnectionId()); + String connectionURL = connectionConfig.getConnectionUrl() != null ? connectionConfig.getConnectionUrl() : previousConfig.getConnectionUrl(); + Credentials credentials = connectionConfig.getCredentials() != null ? connectionConfig.getCredentials() : previousConfig.getCredentials(); + previousConfig.setConnectionUrl(connectionURL); + previousConfig.setCredentials(credentials); + return previousConfig; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/skyflow/VaultClient.java b/skyvault/src/main/java/com/skyflow/VaultClient.java similarity index 91% rename from src/main/java/com/skyflow/VaultClient.java rename to skyvault/src/main/java/com/skyflow/VaultClient.java index 1d5e5d74..84289e8f 100644 --- a/src/main/java/com/skyflow/VaultClient.java +++ b/skyvault/src/main/java/com/skyflow/VaultClient.java @@ -26,12 +26,7 @@ import com.skyflow.generated.rest.resources.tokens.requests.V1TokenizePayload; import com.skyflow.generated.rest.types.Transformations; import com.skyflow.generated.rest.types.*; -import com.skyflow.logs.InfoLogs; -import com.skyflow.serviceaccount.util.Token; -import com.skyflow.utils.Constants; import com.skyflow.utils.Utils; -import com.skyflow.utils.logger.LogUtil; -import com.skyflow.utils.validations.Validations; import com.skyflow.vault.data.FileUploadRequest; import com.skyflow.vault.data.InsertRequest; import com.skyflow.vault.data.UpdateRequest; @@ -41,35 +36,20 @@ import com.skyflow.vault.tokens.DetokenizeData; import com.skyflow.vault.tokens.DetokenizeRequest; import com.skyflow.vault.tokens.TokenizeRequest; -import io.github.cdimascio.dotenv.Dotenv; -import io.github.cdimascio.dotenv.DotenvException; -import okhttp3.ConnectionPool; -import okhttp3.OkHttpClient; -import okhttp3.Request; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.util.*; -import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; -public class VaultClient { - private final VaultConfig vaultConfig; +public class VaultClient extends BaseVaultClient { private final ApiClientBuilder apiClientBuilder; private ApiClient apiClient; - private OkHttpClient sharedHttpClient; - private String currentVaultURL; - private Credentials commonCredentials; - private Credentials finalCredentials; - private String token; - private String apiKey; protected VaultClient(VaultConfig vaultConfig, Credentials credentials) { - super(); - this.vaultConfig = vaultConfig; - this.commonCredentials = credentials; + super(vaultConfig, credentials); this.apiClientBuilder = new ApiClientBuilder(); this.apiClient = null; updateVaultURL(); @@ -95,18 +75,19 @@ protected QueryClient getQueryApi() { return this.apiClient.query(); } - protected VaultConfig getVaultConfig() { - return vaultConfig; - } - protected void setCommonCredentials(Credentials commonCredentials) throws SkyflowException { this.commonCredentials = commonCredentials; - prioritiseCredentials(); + super.prioritiseCredentials(this.vaultConfig.getCredentials()); } protected void updateVaultConfig() throws SkyflowException { updateVaultURL(); - prioritiseCredentials(); + super.prioritiseCredentials(this.vaultConfig.getCredentials()); + } + + protected void updateVaultConfig(VaultConfig newConfig) throws SkyflowException { + this.vaultConfig = newConfig; + updateVaultConfig(); } protected V1DetokenizePayload getDetokenizePayload(DetokenizeRequest request) { @@ -233,19 +214,7 @@ protected File getFileForFileUpload(FileUploadRequest fileUploadRequest) throws } protected void setBearerToken() throws SkyflowException { - prioritiseCredentials(); - Validations.validateCredentials(this.finalCredentials); - if (this.finalCredentials.getApiKey() != null) { - LogUtil.printInfoLog(InfoLogs.REUSE_API_KEY.getLog()); - token = this.finalCredentials.getApiKey(); - } else if (token == null || token.trim().isEmpty()) { - token = Utils.generateBearerToken(this.finalCredentials); - } else if (Token.isExpired(token)) { - LogUtil.printInfoLog(InfoLogs.BEARER_TOKEN_EXPIRED.getLog()); - token = Utils.generateBearerToken(this.finalCredentials); - } else { - LogUtil.printInfoLog(InfoLogs.REUSE_BEARER_TOKEN.getLog()); - } + super.setBearerToken(this.vaultConfig.getCredentials()); if (apiClient == null) { updateExecutorInHTTP(); this.apiClient = this.apiClientBuilder.build(); @@ -838,48 +807,9 @@ private void updateVaultURL() { private void updateExecutorInHTTP() { if (sharedHttpClient == null) { - sharedHttpClient = new OkHttpClient.Builder() - .connectionPool(new ConnectionPool(10, 1, TimeUnit.MINUTES)) - .addInterceptor(chain -> { - Request requestWithAuth = chain.request().newBuilder() - .header("Authorization", "Bearer " + this.token) - .build(); - return chain.proceed(requestWithAuth); - }) - .build(); + sharedHttpClient = buildSharedHttpClient(() -> this.token); apiClientBuilder.httpClient(sharedHttpClient); } } - private void prioritiseCredentials() throws SkyflowException { - try { - Credentials original = this.finalCredentials; - if (this.vaultConfig.getCredentials() != null) { - this.finalCredentials = this.vaultConfig.getCredentials(); - } else if (this.commonCredentials != null) { - this.finalCredentials = this.commonCredentials; - } else { - Dotenv dotenv = Dotenv.load(); - String sysCredentials = dotenv.get(Constants.ENV_CREDENTIALS_KEY_NAME); - if (sysCredentials == null) { - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), - ErrorMessage.EmptyCredentials.getMessage()); - } else { - this.finalCredentials = new Credentials(); - this.finalCredentials.setCredentialsString(sysCredentials); - } - } - if (original != null && !original.equals(this.finalCredentials)) { - token = null; - apiKey = null; - } - } catch (DotenvException e) { - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), - ErrorMessage.EmptyCredentials.getMessage()); - } catch (SkyflowException e) { - throw e; - } catch (Exception e) { - throw new RuntimeException(e); - } - } -} +} \ No newline at end of file diff --git a/src/main/java/com/skyflow/config/ConnectionConfig.java b/skyvault/src/main/java/com/skyflow/config/ConnectionConfig.java similarity index 100% rename from src/main/java/com/skyflow/config/ConnectionConfig.java rename to skyvault/src/main/java/com/skyflow/config/ConnectionConfig.java diff --git a/src/main/java/com/skyflow/config/ManagementConfig.java b/skyvault/src/main/java/com/skyflow/config/ManagementConfig.java similarity index 100% rename from src/main/java/com/skyflow/config/ManagementConfig.java rename to skyvault/src/main/java/com/skyflow/config/ManagementConfig.java diff --git a/skyvault/src/main/java/com/skyflow/config/VaultConfig.java b/skyvault/src/main/java/com/skyflow/config/VaultConfig.java new file mode 100644 index 00000000..2abd6a27 --- /dev/null +++ b/skyvault/src/main/java/com/skyflow/config/VaultConfig.java @@ -0,0 +1,9 @@ +package com.skyflow.config; + +public class VaultConfig extends BaseVaultConfig { + + public VaultConfig() { + super(); + } + +} diff --git a/src/main/java/com/skyflow/enums/DeidentifyFileStatus.java b/skyvault/src/main/java/com/skyflow/enums/DeidentifyFileStatus.java similarity index 100% rename from src/main/java/com/skyflow/enums/DeidentifyFileStatus.java rename to skyvault/src/main/java/com/skyflow/enums/DeidentifyFileStatus.java diff --git a/src/main/java/com/skyflow/enums/DetectEntities.java b/skyvault/src/main/java/com/skyflow/enums/DetectEntities.java similarity index 100% rename from src/main/java/com/skyflow/enums/DetectEntities.java rename to skyvault/src/main/java/com/skyflow/enums/DetectEntities.java diff --git a/src/main/java/com/skyflow/enums/DetectOutputTranscriptions.java b/skyvault/src/main/java/com/skyflow/enums/DetectOutputTranscriptions.java similarity index 100% rename from src/main/java/com/skyflow/enums/DetectOutputTranscriptions.java rename to skyvault/src/main/java/com/skyflow/enums/DetectOutputTranscriptions.java diff --git a/skyvault/src/main/java/com/skyflow/enums/Env.java b/skyvault/src/main/java/com/skyflow/enums/Env.java new file mode 100644 index 00000000..3d1466e9 --- /dev/null +++ b/skyvault/src/main/java/com/skyflow/enums/Env.java @@ -0,0 +1,8 @@ +package com.skyflow.enums; + +public enum Env { + DEV, + STAGE, + SANDBOX, + PROD +} diff --git a/src/main/java/com/skyflow/enums/InterfaceName.java b/skyvault/src/main/java/com/skyflow/enums/InterfaceName.java similarity index 100% rename from src/main/java/com/skyflow/enums/InterfaceName.java rename to skyvault/src/main/java/com/skyflow/enums/InterfaceName.java diff --git a/skyvault/src/main/java/com/skyflow/enums/LogLevel.java b/skyvault/src/main/java/com/skyflow/enums/LogLevel.java new file mode 100644 index 00000000..a605b520 --- /dev/null +++ b/skyvault/src/main/java/com/skyflow/enums/LogLevel.java @@ -0,0 +1,9 @@ +package com.skyflow.enums; + +public enum LogLevel { + OFF, + ERROR, + WARN, + INFO, + DEBUG +} diff --git a/src/main/java/com/skyflow/enums/MaskingMethod.java b/skyvault/src/main/java/com/skyflow/enums/MaskingMethod.java similarity index 100% rename from src/main/java/com/skyflow/enums/MaskingMethod.java rename to skyvault/src/main/java/com/skyflow/enums/MaskingMethod.java diff --git a/src/main/java/com/skyflow/enums/RedactionType.java b/skyvault/src/main/java/com/skyflow/enums/RedactionType.java similarity index 100% rename from src/main/java/com/skyflow/enums/RedactionType.java rename to skyvault/src/main/java/com/skyflow/enums/RedactionType.java diff --git a/src/main/java/com/skyflow/enums/RequestMethod.java b/skyvault/src/main/java/com/skyflow/enums/RequestMethod.java similarity index 100% rename from src/main/java/com/skyflow/enums/RequestMethod.java rename to skyvault/src/main/java/com/skyflow/enums/RequestMethod.java diff --git a/src/main/java/com/skyflow/enums/TokenMode.java b/skyvault/src/main/java/com/skyflow/enums/TokenMode.java similarity index 100% rename from src/main/java/com/skyflow/enums/TokenMode.java rename to skyvault/src/main/java/com/skyflow/enums/TokenMode.java diff --git a/src/main/java/com/skyflow/enums/TokenType.java b/skyvault/src/main/java/com/skyflow/enums/TokenType.java similarity index 100% rename from src/main/java/com/skyflow/enums/TokenType.java rename to skyvault/src/main/java/com/skyflow/enums/TokenType.java diff --git a/src/main/java/com/skyflow/errors/ErrorMessage.java b/skyvault/src/main/java/com/skyflow/errors/ErrorMessage.java similarity index 100% rename from src/main/java/com/skyflow/errors/ErrorMessage.java rename to skyvault/src/main/java/com/skyflow/errors/ErrorMessage.java diff --git a/src/main/java/com/skyflow/errors/SkyflowException.java b/skyvault/src/main/java/com/skyflow/errors/SkyflowException.java similarity index 100% rename from src/main/java/com/skyflow/errors/SkyflowException.java rename to skyvault/src/main/java/com/skyflow/errors/SkyflowException.java diff --git a/src/main/java/com/skyflow/generated/rest/ApiClient.java b/skyvault/src/main/java/com/skyflow/generated/rest/ApiClient.java similarity index 88% rename from src/main/java/com/skyflow/generated/rest/ApiClient.java rename to skyvault/src/main/java/com/skyflow/generated/rest/ApiClient.java index 1eb1e429..ab81f443 100644 --- a/src/main/java/com/skyflow/generated/rest/ApiClient.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/ApiClient.java @@ -6,7 +6,6 @@ import com.skyflow.generated.rest.core.ClientOptions; import com.skyflow.generated.rest.core.Suppliers; import com.skyflow.generated.rest.resources.audit.AuditClient; -import com.skyflow.generated.rest.resources.authentication.AuthenticationClient; import com.skyflow.generated.rest.resources.binlookup.BinLookupClient; import com.skyflow.generated.rest.resources.files.FilesClient; import com.skyflow.generated.rest.resources.guardrails.GuardrailsClient; @@ -14,6 +13,7 @@ import com.skyflow.generated.rest.resources.records.RecordsClient; import com.skyflow.generated.rest.resources.strings.StringsClient; import com.skyflow.generated.rest.resources.tokens.TokensClient; + import java.util.function.Supplier; public class ApiClient { @@ -29,8 +29,6 @@ public class ApiClient { protected final Supplier queryClient; - protected final Supplier authenticationClient; - protected final Supplier filesClient; protected final Supplier stringsClient; @@ -44,7 +42,6 @@ public ApiClient(ClientOptions clientOptions) { this.recordsClient = Suppliers.memoize(() -> new RecordsClient(clientOptions)); this.tokensClient = Suppliers.memoize(() -> new TokensClient(clientOptions)); this.queryClient = Suppliers.memoize(() -> new QueryClient(clientOptions)); - this.authenticationClient = Suppliers.memoize(() -> new AuthenticationClient(clientOptions)); this.filesClient = Suppliers.memoize(() -> new FilesClient(clientOptions)); this.stringsClient = Suppliers.memoize(() -> new StringsClient(clientOptions)); this.guardrailsClient = Suppliers.memoize(() -> new GuardrailsClient(clientOptions)); @@ -70,10 +67,6 @@ public QueryClient query() { return this.queryClient.get(); } - public AuthenticationClient authentication() { - return this.authenticationClient.get(); - } - public FilesClient files() { return this.filesClient.get(); } diff --git a/src/main/java/com/skyflow/generated/rest/ApiClientBuilder.java b/skyvault/src/main/java/com/skyflow/generated/rest/ApiClientBuilder.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/ApiClientBuilder.java rename to skyvault/src/main/java/com/skyflow/generated/rest/ApiClientBuilder.java diff --git a/src/main/java/com/skyflow/generated/rest/AsyncApiClient.java b/skyvault/src/main/java/com/skyflow/generated/rest/AsyncApiClient.java similarity index 88% rename from src/main/java/com/skyflow/generated/rest/AsyncApiClient.java rename to skyvault/src/main/java/com/skyflow/generated/rest/AsyncApiClient.java index 405d0208..dc57df06 100644 --- a/src/main/java/com/skyflow/generated/rest/AsyncApiClient.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/AsyncApiClient.java @@ -6,7 +6,6 @@ import com.skyflow.generated.rest.core.ClientOptions; import com.skyflow.generated.rest.core.Suppliers; import com.skyflow.generated.rest.resources.audit.AsyncAuditClient; -import com.skyflow.generated.rest.resources.authentication.AsyncAuthenticationClient; import com.skyflow.generated.rest.resources.binlookup.AsyncBinLookupClient; import com.skyflow.generated.rest.resources.files.AsyncFilesClient; import com.skyflow.generated.rest.resources.guardrails.AsyncGuardrailsClient; @@ -14,6 +13,7 @@ import com.skyflow.generated.rest.resources.records.AsyncRecordsClient; import com.skyflow.generated.rest.resources.strings.AsyncStringsClient; import com.skyflow.generated.rest.resources.tokens.AsyncTokensClient; + import java.util.function.Supplier; public class AsyncApiClient { @@ -29,8 +29,6 @@ public class AsyncApiClient { protected final Supplier queryClient; - protected final Supplier authenticationClient; - protected final Supplier filesClient; protected final Supplier stringsClient; @@ -44,7 +42,6 @@ public AsyncApiClient(ClientOptions clientOptions) { this.recordsClient = Suppliers.memoize(() -> new AsyncRecordsClient(clientOptions)); this.tokensClient = Suppliers.memoize(() -> new AsyncTokensClient(clientOptions)); this.queryClient = Suppliers.memoize(() -> new AsyncQueryClient(clientOptions)); - this.authenticationClient = Suppliers.memoize(() -> new AsyncAuthenticationClient(clientOptions)); this.filesClient = Suppliers.memoize(() -> new AsyncFilesClient(clientOptions)); this.stringsClient = Suppliers.memoize(() -> new AsyncStringsClient(clientOptions)); this.guardrailsClient = Suppliers.memoize(() -> new AsyncGuardrailsClient(clientOptions)); @@ -70,10 +67,6 @@ public AsyncQueryClient query() { return this.queryClient.get(); } - public AsyncAuthenticationClient authentication() { - return this.authenticationClient.get(); - } - public AsyncFilesClient files() { return this.filesClient.get(); } diff --git a/src/main/java/com/skyflow/generated/rest/AsyncApiClientBuilder.java b/skyvault/src/main/java/com/skyflow/generated/rest/AsyncApiClientBuilder.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/AsyncApiClientBuilder.java rename to skyvault/src/main/java/com/skyflow/generated/rest/AsyncApiClientBuilder.java diff --git a/skyvault/src/main/java/com/skyflow/generated/rest/core/ApiClientApiException.java b/skyvault/src/main/java/com/skyflow/generated/rest/core/ApiClientApiException.java new file mode 100644 index 00000000..be5247eb --- /dev/null +++ b/skyvault/src/main/java/com/skyflow/generated/rest/core/ApiClientApiException.java @@ -0,0 +1,74 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.core; + +import okhttp3.Response; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * This exception type will be thrown for any non-2XX API responses. + */ +public class ApiClientApiException extends ApiClientException { + /** + * 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; + + private final Map> headers; + + public ApiClientApiException(String message, int statusCode, Object body) { + super(message); + this.statusCode = statusCode; + this.body = body; + this.headers = new HashMap<>(); + } + + public ApiClientApiException(String message, int statusCode, Object body, Response rawResponse) { + super(message); + this.statusCode = statusCode; + this.body = body; + this.headers = new HashMap<>(); + rawResponse.headers().forEach(header -> { + String key = header.component1(); + String value = header.component2(); + this.headers.computeIfAbsent(key, _str -> new ArrayList<>()).add(value); + }); + } + + /** + * @return the statusCode + */ + public int statusCode() { + return this.statusCode; + } + + /** + * @return the body + */ + public Object body() { + return this.body; + } + + /** + * @return the headers + */ + public Map> headers() { + return this.headers; + } + + @Override + public String toString() { + return "ApiClientApiException{" + "message: " + getMessage() + ", statusCode: " + statusCode + ", body: " + body + + "}"; + } +} diff --git a/skyvault/src/main/java/com/skyflow/generated/rest/core/ApiClientException.java b/skyvault/src/main/java/com/skyflow/generated/rest/core/ApiClientException.java new file mode 100644 index 00000000..7987eba6 --- /dev/null +++ b/skyvault/src/main/java/com/skyflow/generated/rest/core/ApiClientException.java @@ -0,0 +1,17 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.core; + +/** + * This class serves as the base exception for all errors in the SDK. + */ +public class ApiClientException extends RuntimeException { + public ApiClientException(String message) { + super(message); + } + + public ApiClientException(String message, Exception e) { + super(message, e); + } +} diff --git a/skyvault/src/main/java/com/skyflow/generated/rest/core/ApiClientHttpResponse.java b/skyvault/src/main/java/com/skyflow/generated/rest/core/ApiClientHttpResponse.java new file mode 100644 index 00000000..c743352c --- /dev/null +++ b/skyvault/src/main/java/com/skyflow/generated/rest/core/ApiClientHttpResponse.java @@ -0,0 +1,38 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.core; + +import okhttp3.Response; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public final class ApiClientHttpResponse { + + private final T body; + + private final Map> headers; + + public ApiClientHttpResponse(T body, Response rawResponse) { + this.body = body; + + Map> headers = new HashMap<>(); + rawResponse.headers().forEach(header -> { + String key = header.component1(); + String value = header.component2(); + headers.computeIfAbsent(key, _str -> new ArrayList<>()).add(value); + }); + this.headers = headers; + } + + public T body() { + return this.body; + } + + public Map> headers() { + return headers; + } +} diff --git a/src/main/java/com/skyflow/generated/rest/core/ClientOptions.java b/skyvault/src/main/java/com/skyflow/generated/rest/core/ClientOptions.java similarity index 99% rename from src/main/java/com/skyflow/generated/rest/core/ClientOptions.java rename to skyvault/src/main/java/com/skyflow/generated/rest/core/ClientOptions.java index ddb0e107..da41fb25 100644 --- a/src/main/java/com/skyflow/generated/rest/core/ClientOptions.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/core/ClientOptions.java @@ -3,12 +3,13 @@ */ package com.skyflow.generated.rest.core; +import okhttp3.OkHttpClient; + import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; -import okhttp3.OkHttpClient; public final class ClientOptions { private final Environment environment; diff --git a/skyvault/src/main/java/com/skyflow/generated/rest/core/DateTimeDeserializer.java b/skyvault/src/main/java/com/skyflow/generated/rest/core/DateTimeDeserializer.java new file mode 100644 index 00000000..a0a6d7c4 --- /dev/null +++ b/skyvault/src/main/java/com/skyflow/generated/rest/core/DateTimeDeserializer.java @@ -0,0 +1,56 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.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/src/main/java/com/skyflow/generated/rest/core/Environment.java b/skyvault/src/main/java/com/skyflow/generated/rest/core/Environment.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/core/Environment.java rename to skyvault/src/main/java/com/skyflow/generated/rest/core/Environment.java diff --git a/skyvault/src/main/java/com/skyflow/generated/rest/core/FileStream.java b/skyvault/src/main/java/com/skyflow/generated/rest/core/FileStream.java new file mode 100644 index 00000000..2131b0a4 --- /dev/null +++ b/skyvault/src/main/java/com/skyflow/generated/rest/core/FileStream.java @@ -0,0 +1,61 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.core; + +import okhttp3.MediaType; +import okhttp3.RequestBody; +import org.jetbrains.annotations.Nullable; + +import java.io.InputStream; +import java.util.Objects; + +/** + * Represents a file stream with associated metadata for file uploads. + */ +public class FileStream { + private final InputStream inputStream; + private final String fileName; + private final MediaType contentType; + + /** + * Constructs a FileStream with the given input stream and optional metadata. + * + * @param inputStream The input stream of the file content. Must not be null. + * @param fileName The name of the file, or null if unknown. + * @param contentType The MIME type of the file content, or null if unknown. + * @throws NullPointerException if inputStream is null + */ + public FileStream(InputStream inputStream, @Nullable String fileName, @Nullable MediaType contentType) { + this.inputStream = Objects.requireNonNull(inputStream, "Input stream cannot be null"); + this.fileName = fileName; + this.contentType = contentType; + } + + public FileStream(InputStream inputStream) { + this(inputStream, null, null); + } + + public InputStream getInputStream() { + return inputStream; + } + + @Nullable + public String getFileName() { + return fileName; + } + + @Nullable + public MediaType getContentType() { + return contentType; + } + + /** + * Creates a RequestBody suitable for use with OkHttp client. + * + * @return A RequestBody instance representing this file stream. + */ + public RequestBody toRequestBody() { + return new InputStreamRequestBody(contentType, inputStream); + } +} diff --git a/skyvault/src/main/java/com/skyflow/generated/rest/core/InputStreamRequestBody.java b/skyvault/src/main/java/com/skyflow/generated/rest/core/InputStreamRequestBody.java new file mode 100644 index 00000000..55c3c971 --- /dev/null +++ b/skyvault/src/main/java/com/skyflow/generated/rest/core/InputStreamRequestBody.java @@ -0,0 +1,80 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.core; + +import okhttp3.MediaType; +import okhttp3.RequestBody; +import okhttp3.internal.Util; +import okio.BufferedSink; +import okio.Okio; +import okio.Source; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Objects; + +/** + * A custom implementation of OkHttp's RequestBody that wraps an InputStream. + * This class allows streaming of data from an InputStream directly to an HTTP request body, + * which is useful for file uploads or sending large amounts of data without loading it all into memory. + */ +public class InputStreamRequestBody extends RequestBody { + private final InputStream inputStream; + private final MediaType contentType; + + /** + * Constructs an InputStreamRequestBody with the specified content type and input stream. + * + * @param contentType the MediaType of the content, or null if not known + * @param inputStream the InputStream containing the data to be sent + * @throws NullPointerException if inputStream is null + */ + public InputStreamRequestBody(@Nullable MediaType contentType, InputStream inputStream) { + this.contentType = contentType; + this.inputStream = Objects.requireNonNull(inputStream, "inputStream == null"); + } + + /** + * Returns the content type of this request body. + * + * @return the MediaType of the content, or null if not specified + */ + @Nullable + @Override + public MediaType contentType() { + return contentType; + } + + /** + * Returns the content length of this request body, if known. + * This method attempts to determine the length using the InputStream's available() method, + * which may not always accurately reflect the total length of the stream. + * + * @return the content length, or -1 if the length is unknown + * @throws IOException if an I/O error occurs + */ + @Override + public long contentLength() throws IOException { + return inputStream.available() == 0 ? -1 : inputStream.available(); + } + + /** + * Writes the content of the InputStream to the given BufferedSink. + * This method is responsible for transferring the data from the InputStream to the network request. + * + * @param sink the BufferedSink to write the content to + * @throws IOException if an I/O error occurs during writing + */ + @Override + public void writeTo(BufferedSink sink) throws IOException { + Source source = null; + try { + source = Okio.source(inputStream); + sink.writeAll(source); + } finally { + Util.closeQuietly(Objects.requireNonNull(source)); + } + } +} diff --git a/skyvault/src/main/java/com/skyflow/generated/rest/core/MediaTypes.java b/skyvault/src/main/java/com/skyflow/generated/rest/core/MediaTypes.java new file mode 100644 index 00000000..11714cb8 --- /dev/null +++ b/skyvault/src/main/java/com/skyflow/generated/rest/core/MediaTypes.java @@ -0,0 +1,13 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.core; + +import okhttp3.MediaType; + +public final class MediaTypes { + + public static final MediaType APPLICATION_JSON = MediaType.parse("application/json"); + + private MediaTypes() {} +} diff --git a/skyvault/src/main/java/com/skyflow/generated/rest/core/Nullable.java b/skyvault/src/main/java/com/skyflow/generated/rest/core/Nullable.java new file mode 100644 index 00000000..5929c12d --- /dev/null +++ b/skyvault/src/main/java/com/skyflow/generated/rest/core/Nullable.java @@ -0,0 +1,140 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.core; + +import java.util.Optional; +import java.util.function.Function; + +public final class Nullable { + + private final Either, Null> value; + + private Nullable() { + this.value = Either.left(Optional.empty()); + } + + private Nullable(T value) { + if (value == null) { + this.value = Either.right(Null.INSTANCE); + } else { + this.value = Either.left(Optional.of(value)); + } + } + + public static Nullable ofNull() { + return new Nullable<>(null); + } + + public static Nullable of(T value) { + return new Nullable<>(value); + } + + public static Nullable empty() { + return new Nullable<>(); + } + + public static Nullable ofOptional(Optional value) { + if (value.isPresent()) { + return of(value.get()); + } else { + return empty(); + } + } + + public boolean isNull() { + return this.value.isRight(); + } + + public boolean isEmpty() { + return this.value.isLeft() && !this.value.getLeft().isPresent(); + } + + public T get() { + if (this.isNull()) { + return null; + } + + return this.value.getLeft().get(); + } + + public Nullable map(Function mapper) { + if (this.isNull()) { + return Nullable.ofNull(); + } + + return Nullable.ofOptional(this.value.getLeft().map(mapper)); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof Nullable)) { + return false; + } + + if (((Nullable) other).isNull() && this.isNull()) { + return true; + } + + return this.value.getLeft().equals(((Nullable) other).value.getLeft()); + } + + private static final class Either { + private L left = null; + private R right = null; + + private Either(L left, R right) { + if (left != null && right != null) { + throw new IllegalArgumentException("Left and right argument cannot both be non-null."); + } + + if (left == null && right == null) { + throw new IllegalArgumentException("Left and right argument cannot both be null."); + } + + if (left != null) { + this.left = left; + } + + if (right != null) { + this.right = right; + } + } + + public static Either left(L left) { + return new Either<>(left, null); + } + + public static Either right(R right) { + return new Either<>(null, right); + } + + public boolean isLeft() { + return this.left != null; + } + + public boolean isRight() { + return this.right != null; + } + + public L getLeft() { + if (!this.isLeft()) { + throw new IllegalArgumentException("Cannot get left from right Either."); + } + return this.left; + } + + public R getRight() { + if (!this.isRight()) { + throw new IllegalArgumentException("Cannot get right from left Either."); + } + return this.right; + } + } + + private static final class Null { + private static final Null INSTANCE = new Null(); + + private Null() {} + } +} diff --git a/skyvault/src/main/java/com/skyflow/generated/rest/core/NullableNonemptyFilter.java b/skyvault/src/main/java/com/skyflow/generated/rest/core/NullableNonemptyFilter.java new file mode 100644 index 00000000..98c33be4 --- /dev/null +++ b/skyvault/src/main/java/com/skyflow/generated/rest/core/NullableNonemptyFilter.java @@ -0,0 +1,19 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.core; + +import java.util.Optional; + +public final class NullableNonemptyFilter { + @Override + public boolean equals(Object o) { + boolean isOptionalEmpty = isOptionalEmpty(o); + + return isOptionalEmpty; + } + + private boolean isOptionalEmpty(Object o) { + return o instanceof Optional && !((Optional) o).isPresent(); + } +} diff --git a/skyvault/src/main/java/com/skyflow/generated/rest/core/ObjectMappers.java b/skyvault/src/main/java/com/skyflow/generated/rest/core/ObjectMappers.java new file mode 100644 index 00000000..acec32b4 --- /dev/null +++ b/skyvault/src/main/java/com/skyflow/generated/rest/core/ObjectMappers.java @@ -0,0 +1,37 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.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/skyvault/src/main/java/com/skyflow/generated/rest/core/QueryStringMapper.java b/skyvault/src/main/java/com/skyflow/generated/rest/core/QueryStringMapper.java new file mode 100644 index 00000000..c0687736 --- /dev/null +++ b/skyvault/src/main/java/com/skyflow/generated/rest/core/QueryStringMapper.java @@ -0,0 +1,139 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.core; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import okhttp3.HttpUrl; +import okhttp3.MultipartBody; + +import java.util.*; + +public class QueryStringMapper { + + private static final ObjectMapper MAPPER = ObjectMappers.JSON_MAPPER; + + public static void addQueryParameter(HttpUrl.Builder httpUrl, String key, Object value, boolean arraysAsRepeats) { + JsonNode valueNode = MAPPER.valueToTree(value); + + List> flat; + if (valueNode.isObject()) { + flat = flattenObject((ObjectNode) valueNode, arraysAsRepeats); + } else if (valueNode.isArray()) { + flat = flattenArray((ArrayNode) valueNode, "", arraysAsRepeats); + } else { + if (valueNode.isTextual()) { + httpUrl.addQueryParameter(key, valueNode.textValue()); + } else { + httpUrl.addQueryParameter(key, valueNode.toString()); + } + return; + } + + for (Map.Entry field : flat) { + if (field.getValue().isTextual()) { + httpUrl.addQueryParameter(key + field.getKey(), field.getValue().textValue()); + } else { + httpUrl.addQueryParameter(key + field.getKey(), field.getValue().toString()); + } + } + } + + public static void addFormDataPart( + MultipartBody.Builder multipartBody, String key, Object value, boolean arraysAsRepeats) { + JsonNode valueNode = MAPPER.valueToTree(value); + + List> flat; + if (valueNode.isObject()) { + flat = flattenObject((ObjectNode) valueNode, arraysAsRepeats); + } else if (valueNode.isArray()) { + flat = flattenArray((ArrayNode) valueNode, "", arraysAsRepeats); + } else { + if (valueNode.isTextual()) { + multipartBody.addFormDataPart(key, valueNode.textValue()); + } else { + multipartBody.addFormDataPart(key, valueNode.toString()); + } + return; + } + + for (Map.Entry field : flat) { + if (field.getValue().isTextual()) { + multipartBody.addFormDataPart( + key + field.getKey(), field.getValue().textValue()); + } else { + multipartBody.addFormDataPart( + key + field.getKey(), field.getValue().toString()); + } + } + } + + public static List> flattenObject(ObjectNode object, boolean arraysAsRepeats) { + List> flat = new ArrayList<>(); + + Iterator> fields = object.fields(); + while (fields.hasNext()) { + Map.Entry field = fields.next(); + + String key = "[" + field.getKey() + "]"; + + if (field.getValue().isObject()) { + List> flatField = + flattenObject((ObjectNode) field.getValue(), arraysAsRepeats); + addAll(flat, flatField, key); + } else if (field.getValue().isArray()) { + List> flatField = + flattenArray((ArrayNode) field.getValue(), key, arraysAsRepeats); + addAll(flat, flatField, ""); + } else { + flat.add(new AbstractMap.SimpleEntry<>(key, field.getValue())); + } + } + + return flat; + } + + private static List> flattenArray( + ArrayNode array, String key, boolean arraysAsRepeats) { + List> flat = new ArrayList<>(); + + Iterator elements = array.elements(); + + int index = 0; + while (elements.hasNext()) { + JsonNode element = elements.next(); + + String indexKey = key + "[" + index + "]"; + + if (arraysAsRepeats) { + indexKey = key; + } + + if (element.isObject()) { + List> flatField = flattenObject((ObjectNode) element, arraysAsRepeats); + addAll(flat, flatField, indexKey); + } else if (element.isArray()) { + List> flatField = flattenArray((ArrayNode) element, "", arraysAsRepeats); + addAll(flat, flatField, indexKey); + } else { + flat.add(new AbstractMap.SimpleEntry<>(indexKey, element)); + } + + index++; + } + + return flat; + } + + private static void addAll( + List> target, List> source, String prefix) { + for (Map.Entry entry : source) { + Map.Entry entryToAdd = + new AbstractMap.SimpleEntry<>(prefix + entry.getKey(), entry.getValue()); + target.add(entryToAdd); + } + } +} diff --git a/src/main/java/com/skyflow/generated/rest/core/RequestOptions.java b/skyvault/src/main/java/com/skyflow/generated/rest/core/RequestOptions.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/core/RequestOptions.java rename to skyvault/src/main/java/com/skyflow/generated/rest/core/RequestOptions.java diff --git a/skyvault/src/main/java/com/skyflow/generated/rest/core/ResponseBodyInputStream.java b/skyvault/src/main/java/com/skyflow/generated/rest/core/ResponseBodyInputStream.java new file mode 100644 index 00000000..1bb0b5dc --- /dev/null +++ b/skyvault/src/main/java/com/skyflow/generated/rest/core/ResponseBodyInputStream.java @@ -0,0 +1,46 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.core; + +import okhttp3.Response; + +import java.io.FilterInputStream; +import java.io.IOException; + +/** + * 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/skyvault/src/main/java/com/skyflow/generated/rest/core/ResponseBodyReader.java b/skyvault/src/main/java/com/skyflow/generated/rest/core/ResponseBodyReader.java new file mode 100644 index 00000000..e6c1a525 --- /dev/null +++ b/skyvault/src/main/java/com/skyflow/generated/rest/core/ResponseBodyReader.java @@ -0,0 +1,45 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.core; + +import okhttp3.Response; + +import java.io.FilterReader; +import java.io.IOException; + +/** + * 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/skyvault/src/main/java/com/skyflow/generated/rest/core/RetryInterceptor.java b/skyvault/src/main/java/com/skyflow/generated/rest/core/RetryInterceptor.java new file mode 100644 index 00000000..7a28c3c9 --- /dev/null +++ b/skyvault/src/main/java/com/skyflow/generated/rest/core/RetryInterceptor.java @@ -0,0 +1,79 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.core; + +import okhttp3.Interceptor; +import okhttp3.Response; + +import java.io.IOException; +import java.time.Duration; +import java.util.Optional; +import java.util.Random; + +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 == 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/skyvault/src/main/java/com/skyflow/generated/rest/core/Stream.java b/skyvault/src/main/java/com/skyflow/generated/rest/core/Stream.java new file mode 100644 index 00000000..f037712a --- /dev/null +++ b/skyvault/src/main/java/com/skyflow/generated/rest/core/Stream.java @@ -0,0 +1,97 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.core; + +import java.io.Reader; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Scanner; + +/** + * The {@code Stream} class implements {@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 needed 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/skyvault/src/main/java/com/skyflow/generated/rest/core/Suppliers.java b/skyvault/src/main/java/com/skyflow/generated/rest/core/Suppliers.java new file mode 100644 index 00000000..307d5852 --- /dev/null +++ b/skyvault/src/main/java/com/skyflow/generated/rest/core/Suppliers.java @@ -0,0 +1,23 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +package com.skyflow.generated.rest.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/src/main/java/com/skyflow/generated/rest/errors/BadRequestError.java b/skyvault/src/main/java/com/skyflow/generated/rest/errors/BadRequestError.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/errors/BadRequestError.java rename to skyvault/src/main/java/com/skyflow/generated/rest/errors/BadRequestError.java diff --git a/src/main/java/com/skyflow/generated/rest/errors/InternalServerError.java b/skyvault/src/main/java/com/skyflow/generated/rest/errors/InternalServerError.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/errors/InternalServerError.java rename to skyvault/src/main/java/com/skyflow/generated/rest/errors/InternalServerError.java diff --git a/src/main/java/com/skyflow/generated/rest/errors/NotFoundError.java b/skyvault/src/main/java/com/skyflow/generated/rest/errors/NotFoundError.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/errors/NotFoundError.java rename to skyvault/src/main/java/com/skyflow/generated/rest/errors/NotFoundError.java diff --git a/src/main/java/com/skyflow/generated/rest/errors/UnauthorizedError.java b/skyvault/src/main/java/com/skyflow/generated/rest/errors/UnauthorizedError.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/errors/UnauthorizedError.java rename to skyvault/src/main/java/com/skyflow/generated/rest/errors/UnauthorizedError.java diff --git a/src/main/java/com/skyflow/generated/rest/resources/audit/AsyncAuditClient.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/audit/AsyncAuditClient.java similarity index 99% rename from src/main/java/com/skyflow/generated/rest/resources/audit/AsyncAuditClient.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/audit/AsyncAuditClient.java index 0064558d..7e2719f2 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/audit/AsyncAuditClient.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/audit/AsyncAuditClient.java @@ -7,6 +7,7 @@ import com.skyflow.generated.rest.core.RequestOptions; import com.skyflow.generated.rest.resources.audit.requests.AuditServiceListAuditEventsRequest; import com.skyflow.generated.rest.types.V1AuditResponse; + import java.util.concurrent.CompletableFuture; public class AsyncAuditClient { diff --git a/src/main/java/com/skyflow/generated/rest/resources/audit/AsyncRawAuditClient.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/audit/AsyncRawAuditClient.java similarity index 95% rename from src/main/java/com/skyflow/generated/rest/resources/audit/AsyncRawAuditClient.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/audit/AsyncRawAuditClient.java index 875ca259..e1516eaf 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/audit/AsyncRawAuditClient.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/audit/AsyncRawAuditClient.java @@ -4,27 +4,15 @@ package com.skyflow.generated.rest.resources.audit; import com.fasterxml.jackson.core.JsonProcessingException; -import com.skyflow.generated.rest.core.ApiClientApiException; -import com.skyflow.generated.rest.core.ApiClientException; -import com.skyflow.generated.rest.core.ApiClientHttpResponse; -import com.skyflow.generated.rest.core.ClientOptions; -import com.skyflow.generated.rest.core.ObjectMappers; -import com.skyflow.generated.rest.core.QueryStringMapper; -import com.skyflow.generated.rest.core.RequestOptions; +import com.skyflow.generated.rest.core.*; import com.skyflow.generated.rest.errors.NotFoundError; import com.skyflow.generated.rest.resources.audit.requests.AuditServiceListAuditEventsRequest; import com.skyflow.generated.rest.types.V1AuditResponse; +import okhttp3.*; +import org.jetbrains.annotations.NotNull; + import java.io.IOException; import java.util.concurrent.CompletableFuture; -import okhttp3.Call; -import okhttp3.Callback; -import okhttp3.Headers; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.ResponseBody; -import org.jetbrains.annotations.NotNull; public class AsyncRawAuditClient { protected final ClientOptions clientOptions; diff --git a/src/main/java/com/skyflow/generated/rest/resources/audit/AuditClient.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/audit/AuditClient.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/resources/audit/AuditClient.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/audit/AuditClient.java diff --git a/src/main/java/com/skyflow/generated/rest/resources/audit/RawAuditClient.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/audit/RawAuditClient.java similarity index 95% rename from src/main/java/com/skyflow/generated/rest/resources/audit/RawAuditClient.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/audit/RawAuditClient.java index bfec3bf2..68cd5e3b 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/audit/RawAuditClient.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/audit/RawAuditClient.java @@ -4,23 +4,13 @@ package com.skyflow.generated.rest.resources.audit; import com.fasterxml.jackson.core.JsonProcessingException; -import com.skyflow.generated.rest.core.ApiClientApiException; -import com.skyflow.generated.rest.core.ApiClientException; -import com.skyflow.generated.rest.core.ApiClientHttpResponse; -import com.skyflow.generated.rest.core.ClientOptions; -import com.skyflow.generated.rest.core.ObjectMappers; -import com.skyflow.generated.rest.core.QueryStringMapper; -import com.skyflow.generated.rest.core.RequestOptions; +import com.skyflow.generated.rest.core.*; import com.skyflow.generated.rest.errors.NotFoundError; import com.skyflow.generated.rest.resources.audit.requests.AuditServiceListAuditEventsRequest; import com.skyflow.generated.rest.types.V1AuditResponse; +import okhttp3.*; + import java.io.IOException; -import okhttp3.Headers; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.ResponseBody; public class RawAuditClient { protected final ClientOptions clientOptions; diff --git a/src/main/java/com/skyflow/generated/rest/resources/audit/requests/AuditServiceListAuditEventsRequest.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/audit/requests/AuditServiceListAuditEventsRequest.java similarity index 98% rename from src/main/java/com/skyflow/generated/rest/resources/audit/requests/AuditServiceListAuditEventsRequest.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/audit/requests/AuditServiceListAuditEventsRequest.java index 8b6686e4..e9f8cf12 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/audit/requests/AuditServiceListAuditEventsRequest.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/audit/requests/AuditServiceListAuditEventsRequest.java @@ -3,26 +3,16 @@ */ package com.skyflow.generated.rest.resources.audit.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; -import com.skyflow.generated.rest.resources.audit.types.AuditServiceListAuditEventsRequestFilterOpsActionType; -import com.skyflow.generated.rest.resources.audit.types.AuditServiceListAuditEventsRequestFilterOpsContextAccessType; -import com.skyflow.generated.rest.resources.audit.types.AuditServiceListAuditEventsRequestFilterOpsContextActorType; -import com.skyflow.generated.rest.resources.audit.types.AuditServiceListAuditEventsRequestFilterOpsContextAuthMode; -import com.skyflow.generated.rest.resources.audit.types.AuditServiceListAuditEventsRequestFilterOpsResourceType; -import com.skyflow.generated.rest.resources.audit.types.AuditServiceListAuditEventsRequestSortOpsOrderBy; +import com.skyflow.generated.rest.resources.audit.types.*; +import org.jetbrains.annotations.NotNull; + import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.Optional; -import org.jetbrains.annotations.NotNull; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = AuditServiceListAuditEventsRequest.Builder.class) diff --git a/src/main/java/com/skyflow/generated/rest/resources/audit/types/AuditServiceListAuditEventsRequestFilterOpsActionType.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/audit/types/AuditServiceListAuditEventsRequestFilterOpsActionType.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/resources/audit/types/AuditServiceListAuditEventsRequestFilterOpsActionType.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/audit/types/AuditServiceListAuditEventsRequestFilterOpsActionType.java diff --git a/src/main/java/com/skyflow/generated/rest/resources/audit/types/AuditServiceListAuditEventsRequestFilterOpsContextAccessType.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/audit/types/AuditServiceListAuditEventsRequestFilterOpsContextAccessType.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/resources/audit/types/AuditServiceListAuditEventsRequestFilterOpsContextAccessType.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/audit/types/AuditServiceListAuditEventsRequestFilterOpsContextAccessType.java diff --git a/src/main/java/com/skyflow/generated/rest/resources/audit/types/AuditServiceListAuditEventsRequestFilterOpsContextActorType.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/audit/types/AuditServiceListAuditEventsRequestFilterOpsContextActorType.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/resources/audit/types/AuditServiceListAuditEventsRequestFilterOpsContextActorType.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/audit/types/AuditServiceListAuditEventsRequestFilterOpsContextActorType.java diff --git a/src/main/java/com/skyflow/generated/rest/resources/audit/types/AuditServiceListAuditEventsRequestFilterOpsContextAuthMode.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/audit/types/AuditServiceListAuditEventsRequestFilterOpsContextAuthMode.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/resources/audit/types/AuditServiceListAuditEventsRequestFilterOpsContextAuthMode.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/audit/types/AuditServiceListAuditEventsRequestFilterOpsContextAuthMode.java diff --git a/src/main/java/com/skyflow/generated/rest/resources/audit/types/AuditServiceListAuditEventsRequestFilterOpsResourceType.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/audit/types/AuditServiceListAuditEventsRequestFilterOpsResourceType.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/resources/audit/types/AuditServiceListAuditEventsRequestFilterOpsResourceType.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/audit/types/AuditServiceListAuditEventsRequestFilterOpsResourceType.java diff --git a/src/main/java/com/skyflow/generated/rest/resources/audit/types/AuditServiceListAuditEventsRequestSortOpsOrderBy.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/audit/types/AuditServiceListAuditEventsRequestSortOpsOrderBy.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/resources/audit/types/AuditServiceListAuditEventsRequestSortOpsOrderBy.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/audit/types/AuditServiceListAuditEventsRequestSortOpsOrderBy.java diff --git a/src/main/java/com/skyflow/generated/rest/resources/binlookup/AsyncBinLookupClient.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/binlookup/AsyncBinLookupClient.java similarity index 99% rename from src/main/java/com/skyflow/generated/rest/resources/binlookup/AsyncBinLookupClient.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/binlookup/AsyncBinLookupClient.java index f10ed979..fb2d07d0 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/binlookup/AsyncBinLookupClient.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/binlookup/AsyncBinLookupClient.java @@ -7,6 +7,7 @@ import com.skyflow.generated.rest.core.RequestOptions; import com.skyflow.generated.rest.resources.binlookup.requests.V1BinListRequest; import com.skyflow.generated.rest.types.V1BinListResponse; + import java.util.concurrent.CompletableFuture; public class AsyncBinLookupClient { diff --git a/src/main/java/com/skyflow/generated/rest/resources/binlookup/AsyncRawBinLookupClient.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/binlookup/AsyncRawBinLookupClient.java similarity index 88% rename from src/main/java/com/skyflow/generated/rest/resources/binlookup/AsyncRawBinLookupClient.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/binlookup/AsyncRawBinLookupClient.java index 2a3f5f4f..9cf03c7d 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/binlookup/AsyncRawBinLookupClient.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/binlookup/AsyncRawBinLookupClient.java @@ -4,28 +4,15 @@ package com.skyflow.generated.rest.resources.binlookup; import com.fasterxml.jackson.core.JsonProcessingException; -import com.skyflow.generated.rest.core.ApiClientApiException; -import com.skyflow.generated.rest.core.ApiClientException; -import com.skyflow.generated.rest.core.ApiClientHttpResponse; -import com.skyflow.generated.rest.core.ClientOptions; -import com.skyflow.generated.rest.core.MediaTypes; -import com.skyflow.generated.rest.core.ObjectMappers; -import com.skyflow.generated.rest.core.RequestOptions; +import com.skyflow.generated.rest.core.*; import com.skyflow.generated.rest.errors.NotFoundError; import com.skyflow.generated.rest.resources.binlookup.requests.V1BinListRequest; import com.skyflow.generated.rest.types.V1BinListResponse; +import okhttp3.*; +import org.jetbrains.annotations.NotNull; + import java.io.IOException; import java.util.concurrent.CompletableFuture; -import okhttp3.Call; -import okhttp3.Callback; -import okhttp3.Headers; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; -import okhttp3.ResponseBody; -import org.jetbrains.annotations.NotNull; public class AsyncRawBinLookupClient { protected final ClientOptions clientOptions; diff --git a/src/main/java/com/skyflow/generated/rest/resources/binlookup/BinLookupClient.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/binlookup/BinLookupClient.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/resources/binlookup/BinLookupClient.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/binlookup/BinLookupClient.java diff --git a/src/main/java/com/skyflow/generated/rest/resources/binlookup/RawBinLookupClient.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/binlookup/RawBinLookupClient.java similarity index 87% rename from src/main/java/com/skyflow/generated/rest/resources/binlookup/RawBinLookupClient.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/binlookup/RawBinLookupClient.java index bb08039b..21e77017 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/binlookup/RawBinLookupClient.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/binlookup/RawBinLookupClient.java @@ -4,24 +4,13 @@ package com.skyflow.generated.rest.resources.binlookup; import com.fasterxml.jackson.core.JsonProcessingException; -import com.skyflow.generated.rest.core.ApiClientApiException; -import com.skyflow.generated.rest.core.ApiClientException; -import com.skyflow.generated.rest.core.ApiClientHttpResponse; -import com.skyflow.generated.rest.core.ClientOptions; -import com.skyflow.generated.rest.core.MediaTypes; -import com.skyflow.generated.rest.core.ObjectMappers; -import com.skyflow.generated.rest.core.RequestOptions; +import com.skyflow.generated.rest.core.*; import com.skyflow.generated.rest.errors.NotFoundError; import com.skyflow.generated.rest.resources.binlookup.requests.V1BinListRequest; import com.skyflow.generated.rest.types.V1BinListResponse; +import okhttp3.*; + 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 RawBinLookupClient { protected final ClientOptions clientOptions; diff --git a/src/main/java/com/skyflow/generated/rest/resources/binlookup/requests/V1BinListRequest.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/binlookup/requests/V1BinListRequest.java similarity index 91% rename from src/main/java/com/skyflow/generated/rest/resources/binlookup/requests/V1BinListRequest.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/binlookup/requests/V1BinListRequest.java index af827e01..def40293 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/binlookup/requests/V1BinListRequest.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/binlookup/requests/V1BinListRequest.java @@ -3,21 +3,12 @@ */ package com.skyflow.generated.rest.resources.binlookup.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; import com.skyflow.generated.rest.types.V1VaultSchemaConfig; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; + +import java.util.*; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = V1BinListRequest.Builder.class) diff --git a/src/main/java/com/skyflow/generated/rest/resources/files/AsyncFilesClient.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/files/AsyncFilesClient.java similarity index 92% rename from src/main/java/com/skyflow/generated/rest/resources/files/AsyncFilesClient.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/files/AsyncFilesClient.java index 1f157223..01031423 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/files/AsyncFilesClient.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/files/AsyncFilesClient.java @@ -5,20 +5,11 @@ import com.skyflow.generated.rest.core.ClientOptions; import com.skyflow.generated.rest.core.RequestOptions; -import com.skyflow.generated.rest.resources.files.requests.DeidentifyFileAudioRequestDeidentifyAudio; -import com.skyflow.generated.rest.resources.files.requests.DeidentifyFileDocumentPdfRequestDeidentifyPdf; -import com.skyflow.generated.rest.resources.files.requests.DeidentifyFileImageRequestDeidentifyImage; -import com.skyflow.generated.rest.resources.files.requests.DeidentifyFileRequest; -import com.skyflow.generated.rest.resources.files.requests.DeidentifyFileRequestDeidentifyDocument; -import com.skyflow.generated.rest.resources.files.requests.DeidentifyFileRequestDeidentifyPresentation; -import com.skyflow.generated.rest.resources.files.requests.DeidentifyFileRequestDeidentifySpreadsheet; -import com.skyflow.generated.rest.resources.files.requests.DeidentifyFileRequestDeidentifyStructuredText; -import com.skyflow.generated.rest.resources.files.requests.DeidentifyFileRequestDeidentifyText; -import com.skyflow.generated.rest.resources.files.requests.GetRunRequest; -import com.skyflow.generated.rest.resources.files.requests.ReidentifyFileRequestReidentifyFile; +import com.skyflow.generated.rest.resources.files.requests.*; import com.skyflow.generated.rest.types.DeidentifyFileResponse; import com.skyflow.generated.rest.types.DetectRunsResponse; import com.skyflow.generated.rest.types.ReidentifyFileResponse; + import java.util.concurrent.CompletableFuture; public class AsyncFilesClient { diff --git a/src/main/java/com/skyflow/generated/rest/resources/files/AsyncRawFilesClient.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/files/AsyncRawFilesClient.java similarity index 96% rename from src/main/java/com/skyflow/generated/rest/resources/files/AsyncRawFilesClient.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/files/AsyncRawFilesClient.java index 014f3cf8..e7f008e8 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/files/AsyncRawFilesClient.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/files/AsyncRawFilesClient.java @@ -4,44 +4,20 @@ package com.skyflow.generated.rest.resources.files; import com.fasterxml.jackson.core.JsonProcessingException; -import com.skyflow.generated.rest.core.ApiClientApiException; -import com.skyflow.generated.rest.core.ApiClientException; -import com.skyflow.generated.rest.core.ApiClientHttpResponse; -import com.skyflow.generated.rest.core.ClientOptions; -import com.skyflow.generated.rest.core.MediaTypes; -import com.skyflow.generated.rest.core.ObjectMappers; -import com.skyflow.generated.rest.core.QueryStringMapper; -import com.skyflow.generated.rest.core.RequestOptions; +import com.skyflow.generated.rest.core.*; import com.skyflow.generated.rest.errors.BadRequestError; import com.skyflow.generated.rest.errors.InternalServerError; import com.skyflow.generated.rest.errors.UnauthorizedError; -import com.skyflow.generated.rest.resources.files.requests.DeidentifyFileAudioRequestDeidentifyAudio; -import com.skyflow.generated.rest.resources.files.requests.DeidentifyFileDocumentPdfRequestDeidentifyPdf; -import com.skyflow.generated.rest.resources.files.requests.DeidentifyFileImageRequestDeidentifyImage; -import com.skyflow.generated.rest.resources.files.requests.DeidentifyFileRequest; -import com.skyflow.generated.rest.resources.files.requests.DeidentifyFileRequestDeidentifyDocument; -import com.skyflow.generated.rest.resources.files.requests.DeidentifyFileRequestDeidentifyPresentation; -import com.skyflow.generated.rest.resources.files.requests.DeidentifyFileRequestDeidentifySpreadsheet; -import com.skyflow.generated.rest.resources.files.requests.DeidentifyFileRequestDeidentifyStructuredText; -import com.skyflow.generated.rest.resources.files.requests.DeidentifyFileRequestDeidentifyText; -import com.skyflow.generated.rest.resources.files.requests.GetRunRequest; -import com.skyflow.generated.rest.resources.files.requests.ReidentifyFileRequestReidentifyFile; +import com.skyflow.generated.rest.resources.files.requests.*; import com.skyflow.generated.rest.types.DeidentifyFileResponse; import com.skyflow.generated.rest.types.DetectRunsResponse; import com.skyflow.generated.rest.types.ErrorResponse; import com.skyflow.generated.rest.types.ReidentifyFileResponse; +import okhttp3.*; +import org.jetbrains.annotations.NotNull; + import java.io.IOException; import java.util.concurrent.CompletableFuture; -import okhttp3.Call; -import okhttp3.Callback; -import okhttp3.Headers; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; -import okhttp3.ResponseBody; -import org.jetbrains.annotations.NotNull; public class AsyncRawFilesClient { protected final ClientOptions clientOptions; diff --git a/src/main/java/com/skyflow/generated/rest/resources/files/FilesClient.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/files/FilesClient.java similarity index 91% rename from src/main/java/com/skyflow/generated/rest/resources/files/FilesClient.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/files/FilesClient.java index 8d083d76..bcda96e6 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/files/FilesClient.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/files/FilesClient.java @@ -5,17 +5,7 @@ import com.skyflow.generated.rest.core.ClientOptions; import com.skyflow.generated.rest.core.RequestOptions; -import com.skyflow.generated.rest.resources.files.requests.DeidentifyFileAudioRequestDeidentifyAudio; -import com.skyflow.generated.rest.resources.files.requests.DeidentifyFileDocumentPdfRequestDeidentifyPdf; -import com.skyflow.generated.rest.resources.files.requests.DeidentifyFileImageRequestDeidentifyImage; -import com.skyflow.generated.rest.resources.files.requests.DeidentifyFileRequest; -import com.skyflow.generated.rest.resources.files.requests.DeidentifyFileRequestDeidentifyDocument; -import com.skyflow.generated.rest.resources.files.requests.DeidentifyFileRequestDeidentifyPresentation; -import com.skyflow.generated.rest.resources.files.requests.DeidentifyFileRequestDeidentifySpreadsheet; -import com.skyflow.generated.rest.resources.files.requests.DeidentifyFileRequestDeidentifyStructuredText; -import com.skyflow.generated.rest.resources.files.requests.DeidentifyFileRequestDeidentifyText; -import com.skyflow.generated.rest.resources.files.requests.GetRunRequest; -import com.skyflow.generated.rest.resources.files.requests.ReidentifyFileRequestReidentifyFile; +import com.skyflow.generated.rest.resources.files.requests.*; import com.skyflow.generated.rest.types.DeidentifyFileResponse; import com.skyflow.generated.rest.types.DetectRunsResponse; import com.skyflow.generated.rest.types.ReidentifyFileResponse; diff --git a/src/main/java/com/skyflow/generated/rest/resources/files/RawFilesClient.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/files/RawFilesClient.java similarity index 96% rename from src/main/java/com/skyflow/generated/rest/resources/files/RawFilesClient.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/files/RawFilesClient.java index 164c4d18..d277d224 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/files/RawFilesClient.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/files/RawFilesClient.java @@ -4,40 +4,18 @@ package com.skyflow.generated.rest.resources.files; import com.fasterxml.jackson.core.JsonProcessingException; -import com.skyflow.generated.rest.core.ApiClientApiException; -import com.skyflow.generated.rest.core.ApiClientException; -import com.skyflow.generated.rest.core.ApiClientHttpResponse; -import com.skyflow.generated.rest.core.ClientOptions; -import com.skyflow.generated.rest.core.MediaTypes; -import com.skyflow.generated.rest.core.ObjectMappers; -import com.skyflow.generated.rest.core.QueryStringMapper; -import com.skyflow.generated.rest.core.RequestOptions; +import com.skyflow.generated.rest.core.*; import com.skyflow.generated.rest.errors.BadRequestError; import com.skyflow.generated.rest.errors.InternalServerError; import com.skyflow.generated.rest.errors.UnauthorizedError; -import com.skyflow.generated.rest.resources.files.requests.DeidentifyFileAudioRequestDeidentifyAudio; -import com.skyflow.generated.rest.resources.files.requests.DeidentifyFileDocumentPdfRequestDeidentifyPdf; -import com.skyflow.generated.rest.resources.files.requests.DeidentifyFileImageRequestDeidentifyImage; -import com.skyflow.generated.rest.resources.files.requests.DeidentifyFileRequest; -import com.skyflow.generated.rest.resources.files.requests.DeidentifyFileRequestDeidentifyDocument; -import com.skyflow.generated.rest.resources.files.requests.DeidentifyFileRequestDeidentifyPresentation; -import com.skyflow.generated.rest.resources.files.requests.DeidentifyFileRequestDeidentifySpreadsheet; -import com.skyflow.generated.rest.resources.files.requests.DeidentifyFileRequestDeidentifyStructuredText; -import com.skyflow.generated.rest.resources.files.requests.DeidentifyFileRequestDeidentifyText; -import com.skyflow.generated.rest.resources.files.requests.GetRunRequest; -import com.skyflow.generated.rest.resources.files.requests.ReidentifyFileRequestReidentifyFile; +import com.skyflow.generated.rest.resources.files.requests.*; import com.skyflow.generated.rest.types.DeidentifyFileResponse; import com.skyflow.generated.rest.types.DetectRunsResponse; import com.skyflow.generated.rest.types.ErrorResponse; import com.skyflow.generated.rest.types.ReidentifyFileResponse; +import okhttp3.*; + 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 RawFilesClient { protected final ClientOptions clientOptions; diff --git a/src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileAudioRequestDeidentifyAudio.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileAudioRequestDeidentifyAudio.java similarity index 98% rename from src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileAudioRequestDeidentifyAudio.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileAudioRequestDeidentifyAudio.java index d1cadca3..c2baa931 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileAudioRequestDeidentifyAudio.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileAudioRequestDeidentifyAudio.java @@ -3,13 +3,7 @@ */ package com.skyflow.generated.rest.resources.files.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; import com.skyflow.generated.rest.resources.files.types.DeidentifyFileAudioRequestDeidentifyAudioEntityTypesItem; @@ -17,13 +11,10 @@ import com.skyflow.generated.rest.types.FileDataDeidentifyAudio; import com.skyflow.generated.rest.types.TokenTypeMapping; import com.skyflow.generated.rest.types.Transformations; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; import org.jetbrains.annotations.NotNull; +import java.util.*; + @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = DeidentifyFileAudioRequestDeidentifyAudio.Builder.class) public final class DeidentifyFileAudioRequestDeidentifyAudio { diff --git a/src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileDocumentPdfRequestDeidentifyPdf.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileDocumentPdfRequestDeidentifyPdf.java similarity index 97% rename from src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileDocumentPdfRequestDeidentifyPdf.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileDocumentPdfRequestDeidentifyPdf.java index 5b6a653f..1844a116 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileDocumentPdfRequestDeidentifyPdf.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileDocumentPdfRequestDeidentifyPdf.java @@ -3,26 +3,17 @@ */ package com.skyflow.generated.rest.resources.files.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; import com.skyflow.generated.rest.resources.files.types.DeidentifyFileDocumentPdfRequestDeidentifyPdfEntityTypesItem; import com.skyflow.generated.rest.types.FileDataDeidentifyPdf; import com.skyflow.generated.rest.types.TokenTypeMapping; import com.skyflow.generated.rest.types.Transformations; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; import org.jetbrains.annotations.NotNull; +import java.util.*; + @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = DeidentifyFileDocumentPdfRequestDeidentifyPdf.Builder.class) public final class DeidentifyFileDocumentPdfRequestDeidentifyPdf { diff --git a/src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileImageRequestDeidentifyImage.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileImageRequestDeidentifyImage.java similarity index 97% rename from src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileImageRequestDeidentifyImage.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileImageRequestDeidentifyImage.java index 015bba9e..25ddea9c 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileImageRequestDeidentifyImage.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileImageRequestDeidentifyImage.java @@ -3,13 +3,7 @@ */ package com.skyflow.generated.rest.resources.files.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; import com.skyflow.generated.rest.resources.files.types.DeidentifyFileImageRequestDeidentifyImageEntityTypesItem; @@ -17,13 +11,10 @@ import com.skyflow.generated.rest.types.FileDataDeidentifyImage; import com.skyflow.generated.rest.types.TokenTypeMapping; import com.skyflow.generated.rest.types.Transformations; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; import org.jetbrains.annotations.NotNull; +import java.util.*; + @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = DeidentifyFileImageRequestDeidentifyImage.Builder.class) public final class DeidentifyFileImageRequestDeidentifyImage { diff --git a/src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileRequest.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileRequest.java similarity index 97% rename from src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileRequest.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileRequest.java index c2cdcfb5..62d77229 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileRequest.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileRequest.java @@ -3,26 +3,17 @@ */ package com.skyflow.generated.rest.resources.files.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; import com.skyflow.generated.rest.resources.files.types.DeidentifyFileRequestEntityTypesItem; import com.skyflow.generated.rest.types.FileData; import com.skyflow.generated.rest.types.TokenTypeMapping; import com.skyflow.generated.rest.types.Transformations; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; import org.jetbrains.annotations.NotNull; +import java.util.*; + @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = DeidentifyFileRequest.Builder.class) public final class DeidentifyFileRequest { diff --git a/src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileRequestDeidentifyDocument.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileRequestDeidentifyDocument.java similarity index 97% rename from src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileRequestDeidentifyDocument.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileRequestDeidentifyDocument.java index fc3db52a..c4d9841d 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileRequestDeidentifyDocument.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileRequestDeidentifyDocument.java @@ -3,26 +3,17 @@ */ package com.skyflow.generated.rest.resources.files.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; import com.skyflow.generated.rest.resources.files.types.DeidentifyFileRequestDeidentifyDocumentEntityTypesItem; import com.skyflow.generated.rest.types.FileDataDeidentifyDocument; import com.skyflow.generated.rest.types.TokenTypeMapping; import com.skyflow.generated.rest.types.Transformations; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; import org.jetbrains.annotations.NotNull; +import java.util.*; + @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = DeidentifyFileRequestDeidentifyDocument.Builder.class) public final class DeidentifyFileRequestDeidentifyDocument { diff --git a/src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileRequestDeidentifyPresentation.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileRequestDeidentifyPresentation.java similarity index 97% rename from src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileRequestDeidentifyPresentation.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileRequestDeidentifyPresentation.java index f45802ec..e1cea77f 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileRequestDeidentifyPresentation.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileRequestDeidentifyPresentation.java @@ -3,26 +3,17 @@ */ package com.skyflow.generated.rest.resources.files.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; import com.skyflow.generated.rest.resources.files.types.DeidentifyFileRequestDeidentifyPresentationEntityTypesItem; import com.skyflow.generated.rest.types.FileDataDeidentifyPresentation; import com.skyflow.generated.rest.types.TokenTypeMapping; import com.skyflow.generated.rest.types.Transformations; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; import org.jetbrains.annotations.NotNull; +import java.util.*; + @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = DeidentifyFileRequestDeidentifyPresentation.Builder.class) public final class DeidentifyFileRequestDeidentifyPresentation { diff --git a/src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileRequestDeidentifySpreadsheet.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileRequestDeidentifySpreadsheet.java similarity index 97% rename from src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileRequestDeidentifySpreadsheet.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileRequestDeidentifySpreadsheet.java index 93ea921a..10ca8feb 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileRequestDeidentifySpreadsheet.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileRequestDeidentifySpreadsheet.java @@ -3,26 +3,17 @@ */ package com.skyflow.generated.rest.resources.files.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; import com.skyflow.generated.rest.resources.files.types.DeidentifyFileRequestDeidentifySpreadsheetEntityTypesItem; import com.skyflow.generated.rest.types.FileDataDeidentifySpreadsheet; import com.skyflow.generated.rest.types.TokenTypeMapping; import com.skyflow.generated.rest.types.Transformations; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; import org.jetbrains.annotations.NotNull; +import java.util.*; + @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = DeidentifyFileRequestDeidentifySpreadsheet.Builder.class) public final class DeidentifyFileRequestDeidentifySpreadsheet { diff --git a/src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileRequestDeidentifyStructuredText.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileRequestDeidentifyStructuredText.java similarity index 97% rename from src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileRequestDeidentifyStructuredText.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileRequestDeidentifyStructuredText.java index b579fe1b..e3f555b8 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileRequestDeidentifyStructuredText.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileRequestDeidentifyStructuredText.java @@ -3,26 +3,17 @@ */ package com.skyflow.generated.rest.resources.files.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; import com.skyflow.generated.rest.resources.files.types.DeidentifyFileRequestDeidentifyStructuredTextEntityTypesItem; import com.skyflow.generated.rest.types.FileDataDeidentifyStructuredText; import com.skyflow.generated.rest.types.TokenTypeMapping; import com.skyflow.generated.rest.types.Transformations; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; import org.jetbrains.annotations.NotNull; +import java.util.*; + @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = DeidentifyFileRequestDeidentifyStructuredText.Builder.class) public final class DeidentifyFileRequestDeidentifyStructuredText { diff --git a/src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileRequestDeidentifyText.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileRequestDeidentifyText.java similarity index 97% rename from src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileRequestDeidentifyText.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileRequestDeidentifyText.java index 7a7c6615..8457c790 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileRequestDeidentifyText.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/files/requests/DeidentifyFileRequestDeidentifyText.java @@ -3,26 +3,17 @@ */ package com.skyflow.generated.rest.resources.files.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; import com.skyflow.generated.rest.resources.files.types.DeidentifyFileRequestDeidentifyTextEntityTypesItem; import com.skyflow.generated.rest.types.FileDataDeidentifyText; import com.skyflow.generated.rest.types.TokenTypeMapping; import com.skyflow.generated.rest.types.Transformations; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; import org.jetbrains.annotations.NotNull; +import java.util.*; + @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = DeidentifyFileRequestDeidentifyText.Builder.class) public final class DeidentifyFileRequestDeidentifyText { diff --git a/src/main/java/com/skyflow/generated/rest/resources/files/requests/GetRunRequest.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/files/requests/GetRunRequest.java similarity index 86% rename from src/main/java/com/skyflow/generated/rest/resources/files/requests/GetRunRequest.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/files/requests/GetRunRequest.java index 490392f1..cde24227 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/files/requests/GetRunRequest.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/files/requests/GetRunRequest.java @@ -3,15 +3,10 @@ */ package com.skyflow.generated.rest.resources.files.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; + import java.util.HashMap; import java.util.Map; import java.util.Objects; diff --git a/src/main/java/com/skyflow/generated/rest/resources/files/requests/ReidentifyFileRequestReidentifyFile.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/files/requests/ReidentifyFileRequestReidentifyFile.java similarity index 92% rename from src/main/java/com/skyflow/generated/rest/resources/files/requests/ReidentifyFileRequestReidentifyFile.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/files/requests/ReidentifyFileRequestReidentifyFile.java index 0f5e79ab..5cd22747 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/files/requests/ReidentifyFileRequestReidentifyFile.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/files/requests/ReidentifyFileRequestReidentifyFile.java @@ -3,22 +3,17 @@ */ package com.skyflow.generated.rest.resources.files.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; import com.skyflow.generated.rest.types.FileDataReidentifyFile; import com.skyflow.generated.rest.types.Format; +import org.jetbrains.annotations.NotNull; + import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.Optional; -import org.jetbrains.annotations.NotNull; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = ReidentifyFileRequestReidentifyFile.Builder.class) diff --git a/src/main/java/com/skyflow/generated/rest/resources/files/types/DeidentifyFileAudioRequestDeidentifyAudioEntityTypesItem.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/files/types/DeidentifyFileAudioRequestDeidentifyAudioEntityTypesItem.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/resources/files/types/DeidentifyFileAudioRequestDeidentifyAudioEntityTypesItem.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/files/types/DeidentifyFileAudioRequestDeidentifyAudioEntityTypesItem.java diff --git a/src/main/java/com/skyflow/generated/rest/resources/files/types/DeidentifyFileAudioRequestDeidentifyAudioOutputTranscription.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/files/types/DeidentifyFileAudioRequestDeidentifyAudioOutputTranscription.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/resources/files/types/DeidentifyFileAudioRequestDeidentifyAudioOutputTranscription.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/files/types/DeidentifyFileAudioRequestDeidentifyAudioOutputTranscription.java diff --git a/src/main/java/com/skyflow/generated/rest/resources/files/types/DeidentifyFileDocumentPdfRequestDeidentifyPdfEntityTypesItem.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/files/types/DeidentifyFileDocumentPdfRequestDeidentifyPdfEntityTypesItem.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/resources/files/types/DeidentifyFileDocumentPdfRequestDeidentifyPdfEntityTypesItem.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/files/types/DeidentifyFileDocumentPdfRequestDeidentifyPdfEntityTypesItem.java diff --git a/src/main/java/com/skyflow/generated/rest/resources/files/types/DeidentifyFileImageRequestDeidentifyImageEntityTypesItem.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/files/types/DeidentifyFileImageRequestDeidentifyImageEntityTypesItem.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/resources/files/types/DeidentifyFileImageRequestDeidentifyImageEntityTypesItem.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/files/types/DeidentifyFileImageRequestDeidentifyImageEntityTypesItem.java diff --git a/src/main/java/com/skyflow/generated/rest/resources/files/types/DeidentifyFileImageRequestDeidentifyImageMaskingMethod.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/files/types/DeidentifyFileImageRequestDeidentifyImageMaskingMethod.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/resources/files/types/DeidentifyFileImageRequestDeidentifyImageMaskingMethod.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/files/types/DeidentifyFileImageRequestDeidentifyImageMaskingMethod.java diff --git a/src/main/java/com/skyflow/generated/rest/resources/files/types/DeidentifyFileRequestDeidentifyDocumentEntityTypesItem.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/files/types/DeidentifyFileRequestDeidentifyDocumentEntityTypesItem.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/resources/files/types/DeidentifyFileRequestDeidentifyDocumentEntityTypesItem.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/files/types/DeidentifyFileRequestDeidentifyDocumentEntityTypesItem.java diff --git a/src/main/java/com/skyflow/generated/rest/resources/files/types/DeidentifyFileRequestDeidentifyPresentationEntityTypesItem.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/files/types/DeidentifyFileRequestDeidentifyPresentationEntityTypesItem.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/resources/files/types/DeidentifyFileRequestDeidentifyPresentationEntityTypesItem.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/files/types/DeidentifyFileRequestDeidentifyPresentationEntityTypesItem.java diff --git a/src/main/java/com/skyflow/generated/rest/resources/files/types/DeidentifyFileRequestDeidentifySpreadsheetEntityTypesItem.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/files/types/DeidentifyFileRequestDeidentifySpreadsheetEntityTypesItem.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/resources/files/types/DeidentifyFileRequestDeidentifySpreadsheetEntityTypesItem.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/files/types/DeidentifyFileRequestDeidentifySpreadsheetEntityTypesItem.java diff --git a/src/main/java/com/skyflow/generated/rest/resources/files/types/DeidentifyFileRequestDeidentifyStructuredTextEntityTypesItem.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/files/types/DeidentifyFileRequestDeidentifyStructuredTextEntityTypesItem.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/resources/files/types/DeidentifyFileRequestDeidentifyStructuredTextEntityTypesItem.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/files/types/DeidentifyFileRequestDeidentifyStructuredTextEntityTypesItem.java diff --git a/src/main/java/com/skyflow/generated/rest/resources/files/types/DeidentifyFileRequestDeidentifyTextEntityTypesItem.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/files/types/DeidentifyFileRequestDeidentifyTextEntityTypesItem.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/resources/files/types/DeidentifyFileRequestDeidentifyTextEntityTypesItem.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/files/types/DeidentifyFileRequestDeidentifyTextEntityTypesItem.java diff --git a/src/main/java/com/skyflow/generated/rest/resources/files/types/DeidentifyFileRequestEntityTypesItem.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/files/types/DeidentifyFileRequestEntityTypesItem.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/resources/files/types/DeidentifyFileRequestEntityTypesItem.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/files/types/DeidentifyFileRequestEntityTypesItem.java diff --git a/src/main/java/com/skyflow/generated/rest/resources/guardrails/AsyncGuardrailsClient.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/guardrails/AsyncGuardrailsClient.java similarity index 99% rename from src/main/java/com/skyflow/generated/rest/resources/guardrails/AsyncGuardrailsClient.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/guardrails/AsyncGuardrailsClient.java index af874ecd..fa45ee60 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/guardrails/AsyncGuardrailsClient.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/guardrails/AsyncGuardrailsClient.java @@ -7,6 +7,7 @@ import com.skyflow.generated.rest.core.RequestOptions; import com.skyflow.generated.rest.resources.guardrails.requests.DetectGuardrailsRequest; import com.skyflow.generated.rest.types.DetectGuardrailsResponse; + import java.util.concurrent.CompletableFuture; public class AsyncGuardrailsClient { diff --git a/src/main/java/com/skyflow/generated/rest/resources/guardrails/AsyncRawGuardrailsClient.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/guardrails/AsyncRawGuardrailsClient.java similarity index 89% rename from src/main/java/com/skyflow/generated/rest/resources/guardrails/AsyncRawGuardrailsClient.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/guardrails/AsyncRawGuardrailsClient.java index 0088d7a3..80853472 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/guardrails/AsyncRawGuardrailsClient.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/guardrails/AsyncRawGuardrailsClient.java @@ -4,31 +4,18 @@ package com.skyflow.generated.rest.resources.guardrails; import com.fasterxml.jackson.core.JsonProcessingException; -import com.skyflow.generated.rest.core.ApiClientApiException; -import com.skyflow.generated.rest.core.ApiClientException; -import com.skyflow.generated.rest.core.ApiClientHttpResponse; -import com.skyflow.generated.rest.core.ClientOptions; -import com.skyflow.generated.rest.core.MediaTypes; -import com.skyflow.generated.rest.core.ObjectMappers; -import com.skyflow.generated.rest.core.RequestOptions; +import com.skyflow.generated.rest.core.*; import com.skyflow.generated.rest.errors.BadRequestError; import com.skyflow.generated.rest.errors.InternalServerError; import com.skyflow.generated.rest.errors.UnauthorizedError; import com.skyflow.generated.rest.resources.guardrails.requests.DetectGuardrailsRequest; import com.skyflow.generated.rest.types.DetectGuardrailsResponse; import com.skyflow.generated.rest.types.ErrorResponse; +import okhttp3.*; +import org.jetbrains.annotations.NotNull; + import java.io.IOException; import java.util.concurrent.CompletableFuture; -import okhttp3.Call; -import okhttp3.Callback; -import okhttp3.Headers; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; -import okhttp3.ResponseBody; -import org.jetbrains.annotations.NotNull; public class AsyncRawGuardrailsClient { protected final ClientOptions clientOptions; diff --git a/src/main/java/com/skyflow/generated/rest/resources/guardrails/GuardrailsClient.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/guardrails/GuardrailsClient.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/resources/guardrails/GuardrailsClient.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/guardrails/GuardrailsClient.java diff --git a/src/main/java/com/skyflow/generated/rest/resources/guardrails/RawGuardrailsClient.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/guardrails/RawGuardrailsClient.java similarity index 87% rename from src/main/java/com/skyflow/generated/rest/resources/guardrails/RawGuardrailsClient.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/guardrails/RawGuardrailsClient.java index 0223134b..5bf5c31a 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/guardrails/RawGuardrailsClient.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/guardrails/RawGuardrailsClient.java @@ -4,27 +4,16 @@ package com.skyflow.generated.rest.resources.guardrails; import com.fasterxml.jackson.core.JsonProcessingException; -import com.skyflow.generated.rest.core.ApiClientApiException; -import com.skyflow.generated.rest.core.ApiClientException; -import com.skyflow.generated.rest.core.ApiClientHttpResponse; -import com.skyflow.generated.rest.core.ClientOptions; -import com.skyflow.generated.rest.core.MediaTypes; -import com.skyflow.generated.rest.core.ObjectMappers; -import com.skyflow.generated.rest.core.RequestOptions; +import com.skyflow.generated.rest.core.*; import com.skyflow.generated.rest.errors.BadRequestError; import com.skyflow.generated.rest.errors.InternalServerError; import com.skyflow.generated.rest.errors.UnauthorizedError; import com.skyflow.generated.rest.resources.guardrails.requests.DetectGuardrailsRequest; import com.skyflow.generated.rest.types.DetectGuardrailsResponse; import com.skyflow.generated.rest.types.ErrorResponse; +import okhttp3.*; + 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 RawGuardrailsClient { protected final ClientOptions clientOptions; diff --git a/src/main/java/com/skyflow/generated/rest/resources/guardrails/requests/DetectGuardrailsRequest.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/guardrails/requests/DetectGuardrailsRequest.java similarity index 93% rename from src/main/java/com/skyflow/generated/rest/resources/guardrails/requests/DetectGuardrailsRequest.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/guardrails/requests/DetectGuardrailsRequest.java index 9063fed8..75250f8c 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/guardrails/requests/DetectGuardrailsRequest.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/guardrails/requests/DetectGuardrailsRequest.java @@ -3,22 +3,13 @@ */ package com.skyflow.generated.rest.resources.guardrails.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; import org.jetbrains.annotations.NotNull; +import java.util.*; + @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = DetectGuardrailsRequest.Builder.class) public final class DetectGuardrailsRequest { diff --git a/src/main/java/com/skyflow/generated/rest/resources/query/AsyncQueryClient.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/query/AsyncQueryClient.java similarity index 99% rename from src/main/java/com/skyflow/generated/rest/resources/query/AsyncQueryClient.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/query/AsyncQueryClient.java index 894c97b5..cab967a7 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/query/AsyncQueryClient.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/query/AsyncQueryClient.java @@ -7,6 +7,7 @@ import com.skyflow.generated.rest.core.RequestOptions; import com.skyflow.generated.rest.resources.query.requests.QueryServiceExecuteQueryBody; import com.skyflow.generated.rest.types.V1GetQueryResponse; + import java.util.concurrent.CompletableFuture; public class AsyncQueryClient { diff --git a/src/main/java/com/skyflow/generated/rest/resources/query/AsyncRawQueryClient.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/query/AsyncRawQueryClient.java similarity index 91% rename from src/main/java/com/skyflow/generated/rest/resources/query/AsyncRawQueryClient.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/query/AsyncRawQueryClient.java index 96e2dc0b..7b8c9383 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/query/AsyncRawQueryClient.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/query/AsyncRawQueryClient.java @@ -4,28 +4,15 @@ package com.skyflow.generated.rest.resources.query; import com.fasterxml.jackson.core.JsonProcessingException; -import com.skyflow.generated.rest.core.ApiClientApiException; -import com.skyflow.generated.rest.core.ApiClientException; -import com.skyflow.generated.rest.core.ApiClientHttpResponse; -import com.skyflow.generated.rest.core.ClientOptions; -import com.skyflow.generated.rest.core.MediaTypes; -import com.skyflow.generated.rest.core.ObjectMappers; -import com.skyflow.generated.rest.core.RequestOptions; +import com.skyflow.generated.rest.core.*; import com.skyflow.generated.rest.errors.NotFoundError; import com.skyflow.generated.rest.resources.query.requests.QueryServiceExecuteQueryBody; import com.skyflow.generated.rest.types.V1GetQueryResponse; +import okhttp3.*; +import org.jetbrains.annotations.NotNull; + import java.io.IOException; import java.util.concurrent.CompletableFuture; -import okhttp3.Call; -import okhttp3.Callback; -import okhttp3.Headers; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; -import okhttp3.ResponseBody; -import org.jetbrains.annotations.NotNull; public class AsyncRawQueryClient { protected final ClientOptions clientOptions; diff --git a/src/main/java/com/skyflow/generated/rest/resources/query/QueryClient.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/query/QueryClient.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/resources/query/QueryClient.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/query/QueryClient.java diff --git a/src/main/java/com/skyflow/generated/rest/resources/query/RawQueryClient.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/query/RawQueryClient.java similarity index 91% rename from src/main/java/com/skyflow/generated/rest/resources/query/RawQueryClient.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/query/RawQueryClient.java index 3e5bee51..ec88ae93 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/query/RawQueryClient.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/query/RawQueryClient.java @@ -4,24 +4,13 @@ package com.skyflow.generated.rest.resources.query; import com.fasterxml.jackson.core.JsonProcessingException; -import com.skyflow.generated.rest.core.ApiClientApiException; -import com.skyflow.generated.rest.core.ApiClientException; -import com.skyflow.generated.rest.core.ApiClientHttpResponse; -import com.skyflow.generated.rest.core.ClientOptions; -import com.skyflow.generated.rest.core.MediaTypes; -import com.skyflow.generated.rest.core.ObjectMappers; -import com.skyflow.generated.rest.core.RequestOptions; +import com.skyflow.generated.rest.core.*; import com.skyflow.generated.rest.errors.NotFoundError; import com.skyflow.generated.rest.resources.query.requests.QueryServiceExecuteQueryBody; import com.skyflow.generated.rest.types.V1GetQueryResponse; +import okhttp3.*; + 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 RawQueryClient { protected final ClientOptions clientOptions; diff --git a/src/main/java/com/skyflow/generated/rest/resources/query/requests/QueryServiceExecuteQueryBody.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/query/requests/QueryServiceExecuteQueryBody.java similarity index 94% rename from src/main/java/com/skyflow/generated/rest/resources/query/requests/QueryServiceExecuteQueryBody.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/query/requests/QueryServiceExecuteQueryBody.java index 60565eb5..000cd685 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/query/requests/QueryServiceExecuteQueryBody.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/query/requests/QueryServiceExecuteQueryBody.java @@ -3,15 +3,10 @@ */ package com.skyflow.generated.rest.resources.query.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; + import java.util.HashMap; import java.util.Map; import java.util.Objects; diff --git a/src/main/java/com/skyflow/generated/rest/resources/records/AsyncRawRecordsClient.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/records/AsyncRawRecordsClient.java similarity index 96% rename from src/main/java/com/skyflow/generated/rest/resources/records/AsyncRawRecordsClient.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/records/AsyncRawRecordsClient.java index a810f142..b1c9ae18 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/records/AsyncRawRecordsClient.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/records/AsyncRawRecordsClient.java @@ -4,54 +4,21 @@ package com.skyflow.generated.rest.resources.records; import com.fasterxml.jackson.core.JsonProcessingException; -import com.skyflow.generated.rest.core.ApiClientApiException; -import com.skyflow.generated.rest.core.ApiClientException; -import com.skyflow.generated.rest.core.ApiClientHttpResponse; -import com.skyflow.generated.rest.core.ClientOptions; -import com.skyflow.generated.rest.core.MediaTypes; -import com.skyflow.generated.rest.core.ObjectMappers; -import com.skyflow.generated.rest.core.QueryStringMapper; -import com.skyflow.generated.rest.core.RequestOptions; +import com.skyflow.generated.rest.core.*; import com.skyflow.generated.rest.errors.BadRequestError; import com.skyflow.generated.rest.errors.InternalServerError; import com.skyflow.generated.rest.errors.NotFoundError; import com.skyflow.generated.rest.errors.UnauthorizedError; -import com.skyflow.generated.rest.resources.records.requests.FileServiceUploadFileRequest; -import com.skyflow.generated.rest.resources.records.requests.RecordServiceBatchOperationBody; -import com.skyflow.generated.rest.resources.records.requests.RecordServiceBulkDeleteRecordBody; -import com.skyflow.generated.rest.resources.records.requests.RecordServiceBulkGetRecordRequest; -import com.skyflow.generated.rest.resources.records.requests.RecordServiceGetRecordRequest; -import com.skyflow.generated.rest.resources.records.requests.RecordServiceInsertRecordBody; -import com.skyflow.generated.rest.resources.records.requests.RecordServiceUpdateRecordBody; -import com.skyflow.generated.rest.resources.records.requests.UploadFileV2Request; -import com.skyflow.generated.rest.types.ErrorResponse; -import com.skyflow.generated.rest.types.UploadFileV2Response; -import com.skyflow.generated.rest.types.V1BatchOperationResponse; -import com.skyflow.generated.rest.types.V1BulkDeleteRecordResponse; -import com.skyflow.generated.rest.types.V1BulkGetRecordResponse; -import com.skyflow.generated.rest.types.V1DeleteFileResponse; -import com.skyflow.generated.rest.types.V1DeleteRecordResponse; -import com.skyflow.generated.rest.types.V1FieldRecords; -import com.skyflow.generated.rest.types.V1GetFileScanStatusResponse; -import com.skyflow.generated.rest.types.V1InsertRecordResponse; -import com.skyflow.generated.rest.types.V1UpdateRecordResponse; +import com.skyflow.generated.rest.resources.records.requests.*; +import com.skyflow.generated.rest.types.*; +import okhttp3.*; +import org.jetbrains.annotations.NotNull; + import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.util.Optional; import java.util.concurrent.CompletableFuture; -import okhttp3.Call; -import okhttp3.Callback; -import okhttp3.Headers; -import okhttp3.HttpUrl; -import okhttp3.MediaType; -import okhttp3.MultipartBody; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; -import okhttp3.ResponseBody; -import org.jetbrains.annotations.NotNull; public class AsyncRawRecordsClient { protected final ClientOptions clientOptions; diff --git a/src/main/java/com/skyflow/generated/rest/resources/records/AsyncRecordsClient.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/records/AsyncRecordsClient.java similarity index 91% rename from src/main/java/com/skyflow/generated/rest/resources/records/AsyncRecordsClient.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/records/AsyncRecordsClient.java index c6925b50..ed7240c9 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/records/AsyncRecordsClient.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/records/AsyncRecordsClient.java @@ -5,24 +5,9 @@ import com.skyflow.generated.rest.core.ClientOptions; import com.skyflow.generated.rest.core.RequestOptions; -import com.skyflow.generated.rest.resources.records.requests.FileServiceUploadFileRequest; -import com.skyflow.generated.rest.resources.records.requests.RecordServiceBatchOperationBody; -import com.skyflow.generated.rest.resources.records.requests.RecordServiceBulkDeleteRecordBody; -import com.skyflow.generated.rest.resources.records.requests.RecordServiceBulkGetRecordRequest; -import com.skyflow.generated.rest.resources.records.requests.RecordServiceGetRecordRequest; -import com.skyflow.generated.rest.resources.records.requests.RecordServiceInsertRecordBody; -import com.skyflow.generated.rest.resources.records.requests.RecordServiceUpdateRecordBody; -import com.skyflow.generated.rest.resources.records.requests.UploadFileV2Request; -import com.skyflow.generated.rest.types.UploadFileV2Response; -import com.skyflow.generated.rest.types.V1BatchOperationResponse; -import com.skyflow.generated.rest.types.V1BulkDeleteRecordResponse; -import com.skyflow.generated.rest.types.V1BulkGetRecordResponse; -import com.skyflow.generated.rest.types.V1DeleteFileResponse; -import com.skyflow.generated.rest.types.V1DeleteRecordResponse; -import com.skyflow.generated.rest.types.V1FieldRecords; -import com.skyflow.generated.rest.types.V1GetFileScanStatusResponse; -import com.skyflow.generated.rest.types.V1InsertRecordResponse; -import com.skyflow.generated.rest.types.V1UpdateRecordResponse; +import com.skyflow.generated.rest.resources.records.requests.*; +import com.skyflow.generated.rest.types.*; + import java.io.File; import java.util.Optional; import java.util.concurrent.CompletableFuture; diff --git a/src/main/java/com/skyflow/generated/rest/resources/records/RawRecordsClient.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/records/RawRecordsClient.java similarity index 95% rename from src/main/java/com/skyflow/generated/rest/resources/records/RawRecordsClient.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/records/RawRecordsClient.java index d1b607a0..29530b56 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/records/RawRecordsClient.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/records/RawRecordsClient.java @@ -4,50 +4,19 @@ package com.skyflow.generated.rest.resources.records; import com.fasterxml.jackson.core.JsonProcessingException; -import com.skyflow.generated.rest.core.ApiClientApiException; -import com.skyflow.generated.rest.core.ApiClientException; -import com.skyflow.generated.rest.core.ApiClientHttpResponse; -import com.skyflow.generated.rest.core.ClientOptions; -import com.skyflow.generated.rest.core.MediaTypes; -import com.skyflow.generated.rest.core.ObjectMappers; -import com.skyflow.generated.rest.core.QueryStringMapper; -import com.skyflow.generated.rest.core.RequestOptions; +import com.skyflow.generated.rest.core.*; import com.skyflow.generated.rest.errors.BadRequestError; import com.skyflow.generated.rest.errors.InternalServerError; import com.skyflow.generated.rest.errors.NotFoundError; import com.skyflow.generated.rest.errors.UnauthorizedError; -import com.skyflow.generated.rest.resources.records.requests.FileServiceUploadFileRequest; -import com.skyflow.generated.rest.resources.records.requests.RecordServiceBatchOperationBody; -import com.skyflow.generated.rest.resources.records.requests.RecordServiceBulkDeleteRecordBody; -import com.skyflow.generated.rest.resources.records.requests.RecordServiceBulkGetRecordRequest; -import com.skyflow.generated.rest.resources.records.requests.RecordServiceGetRecordRequest; -import com.skyflow.generated.rest.resources.records.requests.RecordServiceInsertRecordBody; -import com.skyflow.generated.rest.resources.records.requests.RecordServiceUpdateRecordBody; -import com.skyflow.generated.rest.resources.records.requests.UploadFileV2Request; -import com.skyflow.generated.rest.types.ErrorResponse; -import com.skyflow.generated.rest.types.UploadFileV2Response; -import com.skyflow.generated.rest.types.V1BatchOperationResponse; -import com.skyflow.generated.rest.types.V1BulkDeleteRecordResponse; -import com.skyflow.generated.rest.types.V1BulkGetRecordResponse; -import com.skyflow.generated.rest.types.V1DeleteFileResponse; -import com.skyflow.generated.rest.types.V1DeleteRecordResponse; -import com.skyflow.generated.rest.types.V1FieldRecords; -import com.skyflow.generated.rest.types.V1GetFileScanStatusResponse; -import com.skyflow.generated.rest.types.V1InsertRecordResponse; -import com.skyflow.generated.rest.types.V1UpdateRecordResponse; +import com.skyflow.generated.rest.resources.records.requests.*; +import com.skyflow.generated.rest.types.*; +import okhttp3.*; + import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.util.Optional; -import okhttp3.Headers; -import okhttp3.HttpUrl; -import okhttp3.MediaType; -import okhttp3.MultipartBody; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; -import okhttp3.ResponseBody; public class RawRecordsClient { protected final ClientOptions clientOptions; diff --git a/src/main/java/com/skyflow/generated/rest/resources/records/RecordsClient.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/records/RecordsClient.java similarity index 90% rename from src/main/java/com/skyflow/generated/rest/resources/records/RecordsClient.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/records/RecordsClient.java index 7b4599c6..eb9bc81d 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/records/RecordsClient.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/records/RecordsClient.java @@ -5,24 +5,9 @@ import com.skyflow.generated.rest.core.ClientOptions; import com.skyflow.generated.rest.core.RequestOptions; -import com.skyflow.generated.rest.resources.records.requests.FileServiceUploadFileRequest; -import com.skyflow.generated.rest.resources.records.requests.RecordServiceBatchOperationBody; -import com.skyflow.generated.rest.resources.records.requests.RecordServiceBulkDeleteRecordBody; -import com.skyflow.generated.rest.resources.records.requests.RecordServiceBulkGetRecordRequest; -import com.skyflow.generated.rest.resources.records.requests.RecordServiceGetRecordRequest; -import com.skyflow.generated.rest.resources.records.requests.RecordServiceInsertRecordBody; -import com.skyflow.generated.rest.resources.records.requests.RecordServiceUpdateRecordBody; -import com.skyflow.generated.rest.resources.records.requests.UploadFileV2Request; -import com.skyflow.generated.rest.types.UploadFileV2Response; -import com.skyflow.generated.rest.types.V1BatchOperationResponse; -import com.skyflow.generated.rest.types.V1BulkDeleteRecordResponse; -import com.skyflow.generated.rest.types.V1BulkGetRecordResponse; -import com.skyflow.generated.rest.types.V1DeleteFileResponse; -import com.skyflow.generated.rest.types.V1DeleteRecordResponse; -import com.skyflow.generated.rest.types.V1FieldRecords; -import com.skyflow.generated.rest.types.V1GetFileScanStatusResponse; -import com.skyflow.generated.rest.types.V1InsertRecordResponse; -import com.skyflow.generated.rest.types.V1UpdateRecordResponse; +import com.skyflow.generated.rest.resources.records.requests.*; +import com.skyflow.generated.rest.types.*; + import java.io.File; import java.util.Optional; diff --git a/src/main/java/com/skyflow/generated/rest/resources/records/requests/FileServiceUploadFileRequest.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/records/requests/FileServiceUploadFileRequest.java similarity index 88% rename from src/main/java/com/skyflow/generated/rest/resources/records/requests/FileServiceUploadFileRequest.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/records/requests/FileServiceUploadFileRequest.java index fe805970..3efb0a98 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/records/requests/FileServiceUploadFileRequest.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/records/requests/FileServiceUploadFileRequest.java @@ -3,15 +3,10 @@ */ package com.skyflow.generated.rest.resources.records.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; + import java.util.HashMap; import java.util.Map; import java.util.Objects; diff --git a/src/main/java/com/skyflow/generated/rest/resources/records/requests/RecordServiceBatchOperationBody.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/records/requests/RecordServiceBatchOperationBody.java similarity index 90% rename from src/main/java/com/skyflow/generated/rest/resources/records/requests/RecordServiceBatchOperationBody.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/records/requests/RecordServiceBatchOperationBody.java index 48586839..c1997213 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/records/requests/RecordServiceBatchOperationBody.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/records/requests/RecordServiceBatchOperationBody.java @@ -3,22 +3,13 @@ */ package com.skyflow.generated.rest.resources.records.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; import com.skyflow.generated.rest.types.V1BatchRecord; import com.skyflow.generated.rest.types.V1Byot; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; + +import java.util.*; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = RecordServiceBatchOperationBody.Builder.class) diff --git a/src/main/java/com/skyflow/generated/rest/resources/records/requests/RecordServiceBulkDeleteRecordBody.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/records/requests/RecordServiceBulkDeleteRecordBody.java similarity index 85% rename from src/main/java/com/skyflow/generated/rest/resources/records/requests/RecordServiceBulkDeleteRecordBody.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/records/requests/RecordServiceBulkDeleteRecordBody.java index b417adbb..4b9dcced 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/records/requests/RecordServiceBulkDeleteRecordBody.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/records/requests/RecordServiceBulkDeleteRecordBody.java @@ -3,20 +3,11 @@ */ package com.skyflow.generated.rest.resources.records.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; + +import java.util.*; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = RecordServiceBulkDeleteRecordBody.Builder.class) diff --git a/src/main/java/com/skyflow/generated/rest/resources/records/requests/RecordServiceBulkGetRecordRequest.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/records/requests/RecordServiceBulkGetRecordRequest.java similarity index 96% rename from src/main/java/com/skyflow/generated/rest/resources/records/requests/RecordServiceBulkGetRecordRequest.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/records/requests/RecordServiceBulkGetRecordRequest.java index 22c9090c..9b59168a 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/records/requests/RecordServiceBulkGetRecordRequest.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/records/requests/RecordServiceBulkGetRecordRequest.java @@ -3,23 +3,13 @@ */ package com.skyflow.generated.rest.resources.records.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; import com.skyflow.generated.rest.resources.records.types.RecordServiceBulkGetRecordRequestOrderBy; import com.skyflow.generated.rest.resources.records.types.RecordServiceBulkGetRecordRequestRedaction; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; + +import java.util.*; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = RecordServiceBulkGetRecordRequest.Builder.class) diff --git a/src/main/java/com/skyflow/generated/rest/resources/records/requests/RecordServiceGetRecordRequest.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/records/requests/RecordServiceGetRecordRequest.java similarity index 92% rename from src/main/java/com/skyflow/generated/rest/resources/records/requests/RecordServiceGetRecordRequest.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/records/requests/RecordServiceGetRecordRequest.java index eef17665..91317d02 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/records/requests/RecordServiceGetRecordRequest.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/records/requests/RecordServiceGetRecordRequest.java @@ -3,22 +3,12 @@ */ package com.skyflow.generated.rest.resources.records.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; import com.skyflow.generated.rest.resources.records.types.RecordServiceGetRecordRequestRedaction; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; + +import java.util.*; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = RecordServiceGetRecordRequest.Builder.class) diff --git a/src/main/java/com/skyflow/generated/rest/resources/records/requests/RecordServiceInsertRecordBody.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/records/requests/RecordServiceInsertRecordBody.java similarity index 93% rename from src/main/java/com/skyflow/generated/rest/resources/records/requests/RecordServiceInsertRecordBody.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/records/requests/RecordServiceInsertRecordBody.java index e1de00f7..e832e396 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/records/requests/RecordServiceInsertRecordBody.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/records/requests/RecordServiceInsertRecordBody.java @@ -3,22 +3,13 @@ */ package com.skyflow.generated.rest.resources.records.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; import com.skyflow.generated.rest.types.V1Byot; import com.skyflow.generated.rest.types.V1FieldRecords; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; + +import java.util.*; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = RecordServiceInsertRecordBody.Builder.class) diff --git a/src/main/java/com/skyflow/generated/rest/resources/records/requests/RecordServiceUpdateRecordBody.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/records/requests/RecordServiceUpdateRecordBody.java similarity index 92% rename from src/main/java/com/skyflow/generated/rest/resources/records/requests/RecordServiceUpdateRecordBody.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/records/requests/RecordServiceUpdateRecordBody.java index e52af2e4..f5c7a229 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/records/requests/RecordServiceUpdateRecordBody.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/records/requests/RecordServiceUpdateRecordBody.java @@ -3,17 +3,12 @@ */ package com.skyflow.generated.rest.resources.records.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; import com.skyflow.generated.rest.types.V1Byot; import com.skyflow.generated.rest.types.V1FieldRecords; + import java.util.HashMap; import java.util.Map; import java.util.Objects; diff --git a/src/main/java/com/skyflow/generated/rest/resources/records/requests/UploadFileV2Request.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/records/requests/UploadFileV2Request.java similarity index 95% rename from src/main/java/com/skyflow/generated/rest/resources/records/requests/UploadFileV2Request.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/records/requests/UploadFileV2Request.java index f162530c..81a9f837 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/records/requests/UploadFileV2Request.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/records/requests/UploadFileV2Request.java @@ -3,20 +3,15 @@ */ package com.skyflow.generated.rest.resources.records.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; +import org.jetbrains.annotations.NotNull; + import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.Optional; -import org.jetbrains.annotations.NotNull; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = UploadFileV2Request.Builder.class) diff --git a/src/main/java/com/skyflow/generated/rest/resources/records/types/RecordServiceBulkGetRecordRequestOrderBy.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/records/types/RecordServiceBulkGetRecordRequestOrderBy.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/resources/records/types/RecordServiceBulkGetRecordRequestOrderBy.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/records/types/RecordServiceBulkGetRecordRequestOrderBy.java diff --git a/src/main/java/com/skyflow/generated/rest/resources/records/types/RecordServiceBulkGetRecordRequestRedaction.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/records/types/RecordServiceBulkGetRecordRequestRedaction.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/resources/records/types/RecordServiceBulkGetRecordRequestRedaction.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/records/types/RecordServiceBulkGetRecordRequestRedaction.java diff --git a/src/main/java/com/skyflow/generated/rest/resources/records/types/RecordServiceGetRecordRequestRedaction.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/records/types/RecordServiceGetRecordRequestRedaction.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/resources/records/types/RecordServiceGetRecordRequestRedaction.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/records/types/RecordServiceGetRecordRequestRedaction.java diff --git a/src/main/java/com/skyflow/generated/rest/resources/strings/AsyncRawStringsClient.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/strings/AsyncRawStringsClient.java similarity index 94% rename from src/main/java/com/skyflow/generated/rest/resources/strings/AsyncRawStringsClient.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/strings/AsyncRawStringsClient.java index 96428263..36b364b5 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/strings/AsyncRawStringsClient.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/strings/AsyncRawStringsClient.java @@ -4,13 +4,7 @@ package com.skyflow.generated.rest.resources.strings; import com.fasterxml.jackson.core.JsonProcessingException; -import com.skyflow.generated.rest.core.ApiClientApiException; -import com.skyflow.generated.rest.core.ApiClientException; -import com.skyflow.generated.rest.core.ApiClientHttpResponse; -import com.skyflow.generated.rest.core.ClientOptions; -import com.skyflow.generated.rest.core.MediaTypes; -import com.skyflow.generated.rest.core.ObjectMappers; -import com.skyflow.generated.rest.core.RequestOptions; +import com.skyflow.generated.rest.core.*; import com.skyflow.generated.rest.errors.BadRequestError; import com.skyflow.generated.rest.errors.InternalServerError; import com.skyflow.generated.rest.errors.UnauthorizedError; @@ -19,18 +13,11 @@ import com.skyflow.generated.rest.types.DeidentifyStringResponse; import com.skyflow.generated.rest.types.ErrorResponse; import com.skyflow.generated.rest.types.IdentifyResponse; +import okhttp3.*; +import org.jetbrains.annotations.NotNull; + import java.io.IOException; import java.util.concurrent.CompletableFuture; -import okhttp3.Call; -import okhttp3.Callback; -import okhttp3.Headers; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; -import okhttp3.ResponseBody; -import org.jetbrains.annotations.NotNull; public class AsyncRawStringsClient { protected final ClientOptions clientOptions; diff --git a/src/main/java/com/skyflow/generated/rest/resources/strings/AsyncStringsClient.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/strings/AsyncStringsClient.java similarity index 99% rename from src/main/java/com/skyflow/generated/rest/resources/strings/AsyncStringsClient.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/strings/AsyncStringsClient.java index 046bb289..66ef72c5 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/strings/AsyncStringsClient.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/strings/AsyncStringsClient.java @@ -9,6 +9,7 @@ import com.skyflow.generated.rest.resources.strings.requests.ReidentifyStringRequest; import com.skyflow.generated.rest.types.DeidentifyStringResponse; import com.skyflow.generated.rest.types.IdentifyResponse; + import java.util.concurrent.CompletableFuture; public class AsyncStringsClient { diff --git a/src/main/java/com/skyflow/generated/rest/resources/strings/RawStringsClient.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/strings/RawStringsClient.java similarity index 92% rename from src/main/java/com/skyflow/generated/rest/resources/strings/RawStringsClient.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/strings/RawStringsClient.java index a5e16949..da6bec56 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/strings/RawStringsClient.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/strings/RawStringsClient.java @@ -4,13 +4,7 @@ package com.skyflow.generated.rest.resources.strings; import com.fasterxml.jackson.core.JsonProcessingException; -import com.skyflow.generated.rest.core.ApiClientApiException; -import com.skyflow.generated.rest.core.ApiClientException; -import com.skyflow.generated.rest.core.ApiClientHttpResponse; -import com.skyflow.generated.rest.core.ClientOptions; -import com.skyflow.generated.rest.core.MediaTypes; -import com.skyflow.generated.rest.core.ObjectMappers; -import com.skyflow.generated.rest.core.RequestOptions; +import com.skyflow.generated.rest.core.*; import com.skyflow.generated.rest.errors.BadRequestError; import com.skyflow.generated.rest.errors.InternalServerError; import com.skyflow.generated.rest.errors.UnauthorizedError; @@ -19,14 +13,9 @@ import com.skyflow.generated.rest.types.DeidentifyStringResponse; import com.skyflow.generated.rest.types.ErrorResponse; import com.skyflow.generated.rest.types.IdentifyResponse; +import okhttp3.*; + 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 RawStringsClient { protected final ClientOptions clientOptions; diff --git a/src/main/java/com/skyflow/generated/rest/resources/strings/StringsClient.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/strings/StringsClient.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/resources/strings/StringsClient.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/strings/StringsClient.java diff --git a/src/main/java/com/skyflow/generated/rest/resources/strings/requests/DeidentifyStringRequest.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/strings/requests/DeidentifyStringRequest.java similarity index 97% rename from src/main/java/com/skyflow/generated/rest/resources/strings/requests/DeidentifyStringRequest.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/strings/requests/DeidentifyStringRequest.java index 943ddde4..06ec6cec 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/strings/requests/DeidentifyStringRequest.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/strings/requests/DeidentifyStringRequest.java @@ -3,25 +3,16 @@ */ package com.skyflow.generated.rest.resources.strings.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; import com.skyflow.generated.rest.resources.strings.types.DeidentifyStringRequestEntityTypesItem; import com.skyflow.generated.rest.types.TokenTypeMapping; import com.skyflow.generated.rest.types.Transformations; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; import org.jetbrains.annotations.NotNull; +import java.util.*; + @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = DeidentifyStringRequest.Builder.class) public final class DeidentifyStringRequest { diff --git a/src/main/java/com/skyflow/generated/rest/resources/strings/requests/ReidentifyStringRequest.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/strings/requests/ReidentifyStringRequest.java similarity index 91% rename from src/main/java/com/skyflow/generated/rest/resources/strings/requests/ReidentifyStringRequest.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/strings/requests/ReidentifyStringRequest.java index 59c1aaf2..3c42390c 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/strings/requests/ReidentifyStringRequest.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/strings/requests/ReidentifyStringRequest.java @@ -3,16 +3,11 @@ */ package com.skyflow.generated.rest.resources.strings.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; import com.skyflow.generated.rest.types.Format; + import java.util.HashMap; import java.util.Map; import java.util.Objects; diff --git a/src/main/java/com/skyflow/generated/rest/resources/strings/types/DeidentifyStringRequestEntityTypesItem.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/strings/types/DeidentifyStringRequestEntityTypesItem.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/resources/strings/types/DeidentifyStringRequestEntityTypesItem.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/strings/types/DeidentifyStringRequestEntityTypesItem.java diff --git a/src/main/java/com/skyflow/generated/rest/resources/tokens/AsyncRawTokensClient.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/tokens/AsyncRawTokensClient.java similarity index 94% rename from src/main/java/com/skyflow/generated/rest/resources/tokens/AsyncRawTokensClient.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/tokens/AsyncRawTokensClient.java index fbf69580..794035bd 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/tokens/AsyncRawTokensClient.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/tokens/AsyncRawTokensClient.java @@ -4,30 +4,17 @@ package com.skyflow.generated.rest.resources.tokens; import com.fasterxml.jackson.core.JsonProcessingException; -import com.skyflow.generated.rest.core.ApiClientApiException; -import com.skyflow.generated.rest.core.ApiClientException; -import com.skyflow.generated.rest.core.ApiClientHttpResponse; -import com.skyflow.generated.rest.core.ClientOptions; -import com.skyflow.generated.rest.core.MediaTypes; -import com.skyflow.generated.rest.core.ObjectMappers; -import com.skyflow.generated.rest.core.RequestOptions; +import com.skyflow.generated.rest.core.*; import com.skyflow.generated.rest.errors.NotFoundError; import com.skyflow.generated.rest.resources.tokens.requests.V1DetokenizePayload; import com.skyflow.generated.rest.resources.tokens.requests.V1TokenizePayload; import com.skyflow.generated.rest.types.V1DetokenizeResponse; import com.skyflow.generated.rest.types.V1TokenizeResponse; +import okhttp3.*; +import org.jetbrains.annotations.NotNull; + import java.io.IOException; import java.util.concurrent.CompletableFuture; -import okhttp3.Call; -import okhttp3.Callback; -import okhttp3.Headers; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; -import okhttp3.ResponseBody; -import org.jetbrains.annotations.NotNull; public class AsyncRawTokensClient { protected final ClientOptions clientOptions; diff --git a/src/main/java/com/skyflow/generated/rest/resources/tokens/AsyncTokensClient.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/tokens/AsyncTokensClient.java similarity index 99% rename from src/main/java/com/skyflow/generated/rest/resources/tokens/AsyncTokensClient.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/tokens/AsyncTokensClient.java index 26d5ea05..621701af 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/tokens/AsyncTokensClient.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/tokens/AsyncTokensClient.java @@ -9,6 +9,7 @@ import com.skyflow.generated.rest.resources.tokens.requests.V1TokenizePayload; import com.skyflow.generated.rest.types.V1DetokenizeResponse; import com.skyflow.generated.rest.types.V1TokenizeResponse; + import java.util.concurrent.CompletableFuture; public class AsyncTokensClient { diff --git a/src/main/java/com/skyflow/generated/rest/resources/tokens/RawTokensClient.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/tokens/RawTokensClient.java similarity index 93% rename from src/main/java/com/skyflow/generated/rest/resources/tokens/RawTokensClient.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/tokens/RawTokensClient.java index 5ed17d28..7ad69d11 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/tokens/RawTokensClient.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/tokens/RawTokensClient.java @@ -4,26 +4,15 @@ package com.skyflow.generated.rest.resources.tokens; import com.fasterxml.jackson.core.JsonProcessingException; -import com.skyflow.generated.rest.core.ApiClientApiException; -import com.skyflow.generated.rest.core.ApiClientException; -import com.skyflow.generated.rest.core.ApiClientHttpResponse; -import com.skyflow.generated.rest.core.ClientOptions; -import com.skyflow.generated.rest.core.MediaTypes; -import com.skyflow.generated.rest.core.ObjectMappers; -import com.skyflow.generated.rest.core.RequestOptions; +import com.skyflow.generated.rest.core.*; import com.skyflow.generated.rest.errors.NotFoundError; import com.skyflow.generated.rest.resources.tokens.requests.V1DetokenizePayload; import com.skyflow.generated.rest.resources.tokens.requests.V1TokenizePayload; import com.skyflow.generated.rest.types.V1DetokenizeResponse; import com.skyflow.generated.rest.types.V1TokenizeResponse; +import okhttp3.*; + 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 RawTokensClient { protected final ClientOptions clientOptions; diff --git a/src/main/java/com/skyflow/generated/rest/resources/tokens/TokensClient.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/tokens/TokensClient.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/resources/tokens/TokensClient.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/tokens/TokensClient.java diff --git a/src/main/java/com/skyflow/generated/rest/resources/tokens/requests/V1DetokenizePayload.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/tokens/requests/V1DetokenizePayload.java similarity index 91% rename from src/main/java/com/skyflow/generated/rest/resources/tokens/requests/V1DetokenizePayload.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/tokens/requests/V1DetokenizePayload.java index 59318aa8..52571f64 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/tokens/requests/V1DetokenizePayload.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/tokens/requests/V1DetokenizePayload.java @@ -3,21 +3,12 @@ */ package com.skyflow.generated.rest.resources.tokens.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; import com.skyflow.generated.rest.types.V1DetokenizeRecordRequest; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; + +import java.util.*; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = V1DetokenizePayload.Builder.class) diff --git a/src/main/java/com/skyflow/generated/rest/resources/tokens/requests/V1TokenizePayload.java b/skyvault/src/main/java/com/skyflow/generated/rest/resources/tokens/requests/V1TokenizePayload.java similarity index 85% rename from src/main/java/com/skyflow/generated/rest/resources/tokens/requests/V1TokenizePayload.java rename to skyvault/src/main/java/com/skyflow/generated/rest/resources/tokens/requests/V1TokenizePayload.java index 9729023e..e5fc08f4 100644 --- a/src/main/java/com/skyflow/generated/rest/resources/tokens/requests/V1TokenizePayload.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/resources/tokens/requests/V1TokenizePayload.java @@ -3,21 +3,12 @@ */ package com.skyflow.generated.rest.resources.tokens.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; import com.skyflow.generated.rest.types.V1TokenizeRecordRequest; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; + +import java.util.*; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = V1TokenizePayload.Builder.class) diff --git a/src/main/java/com/skyflow/generated/rest/types/AuditEventAuditResourceType.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/AuditEventAuditResourceType.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/types/AuditEventAuditResourceType.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/AuditEventAuditResourceType.java diff --git a/src/main/java/com/skyflow/generated/rest/types/AuditEventContext.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/AuditEventContext.java similarity index 97% rename from src/main/java/com/skyflow/generated/rest/types/AuditEventContext.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/AuditEventContext.java index 210f96c0..73aed9c1 100644 --- a/src/main/java/com/skyflow/generated/rest/types/AuditEventContext.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/AuditEventContext.java @@ -3,15 +3,10 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; + import java.util.HashMap; import java.util.Map; import java.util.Objects; diff --git a/src/main/java/com/skyflow/generated/rest/types/AuditEventData.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/AuditEventData.java similarity index 87% rename from src/main/java/com/skyflow/generated/rest/types/AuditEventData.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/AuditEventData.java index e2028fba..bab4b42a 100644 --- a/src/main/java/com/skyflow/generated/rest/types/AuditEventData.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/AuditEventData.java @@ -3,15 +3,10 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; + import java.util.HashMap; import java.util.Map; import java.util.Objects; diff --git a/src/main/java/com/skyflow/generated/rest/types/AuditEventHttpInfo.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/AuditEventHttpInfo.java similarity index 89% rename from src/main/java/com/skyflow/generated/rest/types/AuditEventHttpInfo.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/AuditEventHttpInfo.java index 1352c42f..9d3bbaed 100644 --- a/src/main/java/com/skyflow/generated/rest/types/AuditEventHttpInfo.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/AuditEventHttpInfo.java @@ -3,15 +3,10 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; + import java.util.HashMap; import java.util.Map; import java.util.Objects; diff --git a/src/main/java/com/skyflow/generated/rest/types/BatchRecordMethod.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/BatchRecordMethod.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/types/BatchRecordMethod.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/BatchRecordMethod.java diff --git a/src/main/java/com/skyflow/generated/rest/types/ContextAccessType.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/ContextAccessType.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/types/ContextAccessType.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/ContextAccessType.java diff --git a/src/main/java/com/skyflow/generated/rest/types/ContextAuthMode.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/ContextAuthMode.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/types/ContextAuthMode.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/ContextAuthMode.java diff --git a/src/main/java/com/skyflow/generated/rest/types/DeidentifiedFileOutput.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/DeidentifiedFileOutput.java similarity index 93% rename from src/main/java/com/skyflow/generated/rest/types/DeidentifiedFileOutput.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/DeidentifiedFileOutput.java index a7dd7c68..dc244be8 100644 --- a/src/main/java/com/skyflow/generated/rest/types/DeidentifiedFileOutput.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/DeidentifiedFileOutput.java @@ -3,15 +3,10 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; + import java.util.HashMap; import java.util.Map; import java.util.Objects; diff --git a/src/main/java/com/skyflow/generated/rest/types/DeidentifiedFileOutputProcessedFileExtension.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/DeidentifiedFileOutputProcessedFileExtension.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/types/DeidentifiedFileOutputProcessedFileExtension.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/DeidentifiedFileOutputProcessedFileExtension.java diff --git a/src/main/java/com/skyflow/generated/rest/types/DeidentifiedFileOutputProcessedFileType.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/DeidentifiedFileOutputProcessedFileType.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/types/DeidentifiedFileOutputProcessedFileType.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/DeidentifiedFileOutputProcessedFileType.java diff --git a/src/main/java/com/skyflow/generated/rest/types/DeidentifyFileResponse.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/DeidentifyFileResponse.java similarity index 87% rename from src/main/java/com/skyflow/generated/rest/types/DeidentifyFileResponse.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/DeidentifyFileResponse.java index d2b8e5b6..d5a2ebd5 100644 --- a/src/main/java/com/skyflow/generated/rest/types/DeidentifyFileResponse.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/DeidentifyFileResponse.java @@ -3,15 +3,10 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; + import java.util.HashMap; import java.util.Map; import java.util.Objects; diff --git a/src/main/java/com/skyflow/generated/rest/types/DeidentifyStringResponse.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/DeidentifyStringResponse.java similarity index 91% rename from src/main/java/com/skyflow/generated/rest/types/DeidentifyStringResponse.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/DeidentifyStringResponse.java index e831f142..1389fa0f 100644 --- a/src/main/java/com/skyflow/generated/rest/types/DeidentifyStringResponse.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/DeidentifyStringResponse.java @@ -3,20 +3,11 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; + +import java.util.*; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = DeidentifyStringResponse.Builder.class) diff --git a/src/main/java/com/skyflow/generated/rest/types/DetectGuardrailsResponse.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/DetectGuardrailsResponse.java similarity index 94% rename from src/main/java/com/skyflow/generated/rest/types/DetectGuardrailsResponse.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/DetectGuardrailsResponse.java index 9773dcc3..9cb2d319 100644 --- a/src/main/java/com/skyflow/generated/rest/types/DetectGuardrailsResponse.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/DetectGuardrailsResponse.java @@ -3,20 +3,15 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; +import org.jetbrains.annotations.NotNull; + import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.Optional; -import org.jetbrains.annotations.NotNull; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = DetectGuardrailsResponse.Builder.class) diff --git a/src/main/java/com/skyflow/generated/rest/types/DetectGuardrailsResponseValidation.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/DetectGuardrailsResponseValidation.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/types/DetectGuardrailsResponseValidation.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/DetectGuardrailsResponseValidation.java diff --git a/src/main/java/com/skyflow/generated/rest/types/DetectRunsResponse.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/DetectRunsResponse.java similarity index 95% rename from src/main/java/com/skyflow/generated/rest/types/DetectRunsResponse.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/DetectRunsResponse.java index c97f7635..b73f7845 100644 --- a/src/main/java/com/skyflow/generated/rest/types/DetectRunsResponse.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/DetectRunsResponse.java @@ -3,20 +3,11 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; + +import java.util.*; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = DetectRunsResponse.Builder.class) diff --git a/src/main/java/com/skyflow/generated/rest/types/DetectRunsResponseOutputType.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/DetectRunsResponseOutputType.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/types/DetectRunsResponseOutputType.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/DetectRunsResponseOutputType.java diff --git a/src/main/java/com/skyflow/generated/rest/types/DetectRunsResponseStatus.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/DetectRunsResponseStatus.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/types/DetectRunsResponseStatus.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/DetectRunsResponseStatus.java diff --git a/src/main/java/com/skyflow/generated/rest/types/DetokenizeRecordResponseValueType.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/DetokenizeRecordResponseValueType.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/types/DetokenizeRecordResponseValueType.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/DetokenizeRecordResponseValueType.java diff --git a/src/main/java/com/skyflow/generated/rest/types/ErrorResponse.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/ErrorResponse.java similarity index 88% rename from src/main/java/com/skyflow/generated/rest/types/ErrorResponse.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/ErrorResponse.java index 565714eb..4113b998 100644 --- a/src/main/java/com/skyflow/generated/rest/types/ErrorResponse.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/ErrorResponse.java @@ -3,18 +3,14 @@ */ package com.skyflow.generated.rest.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.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; +import org.jetbrains.annotations.NotNull; + import java.util.HashMap; import java.util.Map; import java.util.Objects; -import org.jetbrains.annotations.NotNull; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = ErrorResponse.Builder.class) diff --git a/src/main/java/com/skyflow/generated/rest/types/ErrorResponseError.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/ErrorResponseError.java similarity index 92% rename from src/main/java/com/skyflow/generated/rest/types/ErrorResponseError.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/ErrorResponseError.java index bcc23f03..4774fa57 100644 --- a/src/main/java/com/skyflow/generated/rest/types/ErrorResponseError.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/ErrorResponseError.java @@ -3,22 +3,13 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; import org.jetbrains.annotations.NotNull; +import java.util.*; + @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = ErrorResponseError.Builder.class) public final class ErrorResponseError { diff --git a/src/main/java/com/skyflow/generated/rest/types/FileData.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/FileData.java similarity index 92% rename from src/main/java/com/skyflow/generated/rest/types/FileData.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/FileData.java index 7f58332e..fd6fa822 100644 --- a/src/main/java/com/skyflow/generated/rest/types/FileData.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/FileData.java @@ -3,18 +3,14 @@ */ package com.skyflow.generated.rest.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.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; +import org.jetbrains.annotations.NotNull; + import java.util.HashMap; import java.util.Map; import java.util.Objects; -import org.jetbrains.annotations.NotNull; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = FileData.Builder.class) diff --git a/src/main/java/com/skyflow/generated/rest/types/FileDataDataFormat.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/FileDataDataFormat.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/types/FileDataDataFormat.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/FileDataDataFormat.java diff --git a/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyAudio.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyAudio.java similarity index 92% rename from src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyAudio.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyAudio.java index 2aa88f7b..b6a5ea11 100644 --- a/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyAudio.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyAudio.java @@ -3,18 +3,14 @@ */ package com.skyflow.generated.rest.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.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; +import org.jetbrains.annotations.NotNull; + import java.util.HashMap; import java.util.Map; import java.util.Objects; -import org.jetbrains.annotations.NotNull; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = FileDataDeidentifyAudio.Builder.class) diff --git a/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyAudioDataFormat.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyAudioDataFormat.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyAudioDataFormat.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyAudioDataFormat.java diff --git a/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyDocument.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyDocument.java similarity index 92% rename from src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyDocument.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyDocument.java index e6390e28..5fb12fba 100644 --- a/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyDocument.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyDocument.java @@ -3,18 +3,14 @@ */ package com.skyflow.generated.rest.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.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; +import org.jetbrains.annotations.NotNull; + import java.util.HashMap; import java.util.Map; import java.util.Objects; -import org.jetbrains.annotations.NotNull; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = FileDataDeidentifyDocument.Builder.class) diff --git a/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyDocumentDataFormat.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyDocumentDataFormat.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyDocumentDataFormat.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyDocumentDataFormat.java diff --git a/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyImage.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyImage.java similarity index 92% rename from src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyImage.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyImage.java index 127c5f4c..bc5e2c6b 100644 --- a/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyImage.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyImage.java @@ -3,18 +3,14 @@ */ package com.skyflow.generated.rest.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.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; +import org.jetbrains.annotations.NotNull; + import java.util.HashMap; import java.util.Map; import java.util.Objects; -import org.jetbrains.annotations.NotNull; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = FileDataDeidentifyImage.Builder.class) diff --git a/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyImageDataFormat.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyImageDataFormat.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyImageDataFormat.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyImageDataFormat.java diff --git a/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyPdf.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyPdf.java similarity index 90% rename from src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyPdf.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyPdf.java index 20b0314f..5606e917 100644 --- a/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyPdf.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyPdf.java @@ -3,18 +3,14 @@ */ package com.skyflow.generated.rest.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.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; +import org.jetbrains.annotations.NotNull; + import java.util.HashMap; import java.util.Map; import java.util.Objects; -import org.jetbrains.annotations.NotNull; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = FileDataDeidentifyPdf.Builder.class) diff --git a/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyPresentation.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyPresentation.java similarity index 92% rename from src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyPresentation.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyPresentation.java index e808f0b2..0bedabc5 100644 --- a/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyPresentation.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyPresentation.java @@ -3,18 +3,14 @@ */ package com.skyflow.generated.rest.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.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; +import org.jetbrains.annotations.NotNull; + import java.util.HashMap; import java.util.Map; import java.util.Objects; -import org.jetbrains.annotations.NotNull; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = FileDataDeidentifyPresentation.Builder.class) diff --git a/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyPresentationDataFormat.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyPresentationDataFormat.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyPresentationDataFormat.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyPresentationDataFormat.java diff --git a/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifySpreadsheet.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifySpreadsheet.java similarity index 92% rename from src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifySpreadsheet.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifySpreadsheet.java index 33478d20..dd017bb8 100644 --- a/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifySpreadsheet.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifySpreadsheet.java @@ -3,18 +3,14 @@ */ package com.skyflow.generated.rest.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.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; +import org.jetbrains.annotations.NotNull; + import java.util.HashMap; import java.util.Map; import java.util.Objects; -import org.jetbrains.annotations.NotNull; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = FileDataDeidentifySpreadsheet.Builder.class) diff --git a/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifySpreadsheetDataFormat.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifySpreadsheetDataFormat.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifySpreadsheetDataFormat.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifySpreadsheetDataFormat.java diff --git a/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyStructuredText.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyStructuredText.java similarity index 92% rename from src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyStructuredText.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyStructuredText.java index dad188ce..814d51c3 100644 --- a/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyStructuredText.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyStructuredText.java @@ -3,18 +3,14 @@ */ package com.skyflow.generated.rest.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.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; +import org.jetbrains.annotations.NotNull; + import java.util.HashMap; import java.util.Map; import java.util.Objects; -import org.jetbrains.annotations.NotNull; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = FileDataDeidentifyStructuredText.Builder.class) diff --git a/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyStructuredTextDataFormat.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyStructuredTextDataFormat.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyStructuredTextDataFormat.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyStructuredTextDataFormat.java diff --git a/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyText.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyText.java similarity index 90% rename from src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyText.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyText.java index 79045d7a..e5c88180 100644 --- a/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyText.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/FileDataDeidentifyText.java @@ -3,18 +3,14 @@ */ package com.skyflow.generated.rest.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.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; +import org.jetbrains.annotations.NotNull; + import java.util.HashMap; import java.util.Map; import java.util.Objects; -import org.jetbrains.annotations.NotNull; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = FileDataDeidentifyText.Builder.class) diff --git a/src/main/java/com/skyflow/generated/rest/types/FileDataReidentifyFile.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/FileDataReidentifyFile.java similarity index 92% rename from src/main/java/com/skyflow/generated/rest/types/FileDataReidentifyFile.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/FileDataReidentifyFile.java index edea3145..9148703c 100644 --- a/src/main/java/com/skyflow/generated/rest/types/FileDataReidentifyFile.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/FileDataReidentifyFile.java @@ -3,18 +3,14 @@ */ package com.skyflow.generated.rest.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.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; +import org.jetbrains.annotations.NotNull; + import java.util.HashMap; import java.util.Map; import java.util.Objects; -import org.jetbrains.annotations.NotNull; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = FileDataReidentifyFile.Builder.class) diff --git a/src/main/java/com/skyflow/generated/rest/types/FileDataReidentifyFileDataFormat.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/FileDataReidentifyFileDataFormat.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/types/FileDataReidentifyFileDataFormat.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/FileDataReidentifyFileDataFormat.java diff --git a/src/main/java/com/skyflow/generated/rest/types/Format.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/Format.java similarity index 89% rename from src/main/java/com/skyflow/generated/rest/types/Format.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/Format.java index 68ac94ca..1563b90a 100644 --- a/src/main/java/com/skyflow/generated/rest/types/Format.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/Format.java @@ -3,20 +3,11 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; + +import java.util.*; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = Format.Builder.class) diff --git a/src/main/java/com/skyflow/generated/rest/types/FormatMaskedItem.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/FormatMaskedItem.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/types/FormatMaskedItem.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/FormatMaskedItem.java diff --git a/src/main/java/com/skyflow/generated/rest/types/FormatPlaintextItem.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/FormatPlaintextItem.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/types/FormatPlaintextItem.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/FormatPlaintextItem.java diff --git a/src/main/java/com/skyflow/generated/rest/types/FormatRedactedItem.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/FormatRedactedItem.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/types/FormatRedactedItem.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/FormatRedactedItem.java diff --git a/src/main/java/com/skyflow/generated/rest/types/GooglerpcStatus.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/GooglerpcStatus.java similarity index 88% rename from src/main/java/com/skyflow/generated/rest/types/GooglerpcStatus.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/GooglerpcStatus.java index 807aee4e..55be5699 100644 --- a/src/main/java/com/skyflow/generated/rest/types/GooglerpcStatus.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/GooglerpcStatus.java @@ -3,20 +3,11 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; + +import java.util.*; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = GooglerpcStatus.Builder.class) diff --git a/src/main/java/com/skyflow/generated/rest/types/IdentifyResponse.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/IdentifyResponse.java similarity index 89% rename from src/main/java/com/skyflow/generated/rest/types/IdentifyResponse.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/IdentifyResponse.java index e7ae30fc..c1bd23c6 100644 --- a/src/main/java/com/skyflow/generated/rest/types/IdentifyResponse.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/IdentifyResponse.java @@ -3,18 +3,14 @@ */ package com.skyflow.generated.rest.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.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; +import org.jetbrains.annotations.NotNull; + import java.util.HashMap; import java.util.Map; import java.util.Objects; -import org.jetbrains.annotations.NotNull; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = IdentifyResponse.Builder.class) diff --git a/src/main/java/com/skyflow/generated/rest/types/Locations.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/Locations.java similarity index 94% rename from src/main/java/com/skyflow/generated/rest/types/Locations.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/Locations.java index 9864541a..4b95c485 100644 --- a/src/main/java/com/skyflow/generated/rest/types/Locations.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/Locations.java @@ -3,15 +3,10 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; + import java.util.HashMap; import java.util.Map; import java.util.Objects; diff --git a/src/main/java/com/skyflow/generated/rest/types/ProtobufAny.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/ProtobufAny.java similarity index 86% rename from src/main/java/com/skyflow/generated/rest/types/ProtobufAny.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/ProtobufAny.java index 9f019b66..40e73b99 100644 --- a/src/main/java/com/skyflow/generated/rest/types/ProtobufAny.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/ProtobufAny.java @@ -3,15 +3,10 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; + import java.util.HashMap; import java.util.Map; import java.util.Objects; diff --git a/src/main/java/com/skyflow/generated/rest/types/RedactionEnumRedaction.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/RedactionEnumRedaction.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/types/RedactionEnumRedaction.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/RedactionEnumRedaction.java diff --git a/src/main/java/com/skyflow/generated/rest/types/ReidentifiedFileOutput.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/ReidentifiedFileOutput.java similarity index 93% rename from src/main/java/com/skyflow/generated/rest/types/ReidentifiedFileOutput.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/ReidentifiedFileOutput.java index b5a4f6f5..a12660cb 100644 --- a/src/main/java/com/skyflow/generated/rest/types/ReidentifiedFileOutput.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/ReidentifiedFileOutput.java @@ -3,15 +3,10 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; + import java.util.HashMap; import java.util.Map; import java.util.Objects; diff --git a/src/main/java/com/skyflow/generated/rest/types/ReidentifiedFileOutputProcessedFileExtension.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/ReidentifiedFileOutputProcessedFileExtension.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/types/ReidentifiedFileOutputProcessedFileExtension.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/ReidentifiedFileOutputProcessedFileExtension.java diff --git a/src/main/java/com/skyflow/generated/rest/types/ReidentifyFileResponse.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/ReidentifyFileResponse.java similarity index 92% rename from src/main/java/com/skyflow/generated/rest/types/ReidentifyFileResponse.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/ReidentifyFileResponse.java index 024200d1..b7bc22ca 100644 --- a/src/main/java/com/skyflow/generated/rest/types/ReidentifyFileResponse.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/ReidentifyFileResponse.java @@ -3,15 +3,10 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; + import java.util.HashMap; import java.util.Map; import java.util.Objects; diff --git a/src/main/java/com/skyflow/generated/rest/types/ReidentifyFileResponseOutputType.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/ReidentifyFileResponseOutputType.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/types/ReidentifyFileResponseOutputType.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/ReidentifyFileResponseOutputType.java diff --git a/src/main/java/com/skyflow/generated/rest/types/ReidentifyFileResponseStatus.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/ReidentifyFileResponseStatus.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/types/ReidentifyFileResponseStatus.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/ReidentifyFileResponseStatus.java diff --git a/src/main/java/com/skyflow/generated/rest/types/RequestActionType.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/RequestActionType.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/types/RequestActionType.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/RequestActionType.java diff --git a/src/main/java/com/skyflow/generated/rest/types/ShiftDates.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/ShiftDates.java similarity index 89% rename from src/main/java/com/skyflow/generated/rest/types/ShiftDates.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/ShiftDates.java index 2fc4b11a..37ef8cdf 100644 --- a/src/main/java/com/skyflow/generated/rest/types/ShiftDates.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/ShiftDates.java @@ -3,20 +3,11 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; + +import java.util.*; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = ShiftDates.Builder.class) diff --git a/src/main/java/com/skyflow/generated/rest/types/ShiftDatesEntityTypesItem.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/ShiftDatesEntityTypesItem.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/types/ShiftDatesEntityTypesItem.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/ShiftDatesEntityTypesItem.java diff --git a/src/main/java/com/skyflow/generated/rest/types/StringResponseEntities.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/StringResponseEntities.java similarity index 94% rename from src/main/java/com/skyflow/generated/rest/types/StringResponseEntities.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/StringResponseEntities.java index 81c1e58d..78df8838 100644 --- a/src/main/java/com/skyflow/generated/rest/types/StringResponseEntities.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/StringResponseEntities.java @@ -3,15 +3,10 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; + import java.util.HashMap; import java.util.Map; import java.util.Objects; diff --git a/src/main/java/com/skyflow/generated/rest/types/TokenTypeMapping.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/TokenTypeMapping.java similarity index 92% rename from src/main/java/com/skyflow/generated/rest/types/TokenTypeMapping.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/TokenTypeMapping.java index f8c1edb4..b703895d 100644 --- a/src/main/java/com/skyflow/generated/rest/types/TokenTypeMapping.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/TokenTypeMapping.java @@ -3,20 +3,11 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; + +import java.util.*; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = TokenTypeMapping.Builder.class) diff --git a/src/main/java/com/skyflow/generated/rest/types/TokenTypeMappingDefault.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/TokenTypeMappingDefault.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/types/TokenTypeMappingDefault.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/TokenTypeMappingDefault.java diff --git a/src/main/java/com/skyflow/generated/rest/types/TokenTypeMappingEntityOnlyItem.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/TokenTypeMappingEntityOnlyItem.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/types/TokenTypeMappingEntityOnlyItem.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/TokenTypeMappingEntityOnlyItem.java diff --git a/src/main/java/com/skyflow/generated/rest/types/TokenTypeMappingEntityUnqCounterItem.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/TokenTypeMappingEntityUnqCounterItem.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/types/TokenTypeMappingEntityUnqCounterItem.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/TokenTypeMappingEntityUnqCounterItem.java diff --git a/src/main/java/com/skyflow/generated/rest/types/TokenTypeMappingVaultTokenItem.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/TokenTypeMappingVaultTokenItem.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/types/TokenTypeMappingVaultTokenItem.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/TokenTypeMappingVaultTokenItem.java diff --git a/src/main/java/com/skyflow/generated/rest/types/Transformations.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/Transformations.java similarity index 87% rename from src/main/java/com/skyflow/generated/rest/types/Transformations.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/Transformations.java index 12a5afc7..c9165409 100644 --- a/src/main/java/com/skyflow/generated/rest/types/Transformations.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/Transformations.java @@ -3,15 +3,10 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; + import java.util.HashMap; import java.util.Map; import java.util.Objects; diff --git a/src/main/java/com/skyflow/generated/rest/types/UploadFileV2Response.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/UploadFileV2Response.java similarity index 90% rename from src/main/java/com/skyflow/generated/rest/types/UploadFileV2Response.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/UploadFileV2Response.java index c1ddbd58..8f5fea5a 100644 --- a/src/main/java/com/skyflow/generated/rest/types/UploadFileV2Response.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/UploadFileV2Response.java @@ -3,15 +3,10 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; + import java.util.HashMap; import java.util.Map; import java.util.Objects; diff --git a/src/main/java/com/skyflow/generated/rest/types/V1AuditAfterOptions.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1AuditAfterOptions.java similarity index 92% rename from src/main/java/com/skyflow/generated/rest/types/V1AuditAfterOptions.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/V1AuditAfterOptions.java index d942fdcd..037b80bf 100644 --- a/src/main/java/com/skyflow/generated/rest/types/V1AuditAfterOptions.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1AuditAfterOptions.java @@ -3,15 +3,10 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; + import java.util.HashMap; import java.util.Map; import java.util.Objects; diff --git a/src/main/java/com/skyflow/generated/rest/types/V1AuditEventResponse.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1AuditEventResponse.java similarity index 93% rename from src/main/java/com/skyflow/generated/rest/types/V1AuditEventResponse.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/V1AuditEventResponse.java index c582d06d..f7ca73f0 100644 --- a/src/main/java/com/skyflow/generated/rest/types/V1AuditEventResponse.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1AuditEventResponse.java @@ -3,15 +3,10 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; + import java.util.HashMap; import java.util.Map; import java.util.Objects; diff --git a/src/main/java/com/skyflow/generated/rest/types/V1AuditResponse.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1AuditResponse.java similarity index 86% rename from src/main/java/com/skyflow/generated/rest/types/V1AuditResponse.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/V1AuditResponse.java index dd23d77c..36d7ccce 100644 --- a/src/main/java/com/skyflow/generated/rest/types/V1AuditResponse.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1AuditResponse.java @@ -3,20 +3,11 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; + +import java.util.*; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = V1AuditResponse.Builder.class) diff --git a/src/main/java/com/skyflow/generated/rest/types/V1AuditResponseEvent.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1AuditResponseEvent.java similarity index 93% rename from src/main/java/com/skyflow/generated/rest/types/V1AuditResponseEvent.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/V1AuditResponseEvent.java index ca2bfdc2..a54eec6c 100644 --- a/src/main/java/com/skyflow/generated/rest/types/V1AuditResponseEvent.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1AuditResponseEvent.java @@ -3,20 +3,11 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; + +import java.util.*; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = V1AuditResponseEvent.Builder.class) diff --git a/src/main/java/com/skyflow/generated/rest/types/V1AuditResponseEventRequest.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1AuditResponseEventRequest.java similarity index 95% rename from src/main/java/com/skyflow/generated/rest/types/V1AuditResponseEventRequest.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/V1AuditResponseEventRequest.java index 046c8fb4..90d9c538 100644 --- a/src/main/java/com/skyflow/generated/rest/types/V1AuditResponseEventRequest.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1AuditResponseEventRequest.java @@ -3,20 +3,11 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; + +import java.util.*; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = V1AuditResponseEventRequest.Builder.class) diff --git a/src/main/java/com/skyflow/generated/rest/types/V1BatchOperationResponse.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1BatchOperationResponse.java similarity index 89% rename from src/main/java/com/skyflow/generated/rest/types/V1BatchOperationResponse.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/V1BatchOperationResponse.java index a557da82..8f5fb775 100644 --- a/src/main/java/com/skyflow/generated/rest/types/V1BatchOperationResponse.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1BatchOperationResponse.java @@ -3,20 +3,11 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; + +import java.util.*; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = V1BatchOperationResponse.Builder.class) diff --git a/src/main/java/com/skyflow/generated/rest/types/V1BatchRecord.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1BatchRecord.java similarity index 97% rename from src/main/java/com/skyflow/generated/rest/types/V1BatchRecord.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/V1BatchRecord.java index afc572cf..3f0455cc 100644 --- a/src/main/java/com/skyflow/generated/rest/types/V1BatchRecord.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1BatchRecord.java @@ -3,15 +3,10 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; + import java.util.HashMap; import java.util.Map; import java.util.Objects; diff --git a/src/main/java/com/skyflow/generated/rest/types/V1BinListResponse.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1BinListResponse.java similarity index 83% rename from src/main/java/com/skyflow/generated/rest/types/V1BinListResponse.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/V1BinListResponse.java index 58472a8b..88c1c125 100644 --- a/src/main/java/com/skyflow/generated/rest/types/V1BinListResponse.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1BinListResponse.java @@ -3,20 +3,11 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; + +import java.util.*; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = V1BinListResponse.Builder.class) diff --git a/src/main/java/com/skyflow/generated/rest/types/V1BulkDeleteRecordResponse.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1BulkDeleteRecordResponse.java similarity index 85% rename from src/main/java/com/skyflow/generated/rest/types/V1BulkDeleteRecordResponse.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/V1BulkDeleteRecordResponse.java index ddb0d213..b7151598 100644 --- a/src/main/java/com/skyflow/generated/rest/types/V1BulkDeleteRecordResponse.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1BulkDeleteRecordResponse.java @@ -3,20 +3,11 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; + +import java.util.*; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = V1BulkDeleteRecordResponse.Builder.class) diff --git a/src/main/java/com/skyflow/generated/rest/types/V1BulkGetRecordResponse.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1BulkGetRecordResponse.java similarity index 83% rename from src/main/java/com/skyflow/generated/rest/types/V1BulkGetRecordResponse.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/V1BulkGetRecordResponse.java index c2d3790a..27878fab 100644 --- a/src/main/java/com/skyflow/generated/rest/types/V1BulkGetRecordResponse.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1BulkGetRecordResponse.java @@ -3,20 +3,11 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; + +import java.util.*; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = V1BulkGetRecordResponse.Builder.class) diff --git a/src/main/java/com/skyflow/generated/rest/types/V1Byot.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1Byot.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/types/V1Byot.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/V1Byot.java diff --git a/src/main/java/com/skyflow/generated/rest/types/V1Card.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1Card.java similarity index 96% rename from src/main/java/com/skyflow/generated/rest/types/V1Card.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/V1Card.java index f2a9db90..ba4f7349 100644 --- a/src/main/java/com/skyflow/generated/rest/types/V1Card.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1Card.java @@ -3,15 +3,10 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; + import java.util.HashMap; import java.util.Map; import java.util.Objects; diff --git a/src/main/java/com/skyflow/generated/rest/types/V1DeleteFileResponse.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1DeleteFileResponse.java similarity index 90% rename from src/main/java/com/skyflow/generated/rest/types/V1DeleteFileResponse.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/V1DeleteFileResponse.java index 4ec85903..4d9b24eb 100644 --- a/src/main/java/com/skyflow/generated/rest/types/V1DeleteFileResponse.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1DeleteFileResponse.java @@ -3,15 +3,10 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; + import java.util.HashMap; import java.util.Map; import java.util.Objects; diff --git a/src/main/java/com/skyflow/generated/rest/types/V1DeleteRecordResponse.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1DeleteRecordResponse.java similarity index 90% rename from src/main/java/com/skyflow/generated/rest/types/V1DeleteRecordResponse.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/V1DeleteRecordResponse.java index 2f3bde14..5351d897 100644 --- a/src/main/java/com/skyflow/generated/rest/types/V1DeleteRecordResponse.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1DeleteRecordResponse.java @@ -3,15 +3,10 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; + import java.util.HashMap; import java.util.Map; import java.util.Objects; diff --git a/src/main/java/com/skyflow/generated/rest/types/V1DetokenizeRecordRequest.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1DetokenizeRecordRequest.java similarity index 90% rename from src/main/java/com/skyflow/generated/rest/types/V1DetokenizeRecordRequest.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/V1DetokenizeRecordRequest.java index 3cf5b59c..b6fb9cb6 100644 --- a/src/main/java/com/skyflow/generated/rest/types/V1DetokenizeRecordRequest.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1DetokenizeRecordRequest.java @@ -3,15 +3,10 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; + import java.util.HashMap; import java.util.Map; import java.util.Objects; diff --git a/src/main/java/com/skyflow/generated/rest/types/V1DetokenizeRecordResponse.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1DetokenizeRecordResponse.java similarity index 93% rename from src/main/java/com/skyflow/generated/rest/types/V1DetokenizeRecordResponse.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/V1DetokenizeRecordResponse.java index ba0abbfa..dd3a9c1f 100644 --- a/src/main/java/com/skyflow/generated/rest/types/V1DetokenizeRecordResponse.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1DetokenizeRecordResponse.java @@ -3,15 +3,10 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; + import java.util.HashMap; import java.util.Map; import java.util.Objects; diff --git a/src/main/java/com/skyflow/generated/rest/types/V1DetokenizeResponse.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1DetokenizeResponse.java similarity index 84% rename from src/main/java/com/skyflow/generated/rest/types/V1DetokenizeResponse.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/V1DetokenizeResponse.java index 74f4f6e6..53660cbe 100644 --- a/src/main/java/com/skyflow/generated/rest/types/V1DetokenizeResponse.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1DetokenizeResponse.java @@ -3,20 +3,11 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; + +import java.util.*; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = V1DetokenizeResponse.Builder.class) diff --git a/src/main/java/com/skyflow/generated/rest/types/V1FieldRecords.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1FieldRecords.java similarity index 91% rename from src/main/java/com/skyflow/generated/rest/types/V1FieldRecords.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/V1FieldRecords.java index dfbccb8b..b64b8792 100644 --- a/src/main/java/com/skyflow/generated/rest/types/V1FieldRecords.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1FieldRecords.java @@ -3,15 +3,10 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; + import java.util.HashMap; import java.util.Map; import java.util.Objects; diff --git a/src/main/java/com/skyflow/generated/rest/types/V1FileAvScanStatus.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1FileAvScanStatus.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/types/V1FileAvScanStatus.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/V1FileAvScanStatus.java diff --git a/src/main/java/com/skyflow/generated/rest/types/V1GetAuthTokenResponse.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1GetAuthTokenResponse.java similarity index 90% rename from src/main/java/com/skyflow/generated/rest/types/V1GetAuthTokenResponse.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/V1GetAuthTokenResponse.java index f5669085..43a86ae0 100644 --- a/src/main/java/com/skyflow/generated/rest/types/V1GetAuthTokenResponse.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1GetAuthTokenResponse.java @@ -3,15 +3,10 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; + import java.util.HashMap; import java.util.Map; import java.util.Objects; diff --git a/src/main/java/com/skyflow/generated/rest/types/V1GetFileScanStatusResponse.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1GetFileScanStatusResponse.java similarity index 87% rename from src/main/java/com/skyflow/generated/rest/types/V1GetFileScanStatusResponse.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/V1GetFileScanStatusResponse.java index 4bd67013..3138c766 100644 --- a/src/main/java/com/skyflow/generated/rest/types/V1GetFileScanStatusResponse.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1GetFileScanStatusResponse.java @@ -3,15 +3,10 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; + import java.util.HashMap; import java.util.Map; import java.util.Objects; diff --git a/src/main/java/com/skyflow/generated/rest/types/V1GetQueryResponse.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1GetQueryResponse.java similarity index 83% rename from src/main/java/com/skyflow/generated/rest/types/V1GetQueryResponse.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/V1GetQueryResponse.java index 821d1f06..a44b0f62 100644 --- a/src/main/java/com/skyflow/generated/rest/types/V1GetQueryResponse.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1GetQueryResponse.java @@ -3,20 +3,11 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; + +import java.util.*; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = V1GetQueryResponse.Builder.class) diff --git a/src/main/java/com/skyflow/generated/rest/types/V1InsertRecordResponse.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1InsertRecordResponse.java similarity index 84% rename from src/main/java/com/skyflow/generated/rest/types/V1InsertRecordResponse.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/V1InsertRecordResponse.java index 0addf786..f8a00001 100644 --- a/src/main/java/com/skyflow/generated/rest/types/V1InsertRecordResponse.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1InsertRecordResponse.java @@ -3,20 +3,11 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; + +import java.util.*; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = V1InsertRecordResponse.Builder.class) diff --git a/src/main/java/com/skyflow/generated/rest/types/V1MemberType.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1MemberType.java similarity index 100% rename from src/main/java/com/skyflow/generated/rest/types/V1MemberType.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/V1MemberType.java diff --git a/src/main/java/com/skyflow/generated/rest/types/V1RecordMetaProperties.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1RecordMetaProperties.java similarity index 90% rename from src/main/java/com/skyflow/generated/rest/types/V1RecordMetaProperties.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/V1RecordMetaProperties.java index f69719cf..17f77df6 100644 --- a/src/main/java/com/skyflow/generated/rest/types/V1RecordMetaProperties.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1RecordMetaProperties.java @@ -3,15 +3,10 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; + import java.util.HashMap; import java.util.Map; import java.util.Objects; diff --git a/src/main/java/com/skyflow/generated/rest/types/V1TokenizeRecordRequest.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1TokenizeRecordRequest.java similarity index 90% rename from src/main/java/com/skyflow/generated/rest/types/V1TokenizeRecordRequest.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/V1TokenizeRecordRequest.java index 2c15ceac..43fe264a 100644 --- a/src/main/java/com/skyflow/generated/rest/types/V1TokenizeRecordRequest.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1TokenizeRecordRequest.java @@ -3,15 +3,10 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; + import java.util.HashMap; import java.util.Map; import java.util.Objects; diff --git a/src/main/java/com/skyflow/generated/rest/types/V1TokenizeRecordResponse.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1TokenizeRecordResponse.java similarity index 87% rename from src/main/java/com/skyflow/generated/rest/types/V1TokenizeRecordResponse.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/V1TokenizeRecordResponse.java index 201a4f60..66721fa1 100644 --- a/src/main/java/com/skyflow/generated/rest/types/V1TokenizeRecordResponse.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1TokenizeRecordResponse.java @@ -3,15 +3,10 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; + import java.util.HashMap; import java.util.Map; import java.util.Objects; diff --git a/src/main/java/com/skyflow/generated/rest/types/V1TokenizeResponse.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1TokenizeResponse.java similarity index 84% rename from src/main/java/com/skyflow/generated/rest/types/V1TokenizeResponse.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/V1TokenizeResponse.java index 62d77bed..05e64bc0 100644 --- a/src/main/java/com/skyflow/generated/rest/types/V1TokenizeResponse.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1TokenizeResponse.java @@ -3,20 +3,11 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; + +import java.util.*; @JsonInclude(JsonInclude.Include.NON_ABSENT) @JsonDeserialize(builder = V1TokenizeResponse.Builder.class) diff --git a/src/main/java/com/skyflow/generated/rest/types/V1UpdateRecordResponse.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1UpdateRecordResponse.java similarity index 90% rename from src/main/java/com/skyflow/generated/rest/types/V1UpdateRecordResponse.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/V1UpdateRecordResponse.java index 03ed7b58..d2fcbf1b 100644 --- a/src/main/java/com/skyflow/generated/rest/types/V1UpdateRecordResponse.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1UpdateRecordResponse.java @@ -3,15 +3,10 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; + import java.util.HashMap; import java.util.Map; import java.util.Objects; diff --git a/src/main/java/com/skyflow/generated/rest/types/V1VaultFieldMapping.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1VaultFieldMapping.java similarity index 92% rename from src/main/java/com/skyflow/generated/rest/types/V1VaultFieldMapping.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/V1VaultFieldMapping.java index c19d1cb5..6fc09854 100644 --- a/src/main/java/com/skyflow/generated/rest/types/V1VaultFieldMapping.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1VaultFieldMapping.java @@ -3,15 +3,10 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; + import java.util.HashMap; import java.util.Map; import java.util.Objects; diff --git a/src/main/java/com/skyflow/generated/rest/types/V1VaultSchemaConfig.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1VaultSchemaConfig.java similarity index 91% rename from src/main/java/com/skyflow/generated/rest/types/V1VaultSchemaConfig.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/V1VaultSchemaConfig.java index cd838305..1542ab0f 100644 --- a/src/main/java/com/skyflow/generated/rest/types/V1VaultSchemaConfig.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/V1VaultSchemaConfig.java @@ -3,15 +3,10 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; + import java.util.HashMap; import java.util.Map; import java.util.Objects; diff --git a/src/main/java/com/skyflow/generated/rest/types/WordCharacterCount.java b/skyvault/src/main/java/com/skyflow/generated/rest/types/WordCharacterCount.java similarity index 90% rename from src/main/java/com/skyflow/generated/rest/types/WordCharacterCount.java rename to skyvault/src/main/java/com/skyflow/generated/rest/types/WordCharacterCount.java index 594b6d11..314b5942 100644 --- a/src/main/java/com/skyflow/generated/rest/types/WordCharacterCount.java +++ b/skyvault/src/main/java/com/skyflow/generated/rest/types/WordCharacterCount.java @@ -3,15 +3,10 @@ */ package com.skyflow.generated.rest.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.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.skyflow.generated.rest.core.ObjectMappers; + import java.util.HashMap; import java.util.Map; import java.util.Objects; diff --git a/skyvault/src/main/java/com/skyflow/utils/Constants.java b/skyvault/src/main/java/com/skyflow/utils/Constants.java new file mode 100644 index 00000000..2ca105d5 --- /dev/null +++ b/skyvault/src/main/java/com/skyflow/utils/Constants.java @@ -0,0 +1,42 @@ +package com.skyflow.utils; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +public final class Constants extends BaseConstants { + public static final String SDK_NAME = "Skyflow Java SDK"; + public static final String DEFAULT_SDK_VERSION = "v2"; + public static final String SDK_VERSION; + public static final String SDK_PREFIX; + public static final String SDK_METRIC_NAME_VERSION_PREFIX = "skyflow-java@"; + public static final String PROCESSED_FILE_NAME_PREFIX = "processed-"; + public static final String DEIDENTIFIED_FILE_PREFIX = "deidentified"; + public static final String HTTPS_PROTOCOL = "https"; + public static final String CURLY_PLACEHOLDER = "{%s}"; + public static final String EMPTY_STRING = ""; + public static final String QUOTE = "\""; + + public static final class HttpUtilityExtra { + public static final String SDK_GENERATED_PREFIX = "SDK-Generated-"; + private HttpUtilityExtra() {} + } + + static { + String sdkVersion; + // Use a static initializer block to read the properties file + Properties properties = new Properties(); + try (InputStream input = Constants.class.getClassLoader().getResourceAsStream("sdk.properties")) { + if (input == null) { + sdkVersion = DEFAULT_SDK_VERSION; + } else { + properties.load(input); + sdkVersion = properties.getProperty("sdk.version", DEFAULT_SDK_VERSION); + } + } catch (IOException ex) { + sdkVersion = DEFAULT_SDK_VERSION; + } + SDK_VERSION = sdkVersion; + SDK_PREFIX = SDK_NAME + " " + SDK_VERSION; + } +} diff --git a/src/main/java/com/skyflow/utils/HttpUtility.java b/skyvault/src/main/java/com/skyflow/utils/HttpUtility.java similarity index 100% rename from src/main/java/com/skyflow/utils/HttpUtility.java rename to skyvault/src/main/java/com/skyflow/utils/HttpUtility.java diff --git a/skyvault/src/main/java/com/skyflow/utils/Utils.java b/skyvault/src/main/java/com/skyflow/utils/Utils.java new file mode 100644 index 00000000..b5f81769 --- /dev/null +++ b/skyvault/src/main/java/com/skyflow/utils/Utils.java @@ -0,0 +1,55 @@ +package com.skyflow.utils; + +import com.google.gson.JsonObject; +import com.skyflow.config.ConnectionConfig; +import com.skyflow.enums.Env; +import com.skyflow.vault.connection.InvokeConnectionRequest; + +import java.util.HashMap; +import java.util.Map; + +public final class Utils extends BaseUtils { + public static String getVaultURL(String clusterId, Env env) { + return getVaultURL(clusterId, env, BaseConstants.V2_VAULT_DOMAIN); + } + + public static String constructConnectionURL(ConnectionConfig config, InvokeConnectionRequest invokeConnectionRequest) { + StringBuilder filledURL = new StringBuilder(config.getConnectionUrl()); + + if (invokeConnectionRequest.getPathParams() != null && !invokeConnectionRequest.getPathParams().isEmpty()) { + for (Map.Entry entry : invokeConnectionRequest.getPathParams().entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + filledURL = new StringBuilder(filledURL.toString().replace(String.format(Constants.CURLY_PLACEHOLDER, key), value)); + } + } + + if (invokeConnectionRequest.getQueryParams() != null && !invokeConnectionRequest.getQueryParams().isEmpty()) { + filledURL.append("?"); + for (Map.Entry entry : invokeConnectionRequest.getQueryParams().entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + filledURL.append(key).append("=").append(value).append("&"); + } + filledURL = new StringBuilder(filledURL.substring(0, filledURL.length() - 1)); + } + + return filledURL.toString(); + } + + public static Map constructConnectionHeadersMap(Map requestHeaders) { + Map headersMap = new HashMap<>(); + for (Map.Entry entry : requestHeaders.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + headersMap.put(key.toLowerCase(), value); + } + return headersMap; + } + + public static JsonObject getMetrics() { + JsonObject details = getCommonMetrics(); + details.addProperty(Constants.SDK_METRIC_NAME_VERSION, Constants.SDK_METRIC_NAME_VERSION_PREFIX + Constants.SDK_VERSION); + return details; + } +} diff --git a/src/main/java/com/skyflow/utils/logger/LogUtil.java b/skyvault/src/main/java/com/skyflow/utils/logger/LogUtil.java similarity index 100% rename from src/main/java/com/skyflow/utils/logger/LogUtil.java rename to skyvault/src/main/java/com/skyflow/utils/logger/LogUtil.java diff --git a/src/main/java/com/skyflow/utils/validations/Validations.java b/skyvault/src/main/java/com/skyflow/utils/validations/Validations.java similarity index 89% rename from src/main/java/com/skyflow/utils/validations/Validations.java rename to skyvault/src/main/java/com/skyflow/utils/validations/Validations.java index 5e528a6a..ff46c409 100644 --- a/src/main/java/com/skyflow/utils/validations/Validations.java +++ b/skyvault/src/main/java/com/skyflow/utils/validations/Validations.java @@ -6,11 +6,10 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import com.google.gson.Gson; import com.google.gson.JsonObject; +import com.skyflow.config.BaseVaultConfig; import com.skyflow.config.ConnectionConfig; import com.skyflow.config.Credentials; import com.skyflow.config.VaultConfig; @@ -41,7 +40,7 @@ import com.skyflow.vault.tokens.DetokenizeRequest; import com.skyflow.vault.tokens.TokenizeRequest; -public class Validations { +public class Validations extends BaseValidations { private Validations() { } @@ -159,97 +158,6 @@ public static void validateInvokeConnectionRequest(InvokeConnectionRequest invok } } - public static void validateCredentials(Credentials credentials) throws SkyflowException { - int nonNullMembers = 0; - String path = credentials.getPath(); - String credentialsString = credentials.getCredentialsString(); - String token = credentials.getToken(); - String apiKey = credentials.getApiKey(); - Object context = credentials.getContext(); - ArrayList roles = credentials.getRoles(); - - if (path != null) nonNullMembers++; - if (credentialsString != null) nonNullMembers++; - if (token != null) nonNullMembers++; - if (apiKey != null) nonNullMembers++; - - if (nonNullMembers > 1) { - LogUtil.printErrorLog(ErrorLogs.MULTIPLE_TOKEN_GENERATION_MEANS_PASSED.getLog()); - throw new SkyflowException( - ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.MultipleTokenGenerationMeansPassed.getMessage() - ); - } else if (nonNullMembers < 1) { - LogUtil.printErrorLog(ErrorLogs.NO_TOKEN_GENERATION_MEANS_PASSED.getLog()); - throw new SkyflowException( - ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.NoTokenGenerationMeansPassed.getMessage() - ); - } else if (path != null && path.trim().isEmpty()) { - LogUtil.printErrorLog(ErrorLogs.EMPTY_CREDENTIALS_PATH.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyCredentialFilePath.getMessage()); - } else if (credentialsString != null && credentialsString.trim().isEmpty()) { - LogUtil.printErrorLog(ErrorLogs.EMPTY_CREDENTIALS_STRING.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyCredentialsString.getMessage()); - } else if (token != null && token.trim().isEmpty()) { - LogUtil.printErrorLog(ErrorLogs.EMPTY_TOKEN_VALUE.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyToken.getMessage()); - } else if (apiKey != null) { - if (apiKey.trim().isEmpty()) { - LogUtil.printErrorLog(ErrorLogs.EMPTY_API_KEY_VALUE.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyApikey.getMessage()); - } else { - Pattern pattern = Pattern.compile(Constants.API_KEY_REGEX); - Matcher matcher = pattern.matcher(apiKey); - if (!matcher.matches()) { - LogUtil.printErrorLog(ErrorLogs.INVALID_API_KEY.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.InvalidApikey.getMessage()); - } - } - } else if (roles != null) { - if (roles.isEmpty()) { - LogUtil.printErrorLog(ErrorLogs.EMPTY_ROLES.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyRoles.getMessage()); - } else { - for (int index = 0; index < roles.size(); index++) { - String role = roles.get(index); - if (role == null || role.trim().isEmpty()) { - LogUtil.printErrorLog(Utils.parameterizedString( - ErrorLogs.EMPTY_OR_NULL_ROLE_IN_ROLES.getLog(), Integer.toString(index) - )); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyRoleInRoles.getMessage()); - } - } - } - } - if (context != null) { - if (context instanceof String) { - String ctxStr = (String) context; - if (ctxStr.trim().isEmpty()) { - LogUtil.printErrorLog(ErrorLogs.EMPTY_OR_NULL_CONTEXT.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyContext.getMessage()); - } - } else if (context instanceof Map) { - Map ctxMap = (Map) context; - if (ctxMap.isEmpty()) { - LogUtil.printErrorLog(ErrorLogs.EMPTY_OR_NULL_CONTEXT.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.EmptyContext.getMessage()); - } - Pattern ctxKeyPattern = Pattern.compile(Constants.CONTEXT_KEY_REGEX); - for (Object key : ctxMap.keySet()) { - if (key == null || !ctxKeyPattern.matcher(key.toString()).matches()) { - String keyStr = key == null ? "null" : key.toString(); - LogUtil.printErrorLog(Utils.parameterizedString( - ErrorLogs.INVALID_CONTEXT_MAP_KEY.getLog(), keyStr)); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), - Utils.parameterizedString(ErrorMessage.InvalidContextMapKey.getMessage(), keyStr)); - } - } - } else { - LogUtil.printErrorLog(ErrorLogs.INVALID_CONTEXT_TYPE.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.InvalidContextType.getMessage()); - } - } - } - public static void validateDetokenizeRequest(DetokenizeRequest detokenizeRequest) throws SkyflowException { ArrayList detokenizeData = detokenizeRequest.getDetokenizeData(); if (detokenizeData == null) { diff --git a/src/main/java/com/skyflow/vault/audit/ListEventRequest.java b/skyvault/src/main/java/com/skyflow/vault/audit/ListEventRequest.java similarity index 100% rename from src/main/java/com/skyflow/vault/audit/ListEventRequest.java rename to skyvault/src/main/java/com/skyflow/vault/audit/ListEventRequest.java diff --git a/src/main/java/com/skyflow/vault/audit/ListEventResponse.java b/skyvault/src/main/java/com/skyflow/vault/audit/ListEventResponse.java similarity index 100% rename from src/main/java/com/skyflow/vault/audit/ListEventResponse.java rename to skyvault/src/main/java/com/skyflow/vault/audit/ListEventResponse.java diff --git a/src/main/java/com/skyflow/vault/bin/GetBinRequest.java b/skyvault/src/main/java/com/skyflow/vault/bin/GetBinRequest.java similarity index 100% rename from src/main/java/com/skyflow/vault/bin/GetBinRequest.java rename to skyvault/src/main/java/com/skyflow/vault/bin/GetBinRequest.java diff --git a/src/main/java/com/skyflow/vault/bin/GetBinResponse.java b/skyvault/src/main/java/com/skyflow/vault/bin/GetBinResponse.java similarity index 100% rename from src/main/java/com/skyflow/vault/bin/GetBinResponse.java rename to skyvault/src/main/java/com/skyflow/vault/bin/GetBinResponse.java diff --git a/src/main/java/com/skyflow/vault/connection/InvokeConnectionRequest.java b/skyvault/src/main/java/com/skyflow/vault/connection/InvokeConnectionRequest.java similarity index 100% rename from src/main/java/com/skyflow/vault/connection/InvokeConnectionRequest.java rename to skyvault/src/main/java/com/skyflow/vault/connection/InvokeConnectionRequest.java diff --git a/src/main/java/com/skyflow/vault/connection/InvokeConnectionResponse.java b/skyvault/src/main/java/com/skyflow/vault/connection/InvokeConnectionResponse.java similarity index 100% rename from src/main/java/com/skyflow/vault/connection/InvokeConnectionResponse.java rename to skyvault/src/main/java/com/skyflow/vault/connection/InvokeConnectionResponse.java diff --git a/src/main/java/com/skyflow/vault/controller/AuditController.java b/skyvault/src/main/java/com/skyflow/vault/controller/AuditController.java similarity index 100% rename from src/main/java/com/skyflow/vault/controller/AuditController.java rename to skyvault/src/main/java/com/skyflow/vault/controller/AuditController.java diff --git a/src/main/java/com/skyflow/vault/controller/BinLookupController.java b/skyvault/src/main/java/com/skyflow/vault/controller/BinLookupController.java similarity index 100% rename from src/main/java/com/skyflow/vault/controller/BinLookupController.java rename to skyvault/src/main/java/com/skyflow/vault/controller/BinLookupController.java diff --git a/src/main/java/com/skyflow/vault/controller/ConnectionController.java b/skyvault/src/main/java/com/skyflow/vault/controller/ConnectionController.java similarity index 100% rename from src/main/java/com/skyflow/vault/controller/ConnectionController.java rename to skyvault/src/main/java/com/skyflow/vault/controller/ConnectionController.java diff --git a/src/main/java/com/skyflow/vault/controller/DetectController.java b/skyvault/src/main/java/com/skyflow/vault/controller/DetectController.java similarity index 99% rename from src/main/java/com/skyflow/vault/controller/DetectController.java rename to skyvault/src/main/java/com/skyflow/vault/controller/DetectController.java index 873c83e1..ad25d7e2 100644 --- a/src/main/java/com/skyflow/vault/controller/DetectController.java +++ b/skyvault/src/main/java/com/skyflow/vault/controller/DetectController.java @@ -8,12 +8,12 @@ import com.skyflow.errors.ErrorCode; import com.skyflow.errors.ErrorMessage; import com.skyflow.errors.SkyflowException; +import com.skyflow.generated.rest.resources.files.requests.*; +import com.skyflow.generated.rest.types.*; import com.skyflow.generated.rest.core.ApiClientApiException; import com.skyflow.generated.rest.core.RequestOptions; -import com.skyflow.generated.rest.resources.files.requests.*; import com.skyflow.generated.rest.resources.strings.requests.DeidentifyStringRequest; import com.skyflow.generated.rest.resources.strings.requests.ReidentifyStringRequest; -import com.skyflow.generated.rest.types.*; import com.skyflow.logs.ErrorLogs; import com.skyflow.logs.InfoLogs; import com.skyflow.utils.Constants; @@ -352,7 +352,7 @@ private String extractBodyAsString(ApiClientApiException e) { private com.skyflow.generated.rest.types.DeidentifyFileResponse processFileByType(String fileExtension, String base64Content, DeidentifyFileRequest request, String vaultId) throws SkyflowException { switch (fileExtension.toLowerCase()) { case "txt": - com.skyflow.generated.rest.resources.files.requests.DeidentifyFileRequestDeidentifyText textFileRequest = + DeidentifyFileRequestDeidentifyText textFileRequest = super.getDeidentifyTextFileRequest(request, vaultId, base64Content); return super.getDetectFileAPi().deidentifyText(textFileRequest); @@ -424,7 +424,7 @@ public DeidentifyFileResponse getDetectRun(GetDetectRunRequest request) throws S .vaultId(vaultId) .build(); - com.skyflow.generated.rest.types.DetectRunsResponse apiResponse = + DetectRunsResponse apiResponse = super.getDetectFileAPi().getRun(runId, getRunRequest); return parseDeidentifyFileResponse(apiResponse, runId, apiResponse.getStatus().toString()); diff --git a/src/main/java/com/skyflow/vault/controller/VaultController.java b/skyvault/src/main/java/com/skyflow/vault/controller/VaultController.java similarity index 88% rename from src/main/java/com/skyflow/vault/controller/VaultController.java rename to skyvault/src/main/java/com/skyflow/vault/controller/VaultController.java index 1812b83b..1d82ac12 100644 --- a/src/main/java/com/skyflow/vault/controller/VaultController.java +++ b/skyvault/src/main/java/com/skyflow/vault/controller/VaultController.java @@ -17,6 +17,7 @@ import com.google.gson.JsonParser; import com.google.gson.JsonPrimitive; import com.skyflow.VaultClient; +import com.skyflow.config.BaseVaultConfig; import com.skyflow.config.Credentials; import com.skyflow.config.VaultConfig; import com.skyflow.enums.RedactionType; @@ -73,10 +74,15 @@ import com.skyflow.vault.tokens.TokenizeRequest; import com.skyflow.vault.tokens.TokenizeResponse; -public final class VaultController extends VaultClient { +public final class VaultController extends VaultClient + implements IVaultController { private static final Gson GSON = new GsonBuilder().serializeNulls().create(); private static final JsonObject SKY_METADATA = Utils.getMetrics(); + private static RequestOptions buildRequestOptions() { + return RequestOptions.builder().addHeader(Constants.SDK_METRICS_HEADER_KEY, SKY_METADATA.toString()).build(); + } + public VaultController(VaultConfig vaultConfig, Credentials credentials) { super(vaultConfig, credentials); } @@ -181,6 +187,7 @@ private static synchronized HashMap getFormattedQueryRecord(V1Fi return queryRecord; } + @Override public InsertResponse insert(InsertRequest insertRequest) throws SkyflowException { LogUtil.printInfoLog(InfoLogs.INSERT_TRIGGERED.getLog()); V1InsertRecordResponse bulkInsertResult = null; @@ -194,7 +201,7 @@ public InsertResponse insert(InsertRequest insertRequest) throws SkyflowExceptio setBearerToken(); if (continueOnError) { RecordServiceBatchOperationBody insertBody = super.getBatchInsertRequestBody(insertRequest); - RequestOptions requestOptions = RequestOptions.builder().addHeader(Constants.SDK_METRICS_HEADER_KEY, SKY_METADATA.toString()).build(); + RequestOptions requestOptions = buildRequestOptions(); batchInsertResult = super.getRecordsApi().withRawResponse().recordServiceBatchOperation(super.getVaultConfig().getVaultId(), insertBody, requestOptions); LogUtil.printInfoLog(InfoLogs.INSERT_REQUEST_RESOLVED.getLog()); Optional>> records = batchInsertResult.body().getResponses(); @@ -229,9 +236,7 @@ public InsertResponse insert(InsertRequest insertRequest) throws SkyflowExceptio } } } catch (ApiClientApiException e) { - String bodyString = GSON.toJson(e.body()); - LogUtil.printErrorLog(ErrorLogs.INSERT_RECORDS_REJECTED.getLog()); - throw new SkyflowException(e.statusCode(), e, e.headers(), bodyString); + throw wrapApiException(e.statusCode(), e, e.headers(), e.body(), ErrorLogs.INSERT_RECORDS_REJECTED); } LogUtil.printInfoLog(InfoLogs.INSERT_SUCCESS.getLog()); if (insertedFields.isEmpty()) { @@ -253,7 +258,7 @@ public DetokenizeResponse detokenize(DetokenizeRequest detokenizeRequest) throws Validations.validateDetokenizeRequest(detokenizeRequest); setBearerToken(); V1DetokenizePayload payload = super.getDetokenizePayload(detokenizeRequest); - RequestOptions requestOptions = RequestOptions.builder().addHeader(Constants.SDK_METRICS_HEADER_KEY, SKY_METADATA.toString()).build(); + RequestOptions requestOptions = buildRequestOptions(); result = super.getTokensApi().withRawResponse().recordServiceDetokenize(super.getVaultConfig().getVaultId(), payload, requestOptions); LogUtil.printInfoLog(InfoLogs.DETOKENIZE_REQUEST_RESOLVED.getLog()); Map> responseHeaders = result.headers(); @@ -274,9 +279,7 @@ public DetokenizeResponse detokenize(DetokenizeRequest detokenizeRequest) throws } } } catch (ApiClientApiException e) { - String bodyString = GSON.toJson(e.body()); - LogUtil.printErrorLog(ErrorLogs.DETOKENIZE_REQUEST_REJECTED.getLog()); - throw new SkyflowException(e.statusCode(), e, e.headers(), bodyString); + throw wrapApiException(e.statusCode(), e, e.headers(), e.body(), ErrorLogs.DETOKENIZE_REQUEST_REJECTED); } if (!errorRecords.isEmpty()) { @@ -316,7 +319,7 @@ public GetResponse get(GetRequest getRequest) throws SkyflowException { .orderBy(RecordServiceBulkGetRecordRequestOrderBy.valueOf(getRequest.getOrderBy())) .build(); - RequestOptions requestOptions = RequestOptions.builder().addHeader(Constants.SDK_METRICS_HEADER_KEY, SKY_METADATA.toString()).build(); + RequestOptions requestOptions = buildRequestOptions(); result = super.getRecordsApi().recordServiceBulkGetRecord( super.getVaultConfig().getVaultId(), getRequest.getTable(), @@ -331,9 +334,7 @@ public GetResponse get(GetRequest getRequest) throws SkyflowException { } } } catch (ApiClientApiException e) { - String bodyString = GSON.toJson(e.body()); - LogUtil.printErrorLog(ErrorLogs.GET_REQUEST_REJECTED.getLog()); - throw new SkyflowException(e.statusCode(), e, e.headers(), bodyString); + throw wrapApiException(e.statusCode(), e, e.headers(), e.body(), ErrorLogs.GET_REQUEST_REJECTED); } LogUtil.printInfoLog(InfoLogs.GET_SUCCESS.getLog()); return new GetResponse(data, null); @@ -349,7 +350,7 @@ public UpdateResponse update(UpdateRequest updateRequest) throws SkyflowExceptio Validations.validateUpdateRequest(updateRequest); setBearerToken(); RecordServiceUpdateRecordBody updateBody = super.getUpdateRequestBody(updateRequest); - RequestOptions requestOptions = RequestOptions.builder().addHeader(Constants.SDK_METRICS_HEADER_KEY, SKY_METADATA.toString()).build(); + RequestOptions requestOptions = buildRequestOptions(); result = super.getRecordsApi().recordServiceUpdateRecord( super.getVaultConfig().getVaultId(), updateRequest.getTable(), @@ -361,9 +362,7 @@ public UpdateResponse update(UpdateRequest updateRequest) throws SkyflowExceptio skyflowId = String.valueOf(result.getSkyflowId()); tokensMap = getFormattedUpdateRecord(result); } catch (ApiClientApiException e) { - String bodyString = GSON.toJson(e.body()); - LogUtil.printErrorLog(ErrorLogs.UPDATE_REQUEST_REJECTED.getLog()); - throw new SkyflowException(e.statusCode(), e, e.headers(), bodyString); + throw wrapApiException(e.statusCode(), e, e.headers(), e.body(), ErrorLogs.UPDATE_REQUEST_REJECTED); } LogUtil.printInfoLog(InfoLogs.UPDATE_SUCCESS.getLog()); return new UpdateResponse(skyflowId, tokensMap); @@ -379,14 +378,12 @@ public DeleteResponse delete(DeleteRequest deleteRequest) throws SkyflowExceptio RecordServiceBulkDeleteRecordBody deleteBody = RecordServiceBulkDeleteRecordBody.builder().skyflowIds(deleteRequest.getIds()) .build(); - RequestOptions requestOptions = RequestOptions.builder().addHeader(Constants.SDK_METRICS_HEADER_KEY, SKY_METADATA.toString()).build(); + RequestOptions requestOptions = buildRequestOptions(); result = super.getRecordsApi().recordServiceBulkDeleteRecord( super.getVaultConfig().getVaultId(), deleteRequest.getTable(), deleteBody, requestOptions); LogUtil.printInfoLog(InfoLogs.DELETE_REQUEST_RESOLVED.getLog()); } catch (ApiClientApiException e) { - String bodyString = GSON.toJson(e.body()); - LogUtil.printErrorLog(ErrorLogs.DELETE_REQUEST_REJECTED.getLog()); - throw new SkyflowException(e.statusCode(), e, e.headers(), bodyString); + throw wrapApiException(e.statusCode(), e, e.headers(), e.body(), ErrorLogs.DELETE_REQUEST_REJECTED); } LogUtil.printInfoLog(InfoLogs.DELETE_SUCCESS.getLog()); return new DeleteResponse(result.getRecordIdResponse().orElse(Collections.emptyList())); @@ -400,7 +397,7 @@ public QueryResponse query(QueryRequest queryRequest) throws SkyflowException { LogUtil.printInfoLog(InfoLogs.VALIDATING_QUERY_REQUEST.getLog()); Validations.validateQueryRequest(queryRequest); setBearerToken(); - RequestOptions requestOptions = RequestOptions.builder().addHeader(Constants.SDK_METRICS_HEADER_KEY, SKY_METADATA.toString()).build(); + RequestOptions requestOptions = buildRequestOptions(); result = super.getQueryApi().queryServiceExecuteQuery( super.getVaultConfig().getVaultId(), QueryServiceExecuteQueryBody.builder().query(queryRequest.getQuery()).build(), @@ -414,9 +411,7 @@ public QueryResponse query(QueryRequest queryRequest) throws SkyflowException { } } } catch (ApiClientApiException e) { - String bodyString = GSON.toJson(e.body()); - LogUtil.printErrorLog(ErrorLogs.QUERY_REQUEST_REJECTED.getLog()); - throw new SkyflowException(e.statusCode(), e, e.headers(), bodyString); + throw wrapApiException(e.statusCode(), e, e.headers(), e.body(), ErrorLogs.QUERY_REQUEST_REJECTED); } LogUtil.printInfoLog(InfoLogs.QUERY_SUCCESS.getLog()); return new QueryResponse(fields); @@ -431,7 +426,7 @@ public TokenizeResponse tokenize(TokenizeRequest tokenizeRequest) throws Skyflow Validations.validateTokenizeRequest(tokenizeRequest); setBearerToken(); V1TokenizePayload payload = super.getTokenizePayload(tokenizeRequest); - RequestOptions requestOptions = RequestOptions.builder().addHeader(Constants.SDK_METRICS_HEADER_KEY, SKY_METADATA.toString()).build(); + RequestOptions requestOptions = buildRequestOptions(); result = super.getTokensApi().recordServiceTokenize(super.getVaultConfig().getVaultId(), payload, requestOptions); LogUtil.printInfoLog(InfoLogs.TOKENIZE_REQUEST_RESOLVED.getLog()); if (result != null && result.getRecords().isPresent() && !result.getRecords().get().isEmpty()) { @@ -442,9 +437,7 @@ public TokenizeResponse tokenize(TokenizeRequest tokenizeRequest) throws Skyflow } } } catch (ApiClientApiException e) { - String bodyString = GSON.toJson(e.body()); - LogUtil.printErrorLog(ErrorLogs.TOKENIZE_REQUEST_REJECTED.getLog()); - throw new SkyflowException(e.statusCode(), e, e.headers(), bodyString); + throw wrapApiException(e.statusCode(), e, e.headers(), e.body(), ErrorLogs.TOKENIZE_REQUEST_REJECTED); } LogUtil.printInfoLog(InfoLogs.TOKENIZE_SUCCESS.getLog()); return new TokenizeResponse(list); @@ -467,7 +460,7 @@ public FileUploadResponse uploadFile(FileUploadRequest fileUploadRequest) throws .returnFileMetadata(false) .build(); - RequestOptions requestOptions = RequestOptions.builder().addHeader(Constants.SDK_METRICS_HEADER_KEY, SKY_METADATA.toString()).build(); + RequestOptions requestOptions = buildRequestOptions(); UploadFileV2Response uploadFileV2Response = super.getRecordsApi().uploadFileV2( super.getVaultConfig().getVaultId(), file, @@ -481,9 +474,7 @@ public FileUploadResponse uploadFile(FileUploadRequest fileUploadRequest) throws ); } catch (ApiClientApiException e) { - String bodyString = GSON.toJson(e.body()); - LogUtil.printErrorLog(ErrorLogs.UPLOAD_FILE_REQUEST_REJECTED.getLog()); - throw new SkyflowException(e.statusCode(), e, e.headers(), bodyString); + throw wrapApiException(e.statusCode(), e, e.headers(), e.body(), ErrorLogs.UPLOAD_FILE_REQUEST_REJECTED); } catch (IOException e) { LogUtil.printErrorLog(ErrorLogs.UPLOAD_FILE_REQUEST_REJECTED.getLog()); throw new SkyflowException(e.getMessage(), e); diff --git a/src/main/java/com/skyflow/vault/data/DeleteRequest.java b/skyvault/src/main/java/com/skyflow/vault/data/DeleteRequest.java similarity index 100% rename from src/main/java/com/skyflow/vault/data/DeleteRequest.java rename to skyvault/src/main/java/com/skyflow/vault/data/DeleteRequest.java diff --git a/src/main/java/com/skyflow/vault/data/DeleteResponse.java b/skyvault/src/main/java/com/skyflow/vault/data/DeleteResponse.java similarity index 100% rename from src/main/java/com/skyflow/vault/data/DeleteResponse.java rename to skyvault/src/main/java/com/skyflow/vault/data/DeleteResponse.java diff --git a/src/main/java/com/skyflow/vault/data/FileUploadRequest.java b/skyvault/src/main/java/com/skyflow/vault/data/FileUploadRequest.java similarity index 100% rename from src/main/java/com/skyflow/vault/data/FileUploadRequest.java rename to skyvault/src/main/java/com/skyflow/vault/data/FileUploadRequest.java diff --git a/src/main/java/com/skyflow/vault/data/FileUploadResponse.java b/skyvault/src/main/java/com/skyflow/vault/data/FileUploadResponse.java similarity index 100% rename from src/main/java/com/skyflow/vault/data/FileUploadResponse.java rename to skyvault/src/main/java/com/skyflow/vault/data/FileUploadResponse.java diff --git a/src/main/java/com/skyflow/vault/data/GetRequest.java b/skyvault/src/main/java/com/skyflow/vault/data/GetRequest.java similarity index 87% rename from src/main/java/com/skyflow/vault/data/GetRequest.java rename to skyvault/src/main/java/com/skyflow/vault/data/GetRequest.java index 0fccf7b8..edd9872e 100644 --- a/src/main/java/com/skyflow/vault/data/GetRequest.java +++ b/skyvault/src/main/java/com/skyflow/vault/data/GetRequest.java @@ -7,10 +7,11 @@ import java.util.ArrayList; -public class GetRequest { +public class GetRequest extends BaseGetRequest { private final GetRequestBuilder builder; private GetRequest(GetRequestBuilder builder) { + super(builder); this.builder = builder; } @@ -18,14 +19,6 @@ public static GetRequestBuilder builder() { return new GetRequestBuilder(); } - public String getTable() { - return this.builder.table; - } - - public ArrayList getIds() { - return this.builder.ids; - } - public RedactionType getRedactionType() { return this.builder.redactionType; } @@ -34,10 +27,6 @@ public Boolean getReturnTokens() { return this.builder.returnTokens; } - public ArrayList getFields() { - return this.builder.fields; - } - public String getOffset() { return this.builder.offset; } @@ -71,12 +60,9 @@ public String getOrderBy() { return this.builder.orderBy; } - public static final class GetRequestBuilder { - private String table; - private ArrayList ids; + public static final class GetRequestBuilder extends BaseGetRequestBuilder { private RedactionType redactionType; private Boolean returnTokens; - private ArrayList fields; private String offset; private String limit; private Boolean downloadUrl; @@ -89,13 +75,21 @@ private GetRequestBuilder() { this.downloadUrl = true; } + @Override public GetRequestBuilder table(String table) { - this.table = table; + super.table(table); return this; } + @Override public GetRequestBuilder ids(ArrayList ids) { - this.ids = ids; + super.ids(ids); + return this; + } + + @Override + public GetRequestBuilder fields(ArrayList fields) { + super.fields(fields); return this; } @@ -109,11 +103,6 @@ public GetRequestBuilder returnTokens(Boolean returnTokens) { return this; } - public GetRequestBuilder fields(ArrayList fields) { - this.fields = fields; - return this; - } - public GetRequestBuilder offset(String offset) { this.offset = offset; return this; diff --git a/skyvault/src/main/java/com/skyflow/vault/data/GetResponse.java b/skyvault/src/main/java/com/skyflow/vault/data/GetResponse.java new file mode 100644 index 00000000..f9f082cd --- /dev/null +++ b/skyvault/src/main/java/com/skyflow/vault/data/GetResponse.java @@ -0,0 +1,15 @@ +package com.skyflow.vault.data; + +import java.util.ArrayList; +import java.util.HashMap; + +/** + * Deprecation notice: the {@code skyflow_id} key in each {@link #getData()} record map is + * deprecated and will be removed in an upcoming release. Use {@code skyflowId} instead. + * Both keys are present simultaneously in v2 for backward compatibility. + */ +public class GetResponse extends BaseGetResponse { + public GetResponse(ArrayList> data, ArrayList> errors) { + super(data, errors); + } +} diff --git a/src/main/java/com/skyflow/vault/data/InsertRequest.java b/skyvault/src/main/java/com/skyflow/vault/data/InsertRequest.java similarity index 98% rename from src/main/java/com/skyflow/vault/data/InsertRequest.java rename to skyvault/src/main/java/com/skyflow/vault/data/InsertRequest.java index 54093467..a7066236 100644 --- a/src/main/java/com/skyflow/vault/data/InsertRequest.java +++ b/skyvault/src/main/java/com/skyflow/vault/data/InsertRequest.java @@ -5,13 +5,14 @@ import java.util.ArrayList; import java.util.HashMap; -public class InsertRequest { +public class InsertRequest extends BaseInsertRequest{ private final InsertRequestBuilder builder; private InsertRequest(InsertRequestBuilder builder) { this.builder = builder; } + public static InsertRequestBuilder builder() { return new InsertRequestBuilder(); } diff --git a/src/main/java/com/skyflow/vault/data/InsertResponse.java b/skyvault/src/main/java/com/skyflow/vault/data/InsertResponse.java similarity index 93% rename from src/main/java/com/skyflow/vault/data/InsertResponse.java rename to skyvault/src/main/java/com/skyflow/vault/data/InsertResponse.java index 3d311525..29954b82 100644 --- a/src/main/java/com/skyflow/vault/data/InsertResponse.java +++ b/skyvault/src/main/java/com/skyflow/vault/data/InsertResponse.java @@ -5,7 +5,7 @@ import java.util.ArrayList; import java.util.HashMap; -public class InsertResponse { +public class InsertResponse extends BaseInsertResponse { private final ArrayList> insertedFields; private final ArrayList> errors; diff --git a/src/main/java/com/skyflow/vault/data/QueryRequest.java b/skyvault/src/main/java/com/skyflow/vault/data/QueryRequest.java similarity index 59% rename from src/main/java/com/skyflow/vault/data/QueryRequest.java rename to skyvault/src/main/java/com/skyflow/vault/data/QueryRequest.java index 2aaa200d..ea27f5d1 100644 --- a/src/main/java/com/skyflow/vault/data/QueryRequest.java +++ b/skyvault/src/main/java/com/skyflow/vault/data/QueryRequest.java @@ -1,28 +1,21 @@ package com.skyflow.vault.data; -public class QueryRequest { - private final QueryRequestBuilder builder; - +public class QueryRequest extends BaseQueryRequest { private QueryRequest(QueryRequestBuilder builder) { - this.builder = builder; + super(builder); } public static QueryRequestBuilder builder() { return new QueryRequestBuilder(); } - public String getQuery() { - return this.builder.query; - } - - public static final class QueryRequestBuilder { - private String query; - + public static final class QueryRequestBuilder extends BaseQueryRequestBuilder { private QueryRequestBuilder() { } + @Override public QueryRequestBuilder query(String query) { - this.query = query; + super.query(query); return this; } diff --git a/src/main/java/com/skyflow/vault/data/QueryResponse.java b/skyvault/src/main/java/com/skyflow/vault/data/QueryResponse.java similarity index 52% rename from src/main/java/com/skyflow/vault/data/QueryResponse.java rename to skyvault/src/main/java/com/skyflow/vault/data/QueryResponse.java index afb32c60..4b51fc44 100644 --- a/src/main/java/com/skyflow/vault/data/QueryResponse.java +++ b/skyvault/src/main/java/com/skyflow/vault/data/QueryResponse.java @@ -9,32 +9,14 @@ import java.util.ArrayList; import java.util.HashMap; -public class QueryResponse { - private final ArrayList> fields; - private final ArrayList> errors; - +/** + * Deprecation notice: the {@code skyflow_id} key in each {@link #getFields()} record map is + * deprecated and will be removed in an upcoming release. Use {@code skyflowId} instead. + * Both keys are present simultaneously in v2 for backward compatibility. + */ +public class QueryResponse extends BaseQueryResponse { public QueryResponse(ArrayList> fields) { - this.fields = fields; - this.errors = null; - } - - /** - * Returns the list of record maps from the Query response. Each map contains all - * field name/value pairs for the record. - * - *

Deprecation notice: The {@code skyflow_id} key in each record map is - * deprecated and will be removed in an upcoming release. Use {@code skyflowId} instead. - * Both keys are present simultaneously in v2 for backward compatibility.

- */ - public ArrayList> getFields() { - return fields; - } - - /** - * Always returns null. The Query API does not support partial-error responses. - */ - public ArrayList> getErrors() { - return errors; + super(fields); } @Override diff --git a/src/main/java/com/skyflow/vault/data/UpdateRequest.java b/skyvault/src/main/java/com/skyflow/vault/data/UpdateRequest.java similarity index 100% rename from src/main/java/com/skyflow/vault/data/UpdateRequest.java rename to skyvault/src/main/java/com/skyflow/vault/data/UpdateRequest.java diff --git a/src/main/java/com/skyflow/vault/data/UpdateResponse.java b/skyvault/src/main/java/com/skyflow/vault/data/UpdateResponse.java similarity index 100% rename from src/main/java/com/skyflow/vault/data/UpdateResponse.java rename to skyvault/src/main/java/com/skyflow/vault/data/UpdateResponse.java diff --git a/src/main/java/com/skyflow/vault/detect/AudioBleep.java b/skyvault/src/main/java/com/skyflow/vault/detect/AudioBleep.java similarity index 100% rename from src/main/java/com/skyflow/vault/detect/AudioBleep.java rename to skyvault/src/main/java/com/skyflow/vault/detect/AudioBleep.java diff --git a/src/main/java/com/skyflow/vault/detect/DateTransformation.java b/skyvault/src/main/java/com/skyflow/vault/detect/DateTransformation.java similarity index 100% rename from src/main/java/com/skyflow/vault/detect/DateTransformation.java rename to skyvault/src/main/java/com/skyflow/vault/detect/DateTransformation.java diff --git a/src/main/java/com/skyflow/vault/detect/DeidentifyFileRequest.java b/skyvault/src/main/java/com/skyflow/vault/detect/DeidentifyFileRequest.java similarity index 100% rename from src/main/java/com/skyflow/vault/detect/DeidentifyFileRequest.java rename to skyvault/src/main/java/com/skyflow/vault/detect/DeidentifyFileRequest.java diff --git a/src/main/java/com/skyflow/vault/detect/DeidentifyFileResponse.java b/skyvault/src/main/java/com/skyflow/vault/detect/DeidentifyFileResponse.java similarity index 100% rename from src/main/java/com/skyflow/vault/detect/DeidentifyFileResponse.java rename to skyvault/src/main/java/com/skyflow/vault/detect/DeidentifyFileResponse.java diff --git a/src/main/java/com/skyflow/vault/detect/DeidentifyTextRequest.java b/skyvault/src/main/java/com/skyflow/vault/detect/DeidentifyTextRequest.java similarity index 100% rename from src/main/java/com/skyflow/vault/detect/DeidentifyTextRequest.java rename to skyvault/src/main/java/com/skyflow/vault/detect/DeidentifyTextRequest.java diff --git a/src/main/java/com/skyflow/vault/detect/DeidentifyTextResponse.java b/skyvault/src/main/java/com/skyflow/vault/detect/DeidentifyTextResponse.java similarity index 100% rename from src/main/java/com/skyflow/vault/detect/DeidentifyTextResponse.java rename to skyvault/src/main/java/com/skyflow/vault/detect/DeidentifyTextResponse.java diff --git a/src/main/java/com/skyflow/vault/detect/EntityInfo.java b/skyvault/src/main/java/com/skyflow/vault/detect/EntityInfo.java similarity index 100% rename from src/main/java/com/skyflow/vault/detect/EntityInfo.java rename to skyvault/src/main/java/com/skyflow/vault/detect/EntityInfo.java diff --git a/src/main/java/com/skyflow/vault/detect/FileEntityInfo.java b/skyvault/src/main/java/com/skyflow/vault/detect/FileEntityInfo.java similarity index 100% rename from src/main/java/com/skyflow/vault/detect/FileEntityInfo.java rename to skyvault/src/main/java/com/skyflow/vault/detect/FileEntityInfo.java diff --git a/src/main/java/com/skyflow/vault/detect/FileInfo.java b/skyvault/src/main/java/com/skyflow/vault/detect/FileInfo.java similarity index 100% rename from src/main/java/com/skyflow/vault/detect/FileInfo.java rename to skyvault/src/main/java/com/skyflow/vault/detect/FileInfo.java diff --git a/src/main/java/com/skyflow/vault/detect/FileInput.java b/skyvault/src/main/java/com/skyflow/vault/detect/FileInput.java similarity index 100% rename from src/main/java/com/skyflow/vault/detect/FileInput.java rename to skyvault/src/main/java/com/skyflow/vault/detect/FileInput.java diff --git a/src/main/java/com/skyflow/vault/detect/GetDetectRunRequest.java b/skyvault/src/main/java/com/skyflow/vault/detect/GetDetectRunRequest.java similarity index 100% rename from src/main/java/com/skyflow/vault/detect/GetDetectRunRequest.java rename to skyvault/src/main/java/com/skyflow/vault/detect/GetDetectRunRequest.java diff --git a/src/main/java/com/skyflow/vault/detect/ReidentifyTextRequest.java b/skyvault/src/main/java/com/skyflow/vault/detect/ReidentifyTextRequest.java similarity index 100% rename from src/main/java/com/skyflow/vault/detect/ReidentifyTextRequest.java rename to skyvault/src/main/java/com/skyflow/vault/detect/ReidentifyTextRequest.java diff --git a/src/main/java/com/skyflow/vault/detect/ReidentifyTextResponse.java b/skyvault/src/main/java/com/skyflow/vault/detect/ReidentifyTextResponse.java similarity index 100% rename from src/main/java/com/skyflow/vault/detect/ReidentifyTextResponse.java rename to skyvault/src/main/java/com/skyflow/vault/detect/ReidentifyTextResponse.java diff --git a/src/main/java/com/skyflow/vault/detect/TextIndex.java b/skyvault/src/main/java/com/skyflow/vault/detect/TextIndex.java similarity index 100% rename from src/main/java/com/skyflow/vault/detect/TextIndex.java rename to skyvault/src/main/java/com/skyflow/vault/detect/TextIndex.java diff --git a/src/main/java/com/skyflow/vault/detect/TokenFormat.java b/skyvault/src/main/java/com/skyflow/vault/detect/TokenFormat.java similarity index 100% rename from src/main/java/com/skyflow/vault/detect/TokenFormat.java rename to skyvault/src/main/java/com/skyflow/vault/detect/TokenFormat.java diff --git a/src/main/java/com/skyflow/vault/detect/Transformations.java b/skyvault/src/main/java/com/skyflow/vault/detect/Transformations.java similarity index 100% rename from src/main/java/com/skyflow/vault/detect/Transformations.java rename to skyvault/src/main/java/com/skyflow/vault/detect/Transformations.java diff --git a/src/main/java/com/skyflow/vault/tokens/ColumnValue.java b/skyvault/src/main/java/com/skyflow/vault/tokens/ColumnValue.java similarity index 100% rename from src/main/java/com/skyflow/vault/tokens/ColumnValue.java rename to skyvault/src/main/java/com/skyflow/vault/tokens/ColumnValue.java diff --git a/src/main/java/com/skyflow/vault/tokens/DetokenizeData.java b/skyvault/src/main/java/com/skyflow/vault/tokens/DetokenizeData.java similarity index 73% rename from src/main/java/com/skyflow/vault/tokens/DetokenizeData.java rename to skyvault/src/main/java/com/skyflow/vault/tokens/DetokenizeData.java index 0e1b6b63..0a8d7ed5 100644 --- a/src/main/java/com/skyflow/vault/tokens/DetokenizeData.java +++ b/skyvault/src/main/java/com/skyflow/vault/tokens/DetokenizeData.java @@ -1,8 +1,9 @@ package com.skyflow.vault.tokens; import com.skyflow.enums.RedactionType; +import com.skyflow.vault.data.BaseDetokenizeData; -public class DetokenizeData { +public class DetokenizeData extends BaseDetokenizeData { private final String token; private final RedactionType redactionType; @@ -13,7 +14,7 @@ public DetokenizeData(String token) { public DetokenizeData(String token, RedactionType redactionType) { this.token = token; - this.redactionType = redactionType == null ? RedactionType.DEFAULT : redactionType; + this.redactionType = redactionType == null ? RedactionType.DEFAULT : redactionType; } public String getToken() { diff --git a/src/main/java/com/skyflow/vault/tokens/DetokenizeRecordResponse.java b/skyvault/src/main/java/com/skyflow/vault/tokens/DetokenizeRecordResponse.java similarity index 73% rename from src/main/java/com/skyflow/vault/tokens/DetokenizeRecordResponse.java rename to skyvault/src/main/java/com/skyflow/vault/tokens/DetokenizeRecordResponse.java index 7d2ae73c..a16b17dd 100644 --- a/src/main/java/com/skyflow/vault/tokens/DetokenizeRecordResponse.java +++ b/skyvault/src/main/java/com/skyflow/vault/tokens/DetokenizeRecordResponse.java @@ -2,44 +2,29 @@ import com.skyflow.generated.rest.types.V1DetokenizeRecordResponse; +import com.skyflow.vault.data.BaseDetokenizeRecordResponse; -public class DetokenizeRecordResponse { - private final String token; - private final String value; +public class DetokenizeRecordResponse extends BaseDetokenizeRecordResponse { private final String type; - private final String error; private final String requestId; + private final String value; public DetokenizeRecordResponse(V1DetokenizeRecordResponse record) { this(record, null); } public DetokenizeRecordResponse(V1DetokenizeRecordResponse record, String requestId) { - this.token = record.getToken().orElse(null); - + super(record.getToken().orElse(null), record.getError().orElse(null)); this.value = record.getValue() .filter(val -> val != null && !val.toString().isEmpty()) .orElse(null); - this.type = record.getValueType() .map(Enum::toString) .filter(val -> !"NONE".equals(val)) .orElse(null); - this.error = record.getError().orElse(null); - this.requestId = requestId; } - - - public String getError() { - return error; - } - - public String getToken() { - return token; - } - public String getValue() { return value; } diff --git a/src/main/java/com/skyflow/vault/tokens/DetokenizeRequest.java b/skyvault/src/main/java/com/skyflow/vault/tokens/DetokenizeRequest.java similarity index 95% rename from src/main/java/com/skyflow/vault/tokens/DetokenizeRequest.java rename to skyvault/src/main/java/com/skyflow/vault/tokens/DetokenizeRequest.java index 481c0c16..83c7e017 100644 --- a/src/main/java/com/skyflow/vault/tokens/DetokenizeRequest.java +++ b/skyvault/src/main/java/com/skyflow/vault/tokens/DetokenizeRequest.java @@ -1,11 +1,12 @@ package com.skyflow.vault.tokens; import com.skyflow.logs.InfoLogs; +import com.skyflow.vault.data.BaseDetokenizeRequest; import com.skyflow.utils.logger.LogUtil; import java.util.ArrayList; -public class DetokenizeRequest { +public class DetokenizeRequest extends BaseDetokenizeRequest { private final DetokenizeRequestBuilder builder; private DetokenizeRequest(DetokenizeRequestBuilder builder) { diff --git a/src/main/java/com/skyflow/vault/tokens/DetokenizeResponse.java b/skyvault/src/main/java/com/skyflow/vault/tokens/DetokenizeResponse.java similarity index 94% rename from src/main/java/com/skyflow/vault/tokens/DetokenizeResponse.java rename to skyvault/src/main/java/com/skyflow/vault/tokens/DetokenizeResponse.java index bd1d7cde..e7f69c34 100644 --- a/src/main/java/com/skyflow/vault/tokens/DetokenizeResponse.java +++ b/skyvault/src/main/java/com/skyflow/vault/tokens/DetokenizeResponse.java @@ -4,10 +4,11 @@ import com.google.gson.FieldAttributes; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import com.skyflow.vault.data.BaseDetokenizeResponse; import java.util.ArrayList; -public class DetokenizeResponse { +public class DetokenizeResponse extends BaseDetokenizeResponse { private final ArrayList detokenizedFields; private final ArrayList errors; diff --git a/src/main/java/com/skyflow/vault/tokens/TokenizeRequest.java b/skyvault/src/main/java/com/skyflow/vault/tokens/TokenizeRequest.java similarity index 100% rename from src/main/java/com/skyflow/vault/tokens/TokenizeRequest.java rename to skyvault/src/main/java/com/skyflow/vault/tokens/TokenizeRequest.java diff --git a/src/main/java/com/skyflow/vault/tokens/TokenizeResponse.java b/skyvault/src/main/java/com/skyflow/vault/tokens/TokenizeResponse.java similarity index 85% rename from src/main/java/com/skyflow/vault/tokens/TokenizeResponse.java rename to skyvault/src/main/java/com/skyflow/vault/tokens/TokenizeResponse.java index d8d8072b..c77787cf 100644 --- a/src/main/java/com/skyflow/vault/tokens/TokenizeResponse.java +++ b/skyvault/src/main/java/com/skyflow/vault/tokens/TokenizeResponse.java @@ -1,9 +1,13 @@ package com.skyflow.vault.tokens; -import com.google.gson.*; - import java.util.List; +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + public class TokenizeResponse { private final List tokens; diff --git a/src/main/resources/sdk.properties b/skyvault/src/main/resources/sdk.properties similarity index 100% rename from src/main/resources/sdk.properties rename to skyvault/src/main/resources/sdk.properties diff --git a/src/test/java/com/skyflow/ConnectionClientDotenvTests.java b/skyvault/src/test/java/com/skyflow/ConnectionClientDotenvTests.java similarity index 100% rename from src/test/java/com/skyflow/ConnectionClientDotenvTests.java rename to skyvault/src/test/java/com/skyflow/ConnectionClientDotenvTests.java diff --git a/src/test/java/com/skyflow/ConnectionClientTests.java b/skyvault/src/test/java/com/skyflow/ConnectionClientTests.java similarity index 100% rename from src/test/java/com/skyflow/ConnectionClientTests.java rename to skyvault/src/test/java/com/skyflow/ConnectionClientTests.java diff --git a/skyvault/src/test/java/com/skyflow/RequestFidelityTests.java b/skyvault/src/test/java/com/skyflow/RequestFidelityTests.java new file mode 100644 index 00000000..0a63d89e --- /dev/null +++ b/skyvault/src/test/java/com/skyflow/RequestFidelityTests.java @@ -0,0 +1,1406 @@ +package com.skyflow; + +import com.google.gson.JsonArray; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.skyflow.config.ConnectionConfig; +import com.skyflow.config.Credentials; +import com.skyflow.config.VaultConfig; +import com.skyflow.enums.Env; +import com.skyflow.enums.RedactionType; +import com.skyflow.enums.RequestMethod; +import com.skyflow.enums.TokenMode; +import com.skyflow.errors.SkyflowException; +import com.skyflow.generated.rest.ApiClient; +import com.skyflow.generated.rest.resources.query.QueryClient; +import com.skyflow.generated.rest.resources.query.requests.QueryServiceExecuteQueryBody; +import com.skyflow.generated.rest.resources.records.RawRecordsClient; +import com.skyflow.generated.rest.resources.records.RecordsClient; +import com.skyflow.generated.rest.resources.records.requests.RecordServiceBatchOperationBody; +import com.skyflow.generated.rest.resources.records.requests.RecordServiceBulkDeleteRecordBody; +import com.skyflow.generated.rest.resources.records.requests.RecordServiceBulkGetRecordRequest; +import com.skyflow.generated.rest.resources.records.requests.RecordServiceInsertRecordBody; +import com.skyflow.generated.rest.resources.records.requests.RecordServiceUpdateRecordBody; +import com.skyflow.generated.rest.resources.records.requests.UploadFileV2Request; +import com.skyflow.generated.rest.resources.records.types.RecordServiceBulkGetRecordRequestOrderBy; +import com.skyflow.generated.rest.resources.records.types.RecordServiceBulkGetRecordRequestRedaction; +import com.skyflow.generated.rest.resources.tokens.requests.V1DetokenizePayload; +import com.skyflow.generated.rest.resources.tokens.requests.V1TokenizePayload; +import com.skyflow.generated.rest.types.BatchRecordMethod; +import com.skyflow.generated.rest.types.RedactionEnumRedaction; +import com.skyflow.generated.rest.types.UploadFileV2Response; +import com.skyflow.generated.rest.types.V1BatchOperationResponse; +import com.skyflow.generated.rest.types.V1BatchRecord; +import com.skyflow.generated.rest.types.V1BulkDeleteRecordResponse; +import com.skyflow.generated.rest.types.V1BulkGetRecordResponse; +import com.skyflow.generated.rest.types.V1Byot; +import com.skyflow.generated.rest.types.V1DetokenizeRecordRequest; +import com.skyflow.generated.rest.types.V1FieldRecords; +import com.skyflow.generated.rest.types.V1GetQueryResponse; +import com.skyflow.generated.rest.types.V1InsertRecordResponse; +import com.skyflow.generated.rest.types.V1TokenizeRecordRequest; +import com.skyflow.generated.rest.types.V1TokenizeResponse; +import com.skyflow.generated.rest.types.V1UpdateRecordResponse; +import com.skyflow.utils.HttpUtility; +import com.skyflow.utils.Utils; +import com.skyflow.vault.connection.InvokeConnectionRequest; +import com.skyflow.vault.controller.ConnectionController; +import com.skyflow.vault.controller.VaultController; +import com.skyflow.vault.data.DeleteRequest; +import com.skyflow.vault.data.FileUploadRequest; +import com.skyflow.vault.data.GetRequest; +import com.skyflow.vault.data.InsertRequest; +import com.skyflow.vault.data.QueryRequest; +import com.skyflow.vault.data.UpdateRequest; +import com.skyflow.vault.tokens.ColumnValue; +import com.skyflow.vault.tokens.DetokenizeData; +import com.skyflow.vault.tokens.DetokenizeRequest; +import com.skyflow.vault.tokens.TokenizeRequest; +import okhttp3.Protocol; +import okhttp3.Request; +import okhttp3.Response; +import org.junit.Assert; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; + +import java.io.File; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.ProtocolException; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +/** + * Request-fidelity tests for the v2 SDK. + * + *

Every test here builds a request the way an SDK user would, runs it through the real + * production mapping code ({@link VaultClient}'s request-body builders, {@link VaultController}'s + * inline builders, {@link Utils} / {@link HttpUtility} for connections) and asserts the exact value + * that lands on the outgoing generated REST request object. + * + *

Tests suffixed {@code _knownGap} pin behaviour that is a confirmed defect. They assert the + * CURRENT behaviour on purpose so that any future change to production code is caught here. + */ +public class RequestFidelityTests { + + private static final String VAULT_ID = "vault123"; + private static final String CLUSTER_ID = "cluster123"; + private static final String API_KEY = "sky-ab123-abcd1234cdef1234abcd4321cdef4321"; // gitleaks:allow + private static final String CONNECTION_URL = "https://conn.example.com/api/{resource}/details"; + + // Non-trivial values reused across tests. + private static final String NON_ASCII_NAME = "日本語 テスト"; + private static final String NON_ASCII_CITY = "北京市 朝阳区"; + private static final String MULTILINE_NOTE = "line one\nline two with spaces"; + + // ------------------------------------------------------------------ + // harness + // ------------------------------------------------------------------ + + private static VaultConfig testVaultConfig() { + VaultConfig config = new VaultConfig(); + config.setVaultId(VAULT_ID); + config.setClusterId(CLUSTER_ID); + config.setEnv(Env.DEV); + return config; + } + + private static Credentials apiKeyCredentials() { + Credentials credentials = new Credentials(); + credentials.setApiKey(API_KEY); + return credentials; + } + + /** + * VaultClient's request-body builders are {@code protected} and this test lives in the same + * {@code com.skyflow} package, so they can be exercised directly with no mocking at all. + */ + private static VaultClient newVaultClient() { + return new VaultClient(testVaultConfig(), apiKeyCredentials()); + } + + private static VaultController newControllerWithMockApi(ApiClient mockApiClient) throws Exception { + VaultController controller = new VaultController(testVaultConfig(), apiKeyCredentials()); + Field field = VaultClient.class.getDeclaredField("apiClient"); + field.setAccessible(true); + field.set(controller, mockApiClient); + return controller; + } + + private static Response buildOkHttpResponse() { + return new Response.Builder() + .request(new Request.Builder().url("https://dummy.example.com").build()) + .protocol(Protocol.HTTP_1_1) + .code(200) + .message("OK") + .header(com.skyflow.utils.Constants.REQUEST_ID_HEADER_KEY, "req-fidelity-1") + .build(); + } + + private static HashMap richRow() { + HashMap nested = new HashMap<>(); + nested.put("city", NON_ASCII_CITY); + nested.put("zip", "100000"); + + HashMap row = new HashMap<>(); + row.put("name", NON_ASCII_NAME); + row.put("notes", MULTILINE_NOTE); + row.put("age", 42); + row.put("balance", 3.14); + row.put("active", true); + row.put("address", nested); + return row; + } + + private static ArrayList> rows(HashMap... maps) { + return new ArrayList<>(Arrays.asList(maps)); + } + + private static ArrayList list(String... items) { + return new ArrayList<>(Arrays.asList(items)); + } + + // ================================================================== + // insert — bulk branch (continueOnError == false) + // ================================================================== + + @Test + public void testInsertBulk_everyBuilderValueReachesGeneratedBody() { + VaultClient client = newVaultClient(); + + HashMap row1 = richRow(); + HashMap row2 = new HashMap<>(); + row2.put("name", "second record"); + + InsertRequest request = InsertRequest.builder() + .table("cards") + .values(rows(row1, row2)) + .returnTokens(true) + .upsert("email") + .tokenMode(TokenMode.ENABLE_STRICT) + .continueOnError(false) + .build(); + + RecordServiceInsertRecordBody body = client.getBulkInsertRequestBody(request); + + Assert.assertTrue("tokenization must carry returnTokens", body.getTokenization().isPresent()); + Assert.assertTrue(body.getTokenization().get()); + Assert.assertEquals("email", body.getUpsert().get()); + Assert.assertEquals(V1Byot.ENABLE_STRICT, body.getByot().get()); + + List records = body.getRecords().get(); + Assert.assertEquals(2, records.size()); + + Map fields0 = records.get(0).getFields().get(); + Assert.assertEquals(NON_ASCII_NAME, fields0.get("name")); + Assert.assertEquals(MULTILINE_NOTE, fields0.get("notes")); + Assert.assertEquals(42, fields0.get("age")); + Assert.assertEquals(3.14, (Double) fields0.get("balance"), 0.0); + Assert.assertEquals(Boolean.TRUE, fields0.get("active")); + @SuppressWarnings("unchecked") + Map address = (Map) fields0.get("address"); + Assert.assertEquals(NON_ASCII_CITY, address.get("city")); + Assert.assertEquals("100000", address.get("zip")); + Assert.assertEquals("whole values map must be carried verbatim", row1, fields0); + + Assert.assertEquals("second record", records.get(1).getFields().get().get("name")); + Assert.assertFalse("no tokens supplied -> tokens absent", records.get(0).getTokens().isPresent()); + } + + @Test + public void testInsertBulk_homogeneousReachesGeneratedBody() { + VaultClient client = newVaultClient(); + InsertRequest request = InsertRequest.builder() + .table("cards") + .values(rows(richRow())) + .homogeneous(true) + .build(); + + RecordServiceInsertRecordBody body = client.getBulkInsertRequestBody(request); + Assert.assertTrue(body.getHomogeneous().isPresent()); + Assert.assertTrue(body.getHomogeneous().get()); + } + + @Test + public void testInsertBulk_defaultsReachGeneratedBody() { + VaultClient client = newVaultClient(); + InsertRequest request = InsertRequest.builder() + .table("cards") + .values(rows(richRow())) + .build(); + + RecordServiceInsertRecordBody body = client.getBulkInsertRequestBody(request); + Assert.assertFalse("returnTokens defaults to false", body.getTokenization().get()); + Assert.assertEquals("tokenMode defaults to DISABLE -> byot DISABLE", V1Byot.DISABLE, body.getByot().get()); + Assert.assertFalse("upsert not set -> absent", body.getUpsert().isPresent()); + Assert.assertFalse("homogeneous not set -> absent", body.getHomogeneous().isPresent()); + } + + @Test + public void testInsertBulk_tokensPairPositionallyWithValues() { + VaultClient client = newVaultClient(); + + HashMap value0 = new HashMap<>(); + value0.put("card_number", "4111111111111111"); + HashMap value1 = new HashMap<>(); + value1.put("card_number", "5111111111111111"); + + HashMap token0 = new HashMap<>(); + token0.put("card_number", "token-for-row-0"); + HashMap token1 = new HashMap<>(); + token1.put("card_number", "token-for-row-1"); + + InsertRequest request = InsertRequest.builder() + .table("cards") + .values(rows(value0, value1)) + .tokens(rows(token0, token1)) + .tokenMode(TokenMode.ENABLE_STRICT) + .build(); + + List records = client.getBulkInsertRequestBody(request).getRecords().get(); + + Assert.assertEquals("4111111111111111", records.get(0).getFields().get().get("card_number")); + Assert.assertEquals("token-for-row-0", records.get(0).getTokens().get().get("card_number")); + Assert.assertEquals("5111111111111111", records.get(1).getFields().get().get("card_number")); + Assert.assertEquals("token-for-row-1", records.get(1).getTokens().get().get("card_number")); + } + + @Test + public void testInsertBulk_recordOrderIsPreserved() { + VaultClient client = newVaultClient(); + ArrayList> values = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + HashMap row = new HashMap<>(); + row.put("position", i); + values.add(row); + } + + InsertRequest request = InsertRequest.builder().table("cards").values(values).build(); + List records = client.getBulkInsertRequestBody(request).getRecords().get(); + + Assert.assertEquals(5, records.size()); + for (int i = 0; i < 5; i++) { + Assert.assertEquals("record order must be preserved", i, records.get(i).getFields().get().get("position")); + } + } + + @Test + public void testInsertBulk_fewerTokensThanValuesLeavesTrailingRecordsWithoutTokens() { + VaultClient client = newVaultClient(); + + HashMap value0 = new HashMap<>(); + value0.put("card_number", "4111111111111111"); + HashMap value1 = new HashMap<>(); + value1.put("card_number", "5111111111111111"); + HashMap token0 = new HashMap<>(); + token0.put("card_number", "token-0"); + + InsertRequest request = InsertRequest.builder() + .table("cards") + .values(rows(value0, value1)) + .tokens(rows(token0)) + .tokenMode(TokenMode.ENABLE) + .build(); + + List records = client.getBulkInsertRequestBody(request).getRecords().get(); + Assert.assertEquals(2, records.size()); + Assert.assertEquals("token-0", records.get(0).getTokens().get().get("card_number")); + Assert.assertFalse("record without a matching token entry gets no tokens", + records.get(1).getTokens().isPresent()); + } + + // ================================================================== + // insert — batch branch (continueOnError == true) + // ================================================================== + + @Test + public void testInsertBatch_everyBuilderValueReachesGeneratedBody() { + VaultClient client = newVaultClient(); + + HashMap row1 = richRow(); + HashMap row2 = new HashMap<>(); + row2.put("name", "second record"); + + InsertRequest request = InsertRequest.builder() + .table("cards") + .values(rows(row1, row2)) + .returnTokens(true) + .upsert("email") + .tokenMode(TokenMode.ENABLE) + .continueOnError(true) + .build(); + + RecordServiceBatchOperationBody body = client.getBatchInsertRequestBody(request); + + Assert.assertTrue("batch body always sends continueOnError=true", body.getContinueOnError().get()); + Assert.assertEquals(V1Byot.ENABLE, body.getByot().get()); + + List records = body.getRecords().get(); + Assert.assertEquals(2, records.size()); + + V1BatchRecord first = records.get(0); + Assert.assertEquals("table maps to per-record tableName", "cards", first.getTableName().get()); + Assert.assertEquals(BatchRecordMethod.POST, first.getMethod().get()); + Assert.assertEquals("email", first.getUpsert().get()); + Assert.assertTrue(first.getTokenization().get()); + Assert.assertEquals("whole values map must be carried verbatim", row1, first.getFields().get()); + Assert.assertEquals(NON_ASCII_NAME, first.getFields().get().get("name")); + + Assert.assertEquals("cards", records.get(1).getTableName().get()); + Assert.assertEquals("second record", records.get(1).getFields().get().get("name")); + } + + @Test + public void testInsertBatch_tokensPairPositionallyAndOrderIsPreserved() { + VaultClient client = newVaultClient(); + + ArrayList> values = new ArrayList<>(); + ArrayList> tokens = new ArrayList<>(); + for (int i = 0; i < 4; i++) { + HashMap row = new HashMap<>(); + row.put("position", i); + values.add(row); + + HashMap token = new HashMap<>(); + token.put("position", "token-" + i); + tokens.add(token); + } + + InsertRequest request = InsertRequest.builder() + .table("cards") + .values(values) + .tokens(tokens) + .tokenMode(TokenMode.ENABLE_STRICT) + .continueOnError(true) + .build(); + + List records = client.getBatchInsertRequestBody(request).getRecords().get(); + Assert.assertEquals(4, records.size()); + for (int i = 0; i < 4; i++) { + Assert.assertEquals(i, records.get(i).getFields().get().get("position")); + Assert.assertEquals("token-" + i, records.get(i).getTokens().get().get("position")); + } + } + + /** + * KNOWN GAP: {@code homogeneous} is silently dropped when {@code continueOnError(true)} routes + * the insert down the batch path — the batch body has no field able to carry it. + */ + @Test + public void testInsertBatch_homogeneousIsDropped_knownGap() { + VaultClient client = newVaultClient(); + + InsertRequest bulkRequest = InsertRequest.builder() + .table("cards").values(rows(richRow())).homogeneous(true).continueOnError(false).build(); + RecordServiceInsertRecordBody bulkBody = client.getBulkInsertRequestBody(bulkRequest); + Assert.assertTrue("bulk branch DOES send homogeneous", bulkBody.getHomogeneous().get()); + Assert.assertTrue(bulkBody.toString().contains("homogeneous")); + + InsertRequest batchRequest = InsertRequest.builder() + .table("cards").values(rows(richRow())).homogeneous(true).continueOnError(true).build(); + RecordServiceBatchOperationBody batchBody = client.getBatchInsertRequestBody(batchRequest); + + for (Method method : RecordServiceBatchOperationBody.class.getMethods()) { + Assert.assertFalse("batch body has no homogeneous accessor", + method.getName().toLowerCase(Locale.ROOT).contains("homogeneous")); + } + for (Method method : V1BatchRecord.class.getMethods()) { + Assert.assertFalse("batch record has no homogeneous accessor", + method.getName().toLowerCase(Locale.ROOT).contains("homogeneous")); + } + Assert.assertTrue(batchBody.getAdditionalProperties().isEmpty()); + Assert.assertFalse("homogeneous never reaches the wire on the batch branch", + batchBody.toString().contains("homogeneous")); + } + + @Test + public void testInsert_continueOnErrorFalseUsesBulkEndpointWithTableAsPathParam() throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + RecordsClient mockRecords = Mockito.mock(RecordsClient.class); + when(mockApi.records()).thenReturn(mockRecords); + when(mockRecords.recordServiceInsertRecord(anyString(), anyString(), any())) + .thenReturn(V1InsertRecordResponse.builder().build()); + + VaultController controller = newControllerWithMockApi(mockApi); + InsertRequest request = InsertRequest.builder() + .table("cards").values(rows(richRow())).returnTokens(true).continueOnError(false).build(); + controller.insert(request); + + ArgumentCaptor bodyCaptor = + ArgumentCaptor.forClass(RecordServiceInsertRecordBody.class); + Mockito.verify(mockRecords).recordServiceInsertRecord(eq(VAULT_ID), eq("cards"), bodyCaptor.capture()); + Assert.assertTrue(bodyCaptor.getValue().getTokenization().get()); + Mockito.verify(mockRecords, Mockito.never()).withRawResponse(); + } + + @Test + public void testInsert_continueOnErrorTrueUsesBatchEndpoint() throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + RecordsClient mockRecords = Mockito.mock(RecordsClient.class); + RawRecordsClient mockRawRecords = Mockito.mock(RawRecordsClient.class); + when(mockApi.records()).thenReturn(mockRecords); + when(mockRecords.withRawResponse()).thenReturn(mockRawRecords); + when(mockRawRecords.recordServiceBatchOperation(anyString(), any(), any())) + .thenReturn(new com.skyflow.generated.rest.core.ApiClientHttpResponse<>( + V1BatchOperationResponse.builder().build(), buildOkHttpResponse())); + + VaultController controller = newControllerWithMockApi(mockApi); + InsertRequest request = InsertRequest.builder() + .table("cards").values(rows(richRow())).continueOnError(true).build(); + controller.insert(request); + + ArgumentCaptor bodyCaptor = + ArgumentCaptor.forClass(RecordServiceBatchOperationBody.class); + Mockito.verify(mockRawRecords).recordServiceBatchOperation(eq(VAULT_ID), bodyCaptor.capture(), any()); + Assert.assertTrue(bodyCaptor.getValue().getContinueOnError().get()); + Assert.assertEquals("cards", bodyCaptor.getValue().getRecords().get().get(0).getTableName().get()); + Mockito.verify(mockRecords, Mockito.never()).recordServiceInsertRecord(anyString(), anyString(), any()); + } + + // ================================================================== + // update + // ================================================================== + + @Test + public void testUpdate_everyBuilderValueReachesGeneratedRequest() throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + RecordsClient mockRecords = Mockito.mock(RecordsClient.class); + when(mockApi.records()).thenReturn(mockRecords); + when(mockRecords.recordServiceUpdateRecord(anyString(), anyString(), anyString(), any(), any())) + .thenReturn(V1UpdateRecordResponse.builder().skyflowId("sky-id-1").build()); + + VaultController controller = newControllerWithMockApi(mockApi); + + HashMap data = new HashMap<>(); + data.put("skyflowId", "sky-id-1"); + data.put("name", NON_ASCII_NAME); + data.put("notes", MULTILINE_NOTE); + data.put("age", 42); + + HashMap tokens = new HashMap<>(); + tokens.put("name", "token-for-name"); + + UpdateRequest request = UpdateRequest.builder() + .table("cards") + .data(data) + .tokens(tokens) + .returnTokens(true) + .tokenMode(TokenMode.ENABLE) + .build(); + + controller.update(request); + + ArgumentCaptor tableCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor idCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor bodyCaptor = + ArgumentCaptor.forClass(RecordServiceUpdateRecordBody.class); + Mockito.verify(mockRecords).recordServiceUpdateRecord( + eq(VAULT_ID), tableCaptor.capture(), idCaptor.capture(), bodyCaptor.capture(), any()); + + Assert.assertEquals("cards", tableCaptor.getValue()); + Assert.assertEquals("skyflowId becomes the path id", "sky-id-1", idCaptor.getValue()); + + RecordServiceUpdateRecordBody body = bodyCaptor.getValue(); + Assert.assertTrue(body.getTokenization().get()); + Assert.assertEquals(V1Byot.ENABLE, body.getByot().get()); + + Map fields = body.getRecord().get().getFields().get(); + Assert.assertFalse("skyflowId must be stripped from fields", fields.containsKey("skyflowId")); + Assert.assertEquals(NON_ASCII_NAME, fields.get("name")); + Assert.assertEquals(MULTILINE_NOTE, fields.get("notes")); + Assert.assertEquals(42, fields.get("age")); + Assert.assertEquals(3, fields.size()); + + Assert.assertEquals("token-for-name", body.getRecord().get().getTokens().get().get("name")); + } + + @Test + public void testUpdate_deprecatedSkyflowIdKeyBecomesPathIdAndIsStripped() throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + RecordsClient mockRecords = Mockito.mock(RecordsClient.class); + when(mockApi.records()).thenReturn(mockRecords); + when(mockRecords.recordServiceUpdateRecord(anyString(), anyString(), anyString(), any(), any())) + .thenReturn(V1UpdateRecordResponse.builder().skyflowId("snake-id").build()); + + VaultController controller = newControllerWithMockApi(mockApi); + + HashMap data = new HashMap<>(); + data.put("skyflow_id", "snake-id"); + data.put("name", NON_ASCII_NAME); + + controller.update(UpdateRequest.builder().table("cards").data(data).build()); + + ArgumentCaptor idCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor bodyCaptor = + ArgumentCaptor.forClass(RecordServiceUpdateRecordBody.class); + Mockito.verify(mockRecords).recordServiceUpdateRecord( + anyString(), anyString(), idCaptor.capture(), bodyCaptor.capture(), any()); + + Assert.assertEquals("snake-id", idCaptor.getValue()); + Map fields = bodyCaptor.getValue().getRecord().get().getFields().get(); + Assert.assertFalse(fields.containsKey("skyflow_id")); + Assert.assertEquals(NON_ASCII_NAME, fields.get("name")); + } + + @Test + public void testUpdate_defaultsReachGeneratedRequest() throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + RecordsClient mockRecords = Mockito.mock(RecordsClient.class); + when(mockApi.records()).thenReturn(mockRecords); + when(mockRecords.recordServiceUpdateRecord(anyString(), anyString(), anyString(), any(), any())) + .thenReturn(V1UpdateRecordResponse.builder().skyflowId("sky-id-1").build()); + + VaultController controller = newControllerWithMockApi(mockApi); + + HashMap data = new HashMap<>(); + data.put("skyflowId", "sky-id-1"); + data.put("name", "plain"); + + controller.update(UpdateRequest.builder().table("cards").data(data).build()); + + ArgumentCaptor bodyCaptor = + ArgumentCaptor.forClass(RecordServiceUpdateRecordBody.class); + Mockito.verify(mockRecords).recordServiceUpdateRecord( + anyString(), anyString(), anyString(), bodyCaptor.capture(), any()); + + Assert.assertFalse("returnTokens defaults to false", bodyCaptor.getValue().getTokenization().get()); + Assert.assertEquals(V1Byot.DISABLE, bodyCaptor.getValue().getByot().get()); + Assert.assertFalse("no tokens supplied -> tokens absent", + bodyCaptor.getValue().getRecord().get().getTokens().isPresent()); + } + + /** + * KNOWN GAP: {@code update()} removes {@code skyflowId} from the caller's own data map, so the + * same request object cannot be reused — a second call fails validation. + */ + @Test + public void testUpdate_mutatesCallersDataMap_knownGap() throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + RecordsClient mockRecords = Mockito.mock(RecordsClient.class); + when(mockApi.records()).thenReturn(mockRecords); + when(mockRecords.recordServiceUpdateRecord(anyString(), anyString(), anyString(), any(), any())) + .thenReturn(V1UpdateRecordResponse.builder().skyflowId("sky-id-1").build()); + + VaultController controller = newControllerWithMockApi(mockApi); + + HashMap data = new HashMap<>(); + data.put("skyflowId", "sky-id-1"); + data.put("name", NON_ASCII_NAME); + + UpdateRequest request = UpdateRequest.builder().table("cards").data(data).build(); + controller.update(request); + + Assert.assertFalse("caller's map is mutated: skyflowId removed", data.containsKey("skyflowId")); + Assert.assertSame("request still hands back the same (now-mutated) map", data, request.getData()); + + try { + controller.update(request); + Assert.fail("second update with the same request object should fail"); + } catch (SkyflowException e) { + Assert.assertTrue("fails because skyflowId is gone from the caller's map: " + e.getMessage(), + e.getMessage().contains("'skyflow_id' is missing from the data payload")); + } + } + + // ================================================================== + // get + // ================================================================== + + private static VaultController mockGetController(RecordsClient mockRecords) throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + when(mockApi.records()).thenReturn(mockRecords); + when(mockRecords.recordServiceBulkGetRecord(anyString(), anyString(), any(), any())) + .thenReturn(V1BulkGetRecordResponse.builder() + .records(Collections.emptyList()) + .build()); + return newControllerWithMockApi(mockApi); + } + + @Test + public void testGet_idsFieldsRedactionAndPagingReachGeneratedRequest() throws Exception { + RecordsClient mockRecords = Mockito.mock(RecordsClient.class); + VaultController controller = mockGetController(mockRecords); + + GetRequest request = GetRequest.builder() + .table("cards") + .ids(list("id-1", "id-2", "id-3")) + .fields(list("name", "card_number")) + .redactionType(RedactionType.PLAIN_TEXT) + .offset("10") + .limit("25") + .downloadUrl(false) + .orderBy("DESCENDING") + .build(); + + controller.get(request); + + ArgumentCaptor tableCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor requestCaptor = + ArgumentCaptor.forClass(RecordServiceBulkGetRecordRequest.class); + Mockito.verify(mockRecords).recordServiceBulkGetRecord( + eq(VAULT_ID), tableCaptor.capture(), requestCaptor.capture(), any()); + + Assert.assertEquals("cards", tableCaptor.getValue()); + RecordServiceBulkGetRecordRequest generated = requestCaptor.getValue(); + Assert.assertEquals("ids map to skyflowIds, order preserved", + Arrays.asList("id-1", "id-2", "id-3"), generated.getSkyflowIds().get()); + Assert.assertEquals(Arrays.asList("name", "card_number"), generated.getFields().get()); + Assert.assertEquals(RecordServiceBulkGetRecordRequestRedaction.PLAIN_TEXT, generated.getRedaction().get()); + Assert.assertEquals("10", generated.getOffset().get()); + Assert.assertEquals("25", generated.getLimit().get()); + Assert.assertFalse("downloadUrl(false) must reach downloadURL", generated.getDownloadUrl().get()); + Assert.assertEquals(RecordServiceBulkGetRecordRequestOrderBy.DESCENDING, generated.getOrderBy().get()); + Assert.assertFalse(generated.getColumnName().isPresent()); + Assert.assertFalse(generated.getColumnValues().isPresent()); + } + + @Test + public void testGet_columnNameAndColumnValuesReachGeneratedRequest() throws Exception { + RecordsClient mockRecords = Mockito.mock(RecordsClient.class); + VaultController controller = mockGetController(mockRecords); + + GetRequest request = GetRequest.builder() + .table("cards") + .columnName("email") + .columnValues(list("a@example.com", NON_ASCII_NAME, "value with spaces")) + .redactionType(RedactionType.MASKED) + .build(); + + controller.get(request); + + ArgumentCaptor requestCaptor = + ArgumentCaptor.forClass(RecordServiceBulkGetRecordRequest.class); + Mockito.verify(mockRecords).recordServiceBulkGetRecord( + anyString(), anyString(), requestCaptor.capture(), any()); + + RecordServiceBulkGetRecordRequest generated = requestCaptor.getValue(); + Assert.assertEquals("email", generated.getColumnName().get()); + Assert.assertEquals(Arrays.asList("a@example.com", NON_ASCII_NAME, "value with spaces"), + generated.getColumnValues().get()); + Assert.assertEquals(RecordServiceBulkGetRecordRequestRedaction.MASKED, generated.getRedaction().get()); + Assert.assertFalse(generated.getSkyflowIds().isPresent()); + } + + @Test + public void testGet_returnTokensMapsToTokenization() throws Exception { + RecordsClient mockRecords = Mockito.mock(RecordsClient.class); + VaultController controller = mockGetController(mockRecords); + + controller.get(GetRequest.builder().table("cards").ids(list("id-1")).returnTokens(true).build()); + + ArgumentCaptor requestCaptor = + ArgumentCaptor.forClass(RecordServiceBulkGetRecordRequest.class); + Mockito.verify(mockRecords).recordServiceBulkGetRecord( + anyString(), anyString(), requestCaptor.capture(), any()); + + Assert.assertTrue(requestCaptor.getValue().getTokenization().get()); + Assert.assertFalse("no redactionType -> redaction absent", requestCaptor.getValue().getRedaction().isPresent()); + } + + @Test + public void testGet_defaultOrderByAndDownloadUrlReachGeneratedRequest() throws Exception { + RecordsClient mockRecords = Mockito.mock(RecordsClient.class); + VaultController controller = mockGetController(mockRecords); + + controller.get(GetRequest.builder().table("cards").ids(list("id-1")).build()); + + ArgumentCaptor requestCaptor = + ArgumentCaptor.forClass(RecordServiceBulkGetRecordRequest.class); + Mockito.verify(mockRecords).recordServiceBulkGetRecord( + anyString(), anyString(), requestCaptor.capture(), any()); + + Assert.assertEquals(RecordServiceBulkGetRecordRequestOrderBy.ASCENDING, + requestCaptor.getValue().getOrderBy().get()); + Assert.assertTrue("downloadUrl defaults to true", requestCaptor.getValue().getDownloadUrl().get()); + } + + @Test + public void testGet_orderByAcceptsAllThreeGeneratedEnumNames() throws Exception { + for (String orderBy : new String[]{"ASCENDING", "DESCENDING", "NONE"}) { + RecordsClient mockRecords = Mockito.mock(RecordsClient.class); + VaultController controller = mockGetController(mockRecords); + + controller.get(GetRequest.builder().table("cards").ids(list("id-1")).orderBy(orderBy).build()); + + ArgumentCaptor requestCaptor = + ArgumentCaptor.forClass(RecordServiceBulkGetRecordRequest.class); + Mockito.verify(mockRecords).recordServiceBulkGetRecord( + anyString(), anyString(), requestCaptor.capture(), any()); + Assert.assertEquals(RecordServiceBulkGetRecordRequestOrderBy.valueOf(orderBy), + requestCaptor.getValue().getOrderBy().get()); + } + } + + /** + * KNOWN GAP: {@code orderBy} accepts any String but is mapped with {@code Enum.valueOf}, so an + * unrecognised value escapes as a raw {@link IllegalArgumentException} instead of a + * {@link SkyflowException}. + */ + @Test + public void testGet_orderByInvalidValueThrowsRawIllegalArgumentException_knownGap() throws Exception { + for (String orderBy : new String[]{"DESC", "descending", "asc"}) { + RecordsClient mockRecords = Mockito.mock(RecordsClient.class); + VaultController controller = mockGetController(mockRecords); + try { + controller.get(GetRequest.builder().table("cards").ids(list("id-1")).orderBy(orderBy).build()); + Assert.fail("expected an exception for orderBy=" + orderBy); + } catch (SkyflowException e) { + Assert.fail("orderBy=" + orderBy + " should NOT surface as SkyflowException (known gap)"); + } catch (IllegalArgumentException e) { + Assert.assertTrue(e.getMessage().contains(orderBy)); + } + Mockito.verify(mockRecords, Mockito.never()) + .recordServiceBulkGetRecord(anyString(), anyString(), any(), any()); + } + } + + /** + * KNOWN GAP: {@code downloadUrl(null)} silently coerces to {@code true}, while + * {@code returnTokens(null)} keeps the null (and therefore disappears from the request). + */ + @Test + public void testGet_downloadUrlNullCoercesToTrueButReturnTokensNullDoesNot_knownGap() throws Exception { + GetRequest request = GetRequest.builder() + .table("cards") + .ids(list("id-1")) + .downloadUrl(null) + .returnTokens(null) + .build(); + + Assert.assertTrue("downloadUrl(null) coerces to true", request.getDownloadUrl()); + Assert.assertNull("returnTokens(null) stays null", request.getReturnTokens()); + + RecordsClient mockRecords = Mockito.mock(RecordsClient.class); + VaultController controller = mockGetController(mockRecords); + controller.get(request); + + ArgumentCaptor requestCaptor = + ArgumentCaptor.forClass(RecordServiceBulkGetRecordRequest.class); + Mockito.verify(mockRecords).recordServiceBulkGetRecord( + anyString(), anyString(), requestCaptor.capture(), any()); + + Assert.assertTrue("downloadURL=true is sent even though the user passed null", + requestCaptor.getValue().getDownloadUrl().get()); + Assert.assertFalse("tokenization is omitted for a null returnTokens", + requestCaptor.getValue().getTokenization().isPresent()); + } + + // ================================================================== + // delete + // ================================================================== + + @Test + public void testDelete_tableAndIdsReachGeneratedRequest() throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + RecordsClient mockRecords = Mockito.mock(RecordsClient.class); + when(mockApi.records()).thenReturn(mockRecords); + when(mockRecords.recordServiceBulkDeleteRecord(anyString(), anyString(), any(), any())) + .thenReturn(V1BulkDeleteRecordResponse.builder().build()); + + VaultController controller = newControllerWithMockApi(mockApi); + controller.delete(DeleteRequest.builder() + .table("cards") + .ids(list("id-3", "id-1", "id-2")) + .build()); + + ArgumentCaptor tableCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor bodyCaptor = + ArgumentCaptor.forClass(RecordServiceBulkDeleteRecordBody.class); + Mockito.verify(mockRecords).recordServiceBulkDeleteRecord( + eq(VAULT_ID), tableCaptor.capture(), bodyCaptor.capture(), any()); + + Assert.assertEquals("cards", tableCaptor.getValue()); + Assert.assertEquals("ids map to skyflowIds, order preserved", + Arrays.asList("id-3", "id-1", "id-2"), bodyCaptor.getValue().getSkyflowIds().get()); + } + + // ================================================================== + // query + // ================================================================== + + @Test + public void testQuery_queryStringReachesGeneratedBodyVerbatim() throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + QueryClient mockQuery = Mockito.mock(QueryClient.class); + when(mockApi.query()).thenReturn(mockQuery); + when(mockQuery.queryServiceExecuteQuery(anyString(), any(), any())) + .thenReturn(V1GetQueryResponse.builder().build()); + + VaultController controller = newControllerWithMockApi(mockApi); + + String sql = "SELECT name, \"card number\"\nFROM cards\n" + + "WHERE name = 'Smith''s' AND city = '" + NON_ASCII_CITY + "'\nLIMIT 10"; + controller.query(QueryRequest.builder().query(sql).build()); + + ArgumentCaptor bodyCaptor = + ArgumentCaptor.forClass(QueryServiceExecuteQueryBody.class); + Mockito.verify(mockQuery).queryServiceExecuteQuery(eq(VAULT_ID), bodyCaptor.capture(), any()); + + Assert.assertEquals("query with quotes and newlines must be sent unchanged", + sql, bodyCaptor.getValue().getQuery().get()); + } + + @Test + public void testQuery_simpleQueryReachesGeneratedBody() throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + QueryClient mockQuery = Mockito.mock(QueryClient.class); + when(mockApi.query()).thenReturn(mockQuery); + when(mockQuery.queryServiceExecuteQuery(anyString(), any(), any())) + .thenReturn(V1GetQueryResponse.builder().build()); + + VaultController controller = newControllerWithMockApi(mockApi); + controller.query(QueryRequest.builder().query("SELECT * FROM cards LIMIT 1").build()); + + ArgumentCaptor bodyCaptor = + ArgumentCaptor.forClass(QueryServiceExecuteQueryBody.class); + Mockito.verify(mockQuery).queryServiceExecuteQuery(anyString(), bodyCaptor.capture(), any()); + Assert.assertEquals("SELECT * FROM cards LIMIT 1", bodyCaptor.getValue().getQuery().get()); + } + + // ================================================================== + // tokenize + // ================================================================== + + @Test + public void testTokenize_columnValuesReachPayloadInOrder() { + VaultClient client = newVaultClient(); + + List columnValues = Arrays.asList( + ColumnValue.builder().value("4111111111111111").columnGroup("cg_cards").build(), + ColumnValue.builder().value(NON_ASCII_NAME).columnGroup("cg_names").build(), + ColumnValue.builder().value("value with spaces").columnGroup("cg_misc").build()); + + V1TokenizePayload payload = client.getTokenizePayload( + TokenizeRequest.builder().values(columnValues).build()); + + List parameters = payload.getTokenizationParameters().get(); + Assert.assertEquals(3, parameters.size()); + Assert.assertEquals("4111111111111111", parameters.get(0).getValue().get()); + Assert.assertEquals("cg_cards", parameters.get(0).getColumnGroup().get()); + Assert.assertEquals(NON_ASCII_NAME, parameters.get(1).getValue().get()); + Assert.assertEquals("cg_names", parameters.get(1).getColumnGroup().get()); + Assert.assertEquals("value with spaces", parameters.get(2).getValue().get()); + Assert.assertEquals("cg_misc", parameters.get(2).getColumnGroup().get()); + } + + @Test + public void testTokenize_payloadReachesGeneratedApiCall() throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + com.skyflow.generated.rest.resources.tokens.TokensClient mockTokens = + Mockito.mock(com.skyflow.generated.rest.resources.tokens.TokensClient.class); + when(mockApi.tokens()).thenReturn(mockTokens); + when(mockTokens.recordServiceTokenize(anyString(), any(), any())) + .thenReturn(V1TokenizeResponse.builder().build()); + + VaultController controller = newControllerWithMockApi(mockApi); + controller.tokenize(TokenizeRequest.builder().values(Collections.singletonList( + ColumnValue.builder().value(NON_ASCII_NAME).columnGroup("cg_names").build())).build()); + + ArgumentCaptor payloadCaptor = ArgumentCaptor.forClass(V1TokenizePayload.class); + Mockito.verify(mockTokens).recordServiceTokenize(eq(VAULT_ID), payloadCaptor.capture(), any()); + + V1TokenizeRecordRequest parameter = payloadCaptor.getValue().getTokenizationParameters().get().get(0); + Assert.assertEquals(NON_ASCII_NAME, parameter.getValue().get()); + Assert.assertEquals("cg_names", parameter.getColumnGroup().get()); + } + + // ================================================================== + // detokenize + // ================================================================== + + @Test + public void testDetokenize_eachTokenKeepsItsOwnRedactionType() { + VaultClient client = newVaultClient(); + + ArrayList detokenizeData = new ArrayList<>(Arrays.asList( + new DetokenizeData("tok-default", RedactionType.DEFAULT), + new DetokenizeData("tok-plain", RedactionType.PLAIN_TEXT), + new DetokenizeData("tok-masked", RedactionType.MASKED), + new DetokenizeData("tok-redacted", RedactionType.REDACTED))); + + V1DetokenizePayload payload = client.getDetokenizePayload( + DetokenizeRequest.builder().detokenizeData(detokenizeData).build()); + + List parameters = payload.getDetokenizationParameters().get(); + Assert.assertEquals(4, parameters.size()); + + Assert.assertEquals("tok-default", parameters.get(0).getToken().get()); + Assert.assertEquals(RedactionEnumRedaction.DEFAULT, parameters.get(0).getRedaction().get()); + Assert.assertEquals("tok-plain", parameters.get(1).getToken().get()); + Assert.assertEquals(RedactionEnumRedaction.PLAIN_TEXT, parameters.get(1).getRedaction().get()); + Assert.assertEquals("tok-masked", parameters.get(2).getToken().get()); + Assert.assertEquals(RedactionEnumRedaction.MASKED, parameters.get(2).getRedaction().get()); + Assert.assertEquals("tok-redacted", parameters.get(3).getToken().get()); + Assert.assertEquals(RedactionEnumRedaction.REDACTED, parameters.get(3).getRedaction().get()); + } + + @Test + public void testDetokenize_tokenWithoutRedactionDefaultsToDefault() { + VaultClient client = newVaultClient(); + ArrayList detokenizeData = new ArrayList<>(Arrays.asList( + new DetokenizeData("tok-a"), + new DetokenizeData("tok-b", null))); + + V1DetokenizePayload payload = client.getDetokenizePayload( + DetokenizeRequest.builder().detokenizeData(detokenizeData).build()); + + List parameters = payload.getDetokenizationParameters().get(); + Assert.assertEquals(RedactionEnumRedaction.DEFAULT, parameters.get(0).getRedaction().get()); + Assert.assertEquals(RedactionEnumRedaction.DEFAULT, parameters.get(1).getRedaction().get()); + } + + @Test + public void testDetokenize_continueOnErrorAndDownloadUrlReachPayload() { + VaultClient client = newVaultClient(); + ArrayList detokenizeData = + new ArrayList<>(Collections.singletonList(new DetokenizeData("tok-a"))); + + V1DetokenizePayload payload = client.getDetokenizePayload(DetokenizeRequest.builder() + .detokenizeData(detokenizeData) + .continueOnError(true) + .downloadUrl(true) + .build()); + Assert.assertTrue(payload.getContinueOnError().get()); + Assert.assertTrue(payload.getDownloadUrl().get()); + + V1DetokenizePayload defaults = client.getDetokenizePayload(DetokenizeRequest.builder() + .detokenizeData(detokenizeData) + .build()); + Assert.assertFalse("continueOnError defaults to false", defaults.getContinueOnError().get()); + Assert.assertFalse("downloadUrl defaults to false", defaults.getDownloadUrl().get()); + } + + @Test + public void testDetokenize_tokenOrderIsPreserved() { + VaultClient client = newVaultClient(); + ArrayList detokenizeData = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + detokenizeData.add(new DetokenizeData("tok-" + i, RedactionType.MASKED)); + } + + List parameters = client.getDetokenizePayload( + DetokenizeRequest.builder().detokenizeData(detokenizeData).build()) + .getDetokenizationParameters().get(); + + for (int i = 0; i < 5; i++) { + Assert.assertEquals("tok-" + i, parameters.get(i).getToken().get()); + } + } + + // ================================================================== + // uploadFile + // ================================================================== + + private static VaultController mockUploadController(RecordsClient mockRecords) throws Exception { + ApiClient mockApi = Mockito.mock(ApiClient.class); + when(mockApi.records()).thenReturn(mockRecords); + when(mockRecords.uploadFileV2(anyString(), any(File.class), any(), any())) + .thenReturn(UploadFileV2Response.builder().build()); + return newControllerWithMockApi(mockApi); + } + + @Test + public void testUploadFile_metadataFieldsAndFilePathReachGeneratedRequest() throws Exception { + File tempFile = File.createTempFile("fidelity-upload", ".txt"); + tempFile.deleteOnExit(); + Files.write(tempFile.toPath(), "hello".getBytes("UTF-8")); + + RecordsClient mockRecords = Mockito.mock(RecordsClient.class); + VaultController controller = mockUploadController(mockRecords); + + controller.uploadFile(FileUploadRequest.builder() + .table("cards") + .columnName("file_column") + .skyflowId("sky-id-9") + .filePath(tempFile.getAbsolutePath()) + .build()); + + ArgumentCaptor fileCaptor = ArgumentCaptor.forClass(File.class); + ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(UploadFileV2Request.class); + Mockito.verify(mockRecords).uploadFileV2( + eq(VAULT_ID), fileCaptor.capture(), requestCaptor.capture(), any()); + + UploadFileV2Request generated = requestCaptor.getValue(); + Assert.assertEquals("table maps to tableName", "cards", generated.getTableName()); + Assert.assertEquals("file_column", generated.getColumnName()); + Assert.assertEquals("sky-id-9", generated.getSkyflowId().get()); + Assert.assertFalse("returnFileMetadata is hard-coded to false", generated.getReturnFileMetadata().get()); + Assert.assertEquals(tempFile.getAbsolutePath(), fileCaptor.getValue().getPath()); + } + + @Test + public void testUploadFile_base64AndFileNameProduceMatchingFile() throws Exception { + File tempDir = Files.createTempDirectory("fidelity-b64").toFile(); + tempDir.deleteOnExit(); + File target = new File(tempDir, "decoded-upload.txt"); + target.deleteOnExit(); + + byte[] payload = "café ☕".getBytes("UTF-8"); + String base64 = Base64.getEncoder().encodeToString(payload); + + RecordsClient mockRecords = Mockito.mock(RecordsClient.class); + VaultController controller = mockUploadController(mockRecords); + + controller.uploadFile(FileUploadRequest.builder() + .table("cards") + .columnName("file_column") + .skyflowId("sky-id-b64") + .base64(base64) + .fileName(target.getAbsolutePath()) + .build()); + + ArgumentCaptor fileCaptor = ArgumentCaptor.forClass(File.class); + ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(UploadFileV2Request.class); + Mockito.verify(mockRecords).uploadFileV2( + anyString(), fileCaptor.capture(), requestCaptor.capture(), any()); + + Assert.assertEquals(target.getAbsolutePath(), fileCaptor.getValue().getPath()); + Assert.assertArrayEquals("base64 must be decoded into the named file", + payload, Files.readAllBytes(fileCaptor.getValue().toPath())); + Assert.assertEquals("sky-id-b64", requestCaptor.getValue().getSkyflowId().get()); + } + + @Test + public void testUploadFile_fileObjectIsPassedThroughUnchanged() throws Exception { + File tempFile = File.createTempFile("fidelity-object", ".bin"); + tempFile.deleteOnExit(); + Files.write(tempFile.toPath(), new byte[]{1, 2, 3}); + + RecordsClient mockRecords = Mockito.mock(RecordsClient.class); + VaultController controller = mockUploadController(mockRecords); + + controller.uploadFile(FileUploadRequest.builder() + .table("cards") + .columnName("file_column") + .fileObject(tempFile) + .build()); + + ArgumentCaptor fileCaptor = ArgumentCaptor.forClass(File.class); + ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(UploadFileV2Request.class); + Mockito.verify(mockRecords).uploadFileV2( + anyString(), fileCaptor.capture(), requestCaptor.capture(), any()); + + Assert.assertSame("the caller's File instance must be forwarded", tempFile, fileCaptor.getValue()); + Assert.assertFalse("no skyflowId supplied -> absent", requestCaptor.getValue().getSkyflowId().isPresent()); + } + + @Test + public void testUploadFile_filePathTakesPrecedenceOverOtherSources() throws Exception { + File pathFile = File.createTempFile("fidelity-precedence-path", ".txt"); + pathFile.deleteOnExit(); + Files.write(pathFile.toPath(), "from-path".getBytes("UTF-8")); + + File objectFile = File.createTempFile("fidelity-precedence-object", ".txt"); + objectFile.deleteOnExit(); + Files.write(objectFile.toPath(), "from-object".getBytes("UTF-8")); + + RecordsClient mockRecords = Mockito.mock(RecordsClient.class); + VaultController controller = mockUploadController(mockRecords); + + controller.uploadFile(FileUploadRequest.builder() + .table("cards") + .columnName("file_column") + .filePath(pathFile.getAbsolutePath()) + .fileObject(objectFile) + .build()); + + ArgumentCaptor fileCaptor = ArgumentCaptor.forClass(File.class); + Mockito.verify(mockRecords).uploadFileV2(anyString(), fileCaptor.capture(), any(), any()); + Assert.assertEquals(pathFile.getAbsolutePath(), fileCaptor.getValue().getPath()); + } + + // ================================================================== + // invoke — URL construction (com.skyflow.utils.Utils) + // ================================================================== + + private static ConnectionConfig connectionConfig(String url) { + ConnectionConfig config = new ConnectionConfig(); + config.setConnectionId("conn123"); + config.setConnectionUrl(url); + config.setCredentials(apiKeyCredentials()); + return config; + } + + @Test + public void testInvoke_pathParamsAreSubstitutedIntoTheUrl() { + Map pathParams = new LinkedHashMap<>(); + pathParams.put("resource", "cards"); + + String url = Utils.constructConnectionURL(connectionConfig(CONNECTION_URL), + InvokeConnectionRequest.builder().pathParams(pathParams).build()); + + Assert.assertEquals("https://conn.example.com/api/cards/details", url); + } + + @Test + public void testInvoke_queryParamsAreAppendedInOrderWithNoTrailingAmpersand() { + Map queryParams = new LinkedHashMap<>(); + queryParams.put("limit", "10"); + queryParams.put("offset", "20"); + + String url = Utils.constructConnectionURL(connectionConfig("https://conn.example.com/api"), + InvokeConnectionRequest.builder().queryParams(queryParams).build()); + + Assert.assertEquals("https://conn.example.com/api?limit=10&offset=20", url); + } + + /** + * KNOWN GAP: query-param values are concatenated raw, so a value containing {@code &} or + * {@code =} re-partitions the query string into extra parameters. + */ + @Test + public void testInvoke_queryParamValuesAreNotPercentEncoded_knownGap() { + Map queryParams = new LinkedHashMap<>(); + queryParams.put("q", "a&b=c"); + + String url = Utils.constructConnectionURL(connectionConfig("https://conn.example.com/api"), + InvokeConnectionRequest.builder().queryParams(queryParams).build()); + + Assert.assertEquals("value is injected raw", "https://conn.example.com/api?q=a&b=c", url); + + String queryString = url.substring(url.indexOf('?') + 1); + Assert.assertEquals("one user param has become two wire params", 2, queryString.split("&").length); + Assert.assertEquals("q=a", queryString.split("&")[0]); + Assert.assertEquals("b=c", queryString.split("&")[1]); + } + + /** + * KNOWN GAP: a query-param value containing a space is injected raw, producing an invalid URI. + */ + @Test + public void testInvoke_queryParamValueWithSpaceIsNotEncoded_knownGap() { + Map queryParams = new LinkedHashMap<>(); + queryParams.put("name", "John Doe"); + + String url = Utils.constructConnectionURL(connectionConfig("https://conn.example.com/api"), + InvokeConnectionRequest.builder().queryParams(queryParams).build()); + + Assert.assertEquals("https://conn.example.com/api?name=John Doe", url); + Assert.assertFalse("no %20 encoding is applied", url.contains("%20")); + try { + java.net.URI.create(url); + Assert.fail("raw space should make this an invalid URI"); + } catch (IllegalArgumentException expected) { + Assert.assertTrue(expected.getMessage().contains("Illegal character")); + } + } + + /** + * KNOWN GAP: path-param values are substituted raw, so a value containing {@code /} silently + * changes the URL's path structure. + */ + @Test + public void testInvoke_pathParamValuesAreNotPercentEncoded_knownGap() { + Map pathParams = new LinkedHashMap<>(); + pathParams.put("resource", "cards/123"); + + String url = Utils.constructConnectionURL(connectionConfig(CONNECTION_URL), + InvokeConnectionRequest.builder().pathParams(pathParams).build()); + + Assert.assertEquals("https://conn.example.com/api/cards/123/details", url); + Assert.assertFalse("no %2F encoding is applied", url.toUpperCase(Locale.ROOT).contains("%2F")); + Assert.assertEquals("the path gained a segment", 5, java.net.URI.create(url).getPath().split("/").length); + } + + /** + * KNOWN GAP: a path-param entry with no matching {@code {placeholder}} is discarded silently — + * no error, no log, and the URL is unchanged. + */ + @Test + public void testInvoke_pathParamWithoutMatchingPlaceholderIsSilentlyDiscarded_knownGap() { + Map pathParams = new LinkedHashMap<>(); + pathParams.put("resource", "cards"); + pathParams.put("thisKeyIsNotInTheUrl", "ignored-value"); + + String url = Utils.constructConnectionURL(connectionConfig(CONNECTION_URL), + InvokeConnectionRequest.builder().pathParams(pathParams).build()); + + Assert.assertEquals("https://conn.example.com/api/cards/details", url); + Assert.assertFalse(url.contains("ignored-value")); + Assert.assertFalse(url.contains("thisKeyIsNotInTheUrl")); + } + + @Test + public void testInvoke_unfilledPlaceholderRemainsInTheUrl() { + String url = Utils.constructConnectionURL(connectionConfig(CONNECTION_URL), + InvokeConnectionRequest.builder().build()); + Assert.assertEquals("https://conn.example.com/api/{resource}/details", url); + } + + // ================================================================== + // invoke — header construction (com.skyflow.utils.Utils) + // ================================================================== + + @Test + public void testInvoke_headerKeysAreLowercasedAndValuesPreserved() { + Map headers = new LinkedHashMap<>(); + headers.put("Content-Type", "application/json"); + headers.put("X-Custom-Header", NON_ASCII_NAME); + + Map constructed = Utils.constructConnectionHeadersMap(headers); + + Assert.assertEquals(2, constructed.size()); + Assert.assertEquals("application/json", constructed.get("content-type")); + Assert.assertEquals(NON_ASCII_NAME, constructed.get("x-custom-header")); + } + + /** + * KNOWN GAP: header keys are lowercased without a {@link Locale}, and two keys differing only + * in case collapse into a single entry (last writer wins). + */ + @Test + public void testInvoke_headerKeysDifferingOnlyInCaseCollapse_knownGap() { + Map headers = new LinkedHashMap<>(); + headers.put("X-Custom", "first-value"); + headers.put("x-custom", "second-value"); + headers.put("X-CUSTOM", "third-value"); + + Map constructed = Utils.constructConnectionHeadersMap(headers); + + Assert.assertEquals("three user headers collapse into one", 1, constructed.size()); + Assert.assertEquals("last writer wins", "third-value", constructed.get("x-custom")); + } + + // ================================================================== + // invoke — body encoding (com.skyflow.utils.HttpUtility) + // ================================================================== + + @Test + public void testInvoke_formEncodedBodyEncodesKeysValuesAndNestedObjects() { + JsonObject body = new JsonObject(); + body.addProperty("name", "John Doe"); + JsonObject nested = new JsonObject(); + nested.addProperty("city", "New York"); + body.add("address", nested); + + String encoded = HttpUtility.formatJsonToFormEncodedString(body); + + Assert.assertTrue(encoded.contains("name=John+Doe")); + Assert.assertTrue("nested objects are flattened to key[subkey]", + encoded.contains("address%5Bcity%5D=New+York")); + } + + /** + * KNOWN GAP: with {@code application/x-www-form-urlencoded}, a 1-element JSON array silently + * loses its array-ness and is sent as a bare scalar. + */ + @Test + public void testInvoke_formEncodedSingleElementArrayLosesArrayness_knownGap() { + JsonObject body = new JsonObject(); + JsonArray items = new JsonArray(); + items.add("only-item"); + body.add("items", items); + + Assert.assertEquals("items=only-item", HttpUtility.formatJsonToFormEncodedString(body)); + } + + /** + * KNOWN GAP: with {@code application/x-www-form-urlencoded}, an array of 2+ elements blows up + * with a raw {@link IllegalStateException}. + */ + @Test + public void testInvoke_formEncodedMultiElementArrayThrowsIllegalState_knownGap() { + JsonObject body = new JsonObject(); + JsonArray items = new JsonArray(); + items.add("first"); + items.add("second"); + body.add("items", items); + + try { + HttpUtility.formatJsonToFormEncodedString(body); + Assert.fail("expected IllegalStateException for a multi-element array"); + } catch (IllegalStateException expected) { + Assert.assertTrue(expected.getMessage().contains("2")); + } + } + + /** + * KNOWN GAP: with {@code application/x-www-form-urlencoded}, a JSON null blows up with a raw + * {@link UnsupportedOperationException}. + */ + @Test + public void testInvoke_formEncodedJsonNullThrowsUnsupportedOperation_knownGap() { + JsonObject body = new JsonObject(); + body.add("maybe", JsonNull.INSTANCE); + + try { + HttpUtility.formatJsonToFormEncodedString(body); + Assert.fail("expected UnsupportedOperationException for a JSON null"); + } catch (UnsupportedOperationException expected) { + Assert.assertEquals("JsonNull", expected.getMessage()); + } + } + + /** + * KNOWN GAP: the multipart encoder shares {@code convertJsonToMap} and therefore has exactly + * the same array/null defects. + */ + @Test + public void testInvoke_multipartBodyHasTheSameArrayAndNullDefects_knownGap() { + JsonObject single = new JsonObject(); + JsonArray oneItem = new JsonArray(); + oneItem.add("only-item"); + single.add("items", oneItem); + Assert.assertTrue("array-ness is lost", + HttpUtility.formatJsonToMultiPartFormDataString(single, "bnd").contains("only-item")); + + JsonObject many = new JsonObject(); + JsonArray twoItems = new JsonArray(); + twoItems.add("first"); + twoItems.add("second"); + many.add("items", twoItems); + try { + HttpUtility.formatJsonToMultiPartFormDataString(many, "bnd"); + Assert.fail("expected IllegalStateException for a multi-element array"); + } catch (IllegalStateException expected) { + Assert.assertNotNull(expected.getMessage()); + } + + JsonObject withNull = new JsonObject(); + withNull.add("maybe", JsonNull.INSTANCE); + try { + HttpUtility.formatJsonToMultiPartFormDataString(withNull, "bnd"); + Assert.fail("expected UnsupportedOperationException for a JSON null"); + } catch (UnsupportedOperationException expected) { + Assert.assertEquals("JsonNull", expected.getMessage()); + } + } + + // ================================================================== + // invoke — transport + // ================================================================== + + /** + * KNOWN GAP: {@link RequestMethod#PATCH} can be built into a request but never reaches the + * wire — {@code HttpURLConnection.setRequestMethod} rejects it, surfacing as a + * {@link SkyflowException} wrapping a {@link ProtocolException}. + */ + @Test + public void testInvoke_patchMethodCannotBeSent_knownGap() { + ConnectionController controller = + new ConnectionController(connectionConfig("https://conn.example.com/api"), apiKeyCredentials()); + + try { + controller.invoke(InvokeConnectionRequest.builder().method(RequestMethod.PATCH).build()); + Assert.fail("PATCH should not be sendable"); + } catch (SkyflowException e) { + Assert.assertTrue("cause must be a ProtocolException, got: " + e.getCause(), + e.getCause() instanceof ProtocolException); + Assert.assertTrue(e.getCause().getMessage().contains("PATCH")); + } + } +} diff --git a/src/test/java/com/skyflow/SkyflowTests.java b/skyvault/src/test/java/com/skyflow/SkyflowTests.java similarity index 62% rename from src/test/java/com/skyflow/SkyflowTests.java rename to skyvault/src/test/java/com/skyflow/SkyflowTests.java index 983419a7..07386a5b 100644 --- a/src/test/java/com/skyflow/SkyflowTests.java +++ b/skyvault/src/test/java/com/skyflow/SkyflowTests.java @@ -1,8 +1,9 @@ package com.skyflow; -import com.skyflow.config.ConnectionConfig; import com.skyflow.config.Credentials; import com.skyflow.config.VaultConfig; +import com.skyflow.config.ConnectionConfig; +import com.skyflow.config.VaultConfig; import com.skyflow.enums.Env; import com.skyflow.enums.LogLevel; import com.skyflow.errors.ErrorCode; @@ -102,55 +103,6 @@ public void testAddingValidVaultConfigInSkyflowClient() { } } - @Test - public void testAddingExistingVaultConfigInSkyflowClient() { - try { - VaultConfig config = new VaultConfig(); - config.setVaultId(vaultID); - config.setClusterId(clusterID); - config.setEnv(Env.SANDBOX); - Skyflow skyflowClient = Skyflow.builder().build(); - skyflowClient.addVaultConfig(config).addVaultConfig(config); - Assert.fail(EXCEPTION_NOT_THROWN); - } catch (SkyflowException e) { - Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); - Assert.assertEquals(ErrorMessage.VaultIdAlreadyInConfigList.getMessage(), e.getMessage()); - } - } - - @Test - public void testUpdatingNonExistentVaultConfigInSkyflowBuilder() { - try { - VaultConfig config = new VaultConfig(); - config.setVaultId(vaultID); - config.setClusterId(clusterID); - config.setEnv(Env.SANDBOX); - Skyflow.builder().updateVaultConfig(config).build(); - Assert.fail(EXCEPTION_NOT_THROWN); - } catch (SkyflowException e) { - Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); - Assert.assertEquals(ErrorMessage.VaultIdNotInConfigList.getMessage(), e.getMessage()); - } - } - - @Test - public void testUpdatingNonExistentVaultConfigInSkyflowClient() { - try { - VaultConfig config = new VaultConfig(); - config.setVaultId(vaultID); - config.setClusterId(clusterID); - config.setEnv(Env.SANDBOX); - Skyflow skyflowClient = Skyflow.builder().build(); - skyflowClient.updateVaultConfig(config); - Assert.fail(EXCEPTION_NOT_THROWN); - } catch (SkyflowException e) { - Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); - Assert.assertEquals(ErrorMessage.VaultIdNotInConfigList.getMessage(), e.getMessage()); - } catch (Exception e) { - Assert.assertEquals(ErrorMessage.VaultIdNotInConfigList.getMessage(), e.getMessage()); - } - } - @Test public void testUpdatingValidVaultConfigInSkyflowClient() { try { @@ -173,114 +125,6 @@ public void testUpdatingValidVaultConfigInSkyflowClient() { } } - @Test - public void testUpdateVaultConfigNullCredentialsFallsBackToPrevious() { - try { - VaultConfig config = new VaultConfig(); - config.setVaultId(vaultID); - config.setClusterId(clusterID); - config.setEnv(Env.SANDBOX); - - Credentials creds = new Credentials(); - creds.setToken(token); - config.setCredentials(creds); - - Skyflow skyflowClient = Skyflow.builder().addVaultConfig(config).build(); - - // Update with null credentials — should retain previous credentials value - VaultConfig partialUpdate = new VaultConfig(); - partialUpdate.setVaultId(vaultID); - partialUpdate.setClusterId(clusterID); - skyflowClient.updateVaultConfig(partialUpdate); - Assert.assertNotNull(skyflowClient.getVaultConfig(vaultID).getCredentials()); - } catch (SkyflowException e) { - Assert.fail(INVALID_EXCEPTION_THROWN); - } - } - - @Test - public void testRemovingNonExistentVaultConfigInSkyflowBuilder() { - try { - Skyflow.builder().removeVaultConfig(vaultID).build(); - Assert.fail(EXCEPTION_NOT_THROWN); - } catch (SkyflowException e) { - Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); - Assert.assertEquals(ErrorMessage.VaultIdNotInConfigList.getMessage(), e.getMessage()); - } - } - - @Test - public void testRemovingNonExistentVaultConfigInSkyflowClient() { - try { - VaultConfig config = new VaultConfig(); - config.setVaultId(vaultID); - config.setClusterId(clusterID); - config.setEnv(Env.SANDBOX); - Skyflow skyflowClient = Skyflow.builder().addVaultConfig(config).build(); - skyflowClient.removeVaultConfig(vaultID); - } catch (SkyflowException e) { - Assert.fail(INVALID_EXCEPTION_THROWN); - } - } - - @Test - public void testRemovingValidVaultConfigInSkyflowClient() { - try { - Skyflow skyflowClient = Skyflow.builder().build(); - skyflowClient.removeVaultConfig(vaultID); - Assert.fail(EXCEPTION_NOT_THROWN); - } catch (SkyflowException e) { - Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); - Assert.assertEquals(ErrorMessage.VaultIdNotInConfigList.getMessage(), e.getMessage()); - } - } - - @Test - public void testGettingNonExistentVaultConfigInSkyflowClient() { - try { - Skyflow skyflowClient = Skyflow.builder().build(); - VaultConfig config = skyflowClient.getVaultConfig(vaultID); - Assert.assertNull(config); - } catch (Exception e) { - Assert.fail(INVALID_EXCEPTION_THROWN); - } - } - - @Test - public void testGettingAlreadyRemovedVaultFromEmptyConfigs() { - try { - Skyflow skyflowClient = Skyflow.builder().build(); - skyflowClient.vault(); - Assert.fail(EXCEPTION_NOT_THROWN); - } catch (SkyflowException e) { - Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); - Assert.assertEquals(ErrorMessage.VaultIdNotInConfigList.getMessage(), e.getMessage()); - } - } - - @Test - public void testGettingAlreadyRemovedVaultFromNonEmptyConfigs() { - try { - VaultConfig primaryConfig = new VaultConfig(); - primaryConfig.setVaultId(vaultID); - primaryConfig.setClusterId(clusterID); - primaryConfig.setEnv(Env.SANDBOX); - - VaultConfig secondaryConfig = new VaultConfig(); - secondaryConfig.setVaultId(vaultID + "123"); - secondaryConfig.setClusterId(clusterID); - secondaryConfig.setEnv(Env.SANDBOX); - Skyflow skyflowClient = Skyflow.builder().addVaultConfig(primaryConfig).addVaultConfig(secondaryConfig).build(); - skyflowClient.removeVaultConfig(vaultID); - skyflowClient.vault(vaultID); - Assert.fail(EXCEPTION_NOT_THROWN); - } catch (SkyflowException e) { - Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); - Assert.assertEquals(ErrorMessage.VaultIdNotInConfigList.getMessage(), e.getMessage()); - } - } - - @Test public void testAddingInvalidConnectionConfigInSkyflowBuilder() { try { @@ -460,60 +304,6 @@ public void testGettingNonExistentConnectionConfigInSkyflowClient() { } } - @Test - public void testAddingInvalidSkyflowCredentialsInSkyflowBuilder() { - try { - Credentials credentials = new Credentials(); - Skyflow.builder().addSkyflowCredentials(credentials).build(); - Assert.fail(EXCEPTION_NOT_THROWN); - } catch (SkyflowException e) { - Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); - Assert.assertEquals(ErrorMessage.NoTokenGenerationMeansPassed.getMessage(), e.getMessage()); - } - } - - @Test - public void testUpdatingValidSkyflowCredentialsInSkyflowClient() { - try { - VaultConfig vaultConfig = new VaultConfig(); - vaultConfig.setVaultId(vaultID); - vaultConfig.setClusterId(clusterID); - - ConnectionConfig connectionConfig = new ConnectionConfig(); - connectionConfig.setConnectionId(connectionID); - connectionConfig.setConnectionUrl(connectionURL); - - Credentials credentials = new Credentials(); - credentials.setToken(token); - Skyflow skyflowClient = Skyflow.builder().addVaultConfig(vaultConfig).addConnectionConfig(connectionConfig).build(); - skyflowClient.updateSkyflowCredentials(credentials); - } catch (SkyflowException e) { - Assert.fail(INVALID_EXCEPTION_THROWN); - } - } - - @Test - public void testDefaultLogLevel() { - try { - Skyflow skyflowClient = Skyflow.builder().setLogLevel(null).build(); - Assert.assertEquals(LogLevel.ERROR, skyflowClient.getLogLevel()); - } catch (Exception e) { - Assert.fail(INVALID_EXCEPTION_THROWN); - } - } - - @Test - public void testSetLogLevel() { - try { - Skyflow skyflowClient = Skyflow.builder().setLogLevel(LogLevel.INFO).build(); - Assert.assertEquals(LogLevel.INFO, skyflowClient.getLogLevel()); - skyflowClient.setLogLevel(LogLevel.WARN); - Assert.assertEquals(LogLevel.WARN, skyflowClient.getLogLevel()); - } catch (Exception e) { - Assert.fail(INVALID_EXCEPTION_THROWN); - } - } - @Test @SuppressWarnings("deprecation") public void testUpdateLogLevel() { @@ -686,36 +476,6 @@ public void testDetectMethodWithInvalidVaultId() { } } - @Test - public void testUpdateVaultConfig_withNewClusterIdAndCredentials_updatesAllFields() { - try { - VaultConfig config = new VaultConfig(); - config.setVaultId(vaultID); - config.setClusterId(clusterID); - config.setEnv(Env.DEV); - Credentials creds = new Credentials(); - creds.setToken(token); - config.setCredentials(creds); - Skyflow skyflowClient = Skyflow.builder().addVaultConfig(config).build(); - - // Update with a new non-null clusterId and new non-null credentials — covers - // the non-null (true) branches for all three ternaries in findAndUpdateVaultConfig - Credentials newCreds = new Credentials(); - newCreds.setToken("updated-token-value"); - VaultConfig update = new VaultConfig(); - update.setVaultId(vaultID); - update.setClusterId(newClusterID); - update.setEnv(Env.PROD); - update.setCredentials(newCreds); - skyflowClient.updateVaultConfig(update); - Assert.assertEquals(newClusterID, skyflowClient.getVaultConfig(vaultID).getClusterId()); - Assert.assertEquals(Env.PROD, skyflowClient.getVaultConfig(vaultID).getEnv()); - Assert.assertEquals("updated-token-value", skyflowClient.getVaultConfig(vaultID).getCredentials().getToken()); - } catch (SkyflowException e) { - Assert.fail(INVALID_EXCEPTION_THROWN); - } - } - @Test public void testUpdateConnectionConfig_withNewCredentials_updatesCredentials() { try { @@ -743,75 +503,6 @@ public void testUpdateConnectionConfig_withNewCredentials_updatesCredentials() { } } - @Test - public void testUpdateVaultConfig_withNullEnv_fallsBackToPreviousEnv() { - // VaultConfig's constructor defaults env=PROD so getEnv() is never null via normal API. - // Use an anonymous subclass to make getEnv() return null, exercising the false branch - // of `vaultConfig.getEnv() != null` in findAndUpdateVaultConfig. - try { - Credentials creds = new Credentials(); - creds.setToken(token); - VaultConfig initial = new VaultConfig(); - initial.setVaultId(vaultID); - initial.setClusterId(clusterID); - initial.setEnv(Env.SANDBOX); - initial.setCredentials(creds); - Skyflow skyflowClient = Skyflow.builder().addVaultConfig(initial).build(); - - VaultConfig updateWithNullEnv = new VaultConfig() { - @Override public Env getEnv() { return null; } - }; - updateWithNullEnv.setVaultId(vaultID); - updateWithNullEnv.setClusterId(clusterID); - updateWithNullEnv.setCredentials(creds); - - skyflowClient.updateVaultConfig(updateWithNullEnv); - // env falls back to previous (SANDBOX) - Assert.assertEquals(Env.SANDBOX, skyflowClient.getVaultConfig(vaultID).getEnv()); - } catch (SkyflowException e) { - Assert.fail(INVALID_EXCEPTION_THROWN); - } - } - - @Test - public void testFindAndUpdateVaultConfig_withNullClusterId_fallsBackToPreviousClusterId() { - // Validation enforces non-null clusterId, so the false branch of - // `vaultConfig.getClusterId() != null` in findAndUpdateVaultConfig is unreachable - // via the normal flow. Call the private method directly via reflection. - try { - Credentials creds = new Credentials(); - creds.setToken(token); - VaultConfig initial = new VaultConfig(); - initial.setVaultId(vaultID); - initial.setClusterId(clusterID); - initial.setEnv(Env.DEV); - initial.setCredentials(creds); - Skyflow skyflowClient = Skyflow.builder().addVaultConfig(initial).build(); - - java.lang.reflect.Field builderField = Skyflow.class.getDeclaredField("builder"); - builderField.setAccessible(true); - Object builder = builderField.get(skyflowClient); - - VaultConfig nullClusterConfig = new VaultConfig(); - nullClusterConfig.setVaultId(vaultID); - // Override clusterId field to null via reflection (setter enforces non-null) - java.lang.reflect.Field clusterIdField = VaultConfig.class.getDeclaredField("clusterId"); - clusterIdField.setAccessible(true); - clusterIdField.set(nullClusterConfig, null); - - java.lang.reflect.Method method = builder.getClass().getDeclaredMethod( - "findAndUpdateVaultConfig", VaultConfig.class); - method.setAccessible(true); - VaultConfig result = (VaultConfig) method.invoke(builder, nullClusterConfig); - - Assert.assertEquals(clusterID, result.getClusterId()); - } catch (SkyflowException e) { - Assert.fail(INVALID_EXCEPTION_THROWN); - } catch (Exception e) { - Assert.fail("Reflection failed: " + e.getMessage()); - } - } - @Test public void testFindAndUpdateConnectionConfig_withNullConnectionUrl_fallsBackToPreviousUrl() { // `findAndUpdateConnectionConfig` has a ternary for connectionUrl that falls back diff --git a/src/test/java/com/skyflow/VaultClientTests.java b/skyvault/src/test/java/com/skyflow/VaultClientTests.java similarity index 90% rename from src/test/java/com/skyflow/VaultClientTests.java rename to skyvault/src/test/java/com/skyflow/VaultClientTests.java index d98f5964..4c040ad0 100644 --- a/src/test/java/com/skyflow/VaultClientTests.java +++ b/skyvault/src/test/java/com/skyflow/VaultClientTests.java @@ -1,12 +1,15 @@ package com.skyflow; +import com.skyflow.config.VaultConfig; +import com.skyflow.config.BaseCredentials; import com.skyflow.config.Credentials; import com.skyflow.config.VaultConfig; import com.skyflow.enums.*; import com.skyflow.errors.ErrorCode; import com.skyflow.errors.SkyflowException; -import com.skyflow.generated.rest.resources.files.FilesClient; import com.skyflow.generated.rest.resources.files.requests.*; +import com.skyflow.generated.rest.resources.files.FilesClient; +import com.skyflow.generated.rest.types.*; import com.skyflow.generated.rest.resources.query.QueryClient; import com.skyflow.generated.rest.resources.records.RecordsClient; import com.skyflow.generated.rest.resources.records.requests.RecordServiceBatchOperationBody; @@ -18,7 +21,6 @@ import com.skyflow.generated.rest.resources.tokens.TokensClient; import com.skyflow.generated.rest.resources.tokens.requests.V1DetokenizePayload; import com.skyflow.generated.rest.resources.tokens.requests.V1TokenizePayload; -import com.skyflow.generated.rest.types.*; import com.skyflow.vault.data.InsertRequest; import com.skyflow.vault.data.UpdateRequest; import com.skyflow.vault.detect.*; @@ -707,7 +709,7 @@ public void testGetDeidentifyTextFileRequest() { String vaultId = "vault123"; String base64Content = "base64string"; - com.skyflow.generated.rest.resources.files.requests.DeidentifyFileRequestDeidentifyText textRequest = + DeidentifyFileRequestDeidentifyText textRequest = vaultClient.getDeidentifyTextFileRequest(request, vaultId, base64Content); Assert.assertEquals(vaultId, textRequest.getVaultId()); @@ -899,44 +901,16 @@ public void testMapAudioDataFormat_invalid() throws Exception { } } - @Test - public void testPrioritiseCredentials_VaultConfigCredentials() throws Exception { - Credentials creds = new Credentials(); - creds.setApiKey("test_api_key"); - vaultConfig.setCredentials(creds); - - java.lang.reflect.Method method = VaultClient.class.getDeclaredMethod("prioritiseCredentials"); - method.setAccessible(true); - method.invoke(vaultClient); - - Assert.assertEquals(creds, getPrivateField(vaultClient, "finalCredentials")); - } - - @Test - public void testPrioritiseCredentials_CommonCredentials() throws Exception { - vaultConfig.setCredentials(null); - Credentials creds = new Credentials(); - creds.setApiKey("common_api_key"); - setPrivateField(vaultClient, "commonCredentials", creds); - - java.lang.reflect.Method method = VaultClient.class.getDeclaredMethod("prioritiseCredentials"); - method.setAccessible(true); - method.invoke(vaultClient); - - Assert.assertEquals(creds, getPrivateField(vaultClient, "finalCredentials")); - } - - // Helper methods for reflection field access - private Object getPrivateField(Object obj, String fieldName) throws Exception { - java.lang.reflect.Field field = obj.getClass().getDeclaredField(fieldName); - field.setAccessible(true); - return field.get(obj); - } - - private void setPrivateField(Object obj, String fieldName, Object value) throws Exception { - java.lang.reflect.Field field = obj.getClass().getDeclaredField(fieldName); - field.setAccessible(true); - field.set(obj, value); + // Fields moved to base classes (e.g. common.BaseVaultClient) aren't found by + // getDeclaredField on the subclass, so walk up the hierarchy. + private java.lang.reflect.Field findDeclaredField(Class clazz, String fieldName) throws NoSuchFieldException { + for (Class current = clazz; current != null; current = current.getSuperclass()) { + try { + return current.getDeclaredField(fieldName); + } catch (NoSuchFieldException ignored) { + } + } + throw new NoSuchFieldException(fieldName); } @Test @@ -956,31 +930,6 @@ public void testGetFileForFileUpload_withFileObject() { } } - @Test - public void testSetBearerToken_validNonExpiredToken_reusesToken() { - try { - // far-future JWT: header.payload.sig where payload base64 decodes to {"exp":9999999999} - Credentials creds = new Credentials(); - creds.setToken("x.eyJleHAiOjk5OTk5OTk5OTl9.y"); - VaultConfig config = new VaultConfig(); - config.setVaultId(vaultID); - config.setClusterId(clusterID); - config.setEnv(com.skyflow.enums.Env.DEV); - config.setCredentials(creds); - VaultClient freshClient = new VaultClient(config, null); - - // First call: token=null → generates from creds.getToken() - freshClient.setBearerToken(); - Assert.assertEquals("x.eyJleHAiOjk5OTk5OTk5OTl9.y", getPrivateField(freshClient, "token")); - - // Second call: token valid, not expired → REUSE_BEARER_TOKEN else branch - freshClient.setBearerToken(); - Assert.assertEquals("x.eyJleHAiOjk5OTk5OTk5OTl9.y", getPrivateField(freshClient, "token")); - } catch (Exception e) { - Assert.fail("Should not have thrown: " + e.getMessage()); - } - } - @Test public void testGetDeidentifyImageRequest_withMaskingMethod() { try { @@ -1042,52 +991,6 @@ public void testGetDeIdentifyTextResponse_withEntityScores() { Assert.assertEquals(0.95, result.getEntities().get(0).getScores().get("EMAIL_ADDRESS"), 0.001); } - @Test - public void testPrioritiseCredentials_credentialChange_resetsTokenAndApiKey() { - try { - Credentials credentialsA = new Credentials(); - credentialsA.setToken("x.eyJleHAiOjk5OTk5OTk5OTl9.y"); - VaultConfig config = new VaultConfig(); - config.setVaultId("isolated-vault-change"); - config.setClusterId(clusterID); - config.setEnv(com.skyflow.enums.Env.DEV); - config.setCredentials(credentialsA); - VaultClient freshClient = new VaultClient(config, null); - - freshClient.updateVaultConfig(); // sets finalCredentials = credentialsA - setPrivateField(freshClient, "token", "cached-token"); // simulate prior auth - - Credentials credentialsB = new Credentials(); - credentialsB.setToken("other-token"); - config.setCredentials(credentialsB); - - freshClient.updateVaultConfig(); // original=A, new=B → different → reset token/apiKey - Assert.assertNull(getPrivateField(freshClient, "token")); - Assert.assertNull(getPrivateField(freshClient, "apiKey")); - } catch (Exception e) { - Assert.fail("Should not have thrown: " + e.getMessage()); - } - } - - @Test - public void testSetBearerToken_noCredentials_throwsEmptyCredentials() { - VaultConfig config = new VaultConfig(); - config.setVaultId("isolated-vault-nocreds"); - config.setClusterId(clusterID); - config.setEnv(com.skyflow.enums.Env.DEV); - // No credentials — will hit dotenv path → DotenvException → SkyflowException(EmptyCredentials) - VaultClient freshClient = new VaultClient(config, null); - try { - freshClient.setBearerToken(); - Assert.fail("Should have thrown SkyflowException"); - } catch (SkyflowException e) { - // SkyflowException expected — message varies by environment - // (EmptyCredentials when no .env, or credential error when .env provides creds) - } catch (Exception e) { - Assert.fail("Expected SkyflowException, got: " + e.getClass().getName() + ": " + e.getMessage()); - } - } - @Test public void testUpdateExecutorInHTTP_interceptorAddsAuthorizationHeader() { try { @@ -1103,7 +1006,7 @@ public void testUpdateExecutorInHTTP_interceptorAddsAuthorizationHeader() { freshClient.setBearerToken(); // triggers updateExecutorInHTTP → creates sharedHttpClient with interceptor // Access sharedHttpClient via reflection - java.lang.reflect.Field field = VaultClient.class.getDeclaredField("sharedHttpClient"); + java.lang.reflect.Field field = findDeclaredField(VaultClient.class, "sharedHttpClient"); field.setAccessible(true); OkHttpClient httpClient = (OkHttpClient) field.get(freshClient); Assert.assertNotNull(httpClient); diff --git a/src/test/java/com/skyflow/config/ConnectionConfigTests.java b/skyvault/src/test/java/com/skyflow/config/ConnectionConfigTests.java similarity index 100% rename from src/test/java/com/skyflow/config/ConnectionConfigTests.java rename to skyvault/src/test/java/com/skyflow/config/ConnectionConfigTests.java diff --git a/src/test/java/com/skyflow/config/ManagementConfigTest.java b/skyvault/src/test/java/com/skyflow/config/ManagementConfigTest.java similarity index 100% rename from src/test/java/com/skyflow/config/ManagementConfigTest.java rename to skyvault/src/test/java/com/skyflow/config/ManagementConfigTest.java diff --git a/src/test/java/com/skyflow/config/VaultConfigTests.java b/skyvault/src/test/java/com/skyflow/config/VaultConfigTests.java similarity index 100% rename from src/test/java/com/skyflow/config/VaultConfigTests.java rename to skyvault/src/test/java/com/skyflow/config/VaultConfigTests.java diff --git a/src/test/java/com/skyflow/enums/DeidentifyFileStatusTest.java b/skyvault/src/test/java/com/skyflow/enums/DeidentifyFileStatusTest.java similarity index 100% rename from src/test/java/com/skyflow/enums/DeidentifyFileStatusTest.java rename to skyvault/src/test/java/com/skyflow/enums/DeidentifyFileStatusTest.java diff --git a/src/test/java/com/skyflow/enums/DetectEntitiesTest.java b/skyvault/src/test/java/com/skyflow/enums/DetectEntitiesTest.java similarity index 100% rename from src/test/java/com/skyflow/enums/DetectEntitiesTest.java rename to skyvault/src/test/java/com/skyflow/enums/DetectEntitiesTest.java diff --git a/src/test/java/com/skyflow/enums/DetectOutputTranscriptionsTest.java b/skyvault/src/test/java/com/skyflow/enums/DetectOutputTranscriptionsTest.java similarity index 100% rename from src/test/java/com/skyflow/enums/DetectOutputTranscriptionsTest.java rename to skyvault/src/test/java/com/skyflow/enums/DetectOutputTranscriptionsTest.java diff --git a/src/test/java/com/skyflow/enums/MaskingMethodTest.java b/skyvault/src/test/java/com/skyflow/enums/MaskingMethodTest.java similarity index 100% rename from src/test/java/com/skyflow/enums/MaskingMethodTest.java rename to skyvault/src/test/java/com/skyflow/enums/MaskingMethodTest.java diff --git a/src/test/java/com/skyflow/enums/TokenModeTest.java b/skyvault/src/test/java/com/skyflow/enums/TokenModeTest.java similarity index 98% rename from src/test/java/com/skyflow/enums/TokenModeTest.java rename to skyvault/src/test/java/com/skyflow/enums/TokenModeTest.java index 72236a16..b65a72cb 100644 --- a/src/test/java/com/skyflow/enums/TokenModeTest.java +++ b/skyvault/src/test/java/com/skyflow/enums/TokenModeTest.java @@ -1,6 +1,5 @@ package com.skyflow.enums; -import com.skyflow.enums.LogLevel; import com.skyflow.generated.rest.types.V1Byot; import com.skyflow.logs.InfoLogs; import com.skyflow.utils.logger.LogUtil; diff --git a/src/test/java/com/skyflow/enums/TokenTypeTest.java b/skyvault/src/test/java/com/skyflow/enums/TokenTypeTest.java similarity index 100% rename from src/test/java/com/skyflow/enums/TokenTypeTest.java rename to skyvault/src/test/java/com/skyflow/enums/TokenTypeTest.java diff --git a/src/test/java/com/skyflow/utils/HttpUtilityTests.java b/skyvault/src/test/java/com/skyflow/utils/HttpUtilityTests.java similarity index 100% rename from src/test/java/com/skyflow/utils/HttpUtilityTests.java rename to skyvault/src/test/java/com/skyflow/utils/HttpUtilityTests.java diff --git a/skyvault/src/test/java/com/skyflow/utils/UtilsTests.java b/skyvault/src/test/java/com/skyflow/utils/UtilsTests.java new file mode 100644 index 00000000..d1e1c725 --- /dev/null +++ b/skyvault/src/test/java/com/skyflow/utils/UtilsTests.java @@ -0,0 +1,94 @@ +package com.skyflow.utils; + +import com.google.gson.JsonObject; +import com.skyflow.config.ConnectionConfig; +import com.skyflow.enums.RequestMethod; +import com.skyflow.vault.connection.InvokeConnectionRequest; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +public class UtilsTests { + private static final String INVALID_EXCEPTION_THROWN = "Should not have thrown any exception"; + private static String connectionId = null; + private static String connectionUrl = null; + private static Map queryParams; + private static Map pathParams; + private static Map requestHeaders; + + @BeforeClass + public static void setup() { + connectionId = "test_connection_id"; + connectionUrl = "https://test.connection.url"; + pathParams = new HashMap<>(); + queryParams = new HashMap<>(); + requestHeaders = new HashMap<>(); + } + + @Test + public void testConstructConnectionURL() { + try { + queryParams.put("query_param", "value"); + pathParams.put("path_param", "value"); + + ConnectionConfig connectionConfig = new ConnectionConfig(); + connectionConfig.setConnectionId(connectionId); + connectionConfig.setConnectionUrl(connectionUrl); + + InvokeConnectionRequest request = InvokeConnectionRequest.builder() + .method(RequestMethod.POST).pathParams(pathParams).queryParams(queryParams).build(); + String filledUrl = Utils.constructConnectionURL(connectionConfig, request); + Assert.assertEquals(connectionUrl + "?" + "query_param=value", filledUrl); + } catch (Exception e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testConstructConnectionHeaderMap() { + try { + requestHeaders.put("HEADER", "value"); + Map headers = Utils.constructConnectionHeadersMap(requestHeaders); + Assert.assertEquals(1, headers.size()); + Assert.assertTrue(headers.containsKey("header")); + Assert.assertFalse(headers.containsKey("HEADER")); + Assert.assertEquals("value", headers.get("header")); + } catch (Exception e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testGetMetrics() { + try { + JsonObject metrics = Utils.getMetrics(); + Assert.assertNotNull(metrics.get(Constants.SDK_METRIC_NAME_VERSION)); + Assert.assertNotNull(metrics.get(Constants.SDK_METRIC_CLIENT_DEVICE_MODEL)); + Assert.assertNotNull(metrics.get(Constants.SDK_METRIC_CLIENT_OS_DETAILS)); + Assert.assertNotNull(metrics.get(Constants.SDK_METRIC_RUNTIME_DETAILS)); + } catch (Exception e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } + + @Test + public void testGetMetricsWithException() { + try { + // Clearing System Properties explicitly to throw exception + System.clearProperty("os.name"); + System.clearProperty("os.version"); + System.clearProperty("java.version"); + + JsonObject metrics = Utils.getMetrics(); + Assert.assertEquals("skyflow-java@" + Constants.SDK_VERSION, metrics.get(Constants.SDK_METRIC_NAME_VERSION).getAsString()); + Assert.assertEquals("Java@", metrics.get(Constants.SDK_METRIC_RUNTIME_DETAILS).getAsString()); + Assert.assertTrue(metrics.get(Constants.SDK_METRIC_CLIENT_DEVICE_MODEL).getAsString().isEmpty()); + Assert.assertTrue(metrics.get(Constants.SDK_METRIC_CLIENT_OS_DETAILS).getAsString().isEmpty()); + } catch (Exception e) { + Assert.fail(INVALID_EXCEPTION_THROWN); + } + } +} diff --git a/src/test/java/com/skyflow/vault/BinAuditTests.java b/skyvault/src/test/java/com/skyflow/vault/BinAuditTests.java similarity index 100% rename from src/test/java/com/skyflow/vault/BinAuditTests.java rename to skyvault/src/test/java/com/skyflow/vault/BinAuditTests.java diff --git a/src/test/java/com/skyflow/vault/connection/InvokeConnectionTests.java b/skyvault/src/test/java/com/skyflow/vault/connection/InvokeConnectionTests.java similarity index 100% rename from src/test/java/com/skyflow/vault/connection/InvokeConnectionTests.java rename to skyvault/src/test/java/com/skyflow/vault/connection/InvokeConnectionTests.java diff --git a/src/test/java/com/skyflow/vault/controller/ConnectionControllerTests.java b/skyvault/src/test/java/com/skyflow/vault/controller/ConnectionControllerTests.java similarity index 100% rename from src/test/java/com/skyflow/vault/controller/ConnectionControllerTests.java rename to skyvault/src/test/java/com/skyflow/vault/controller/ConnectionControllerTests.java diff --git a/skyvault/src/test/java/com/skyflow/vault/controller/ConnectionRequestFidelityTests.java b/skyvault/src/test/java/com/skyflow/vault/controller/ConnectionRequestFidelityTests.java new file mode 100644 index 00000000..b44c3310 --- /dev/null +++ b/skyvault/src/test/java/com/skyflow/vault/controller/ConnectionRequestFidelityTests.java @@ -0,0 +1,267 @@ +package com.skyflow.vault.controller; + +import com.google.gson.JsonObject; +import com.skyflow.config.ConnectionConfig; +import com.skyflow.config.Credentials; +import com.skyflow.enums.RequestMethod; +import com.skyflow.utils.Constants; +import com.skyflow.utils.HttpUtility; +import com.skyflow.vault.connection.InvokeConnectionRequest; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import java.net.URL; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; + +/** + * Request-fidelity tests for {@code ConnectionController.invoke} at the transport boundary: every + * value the user sets on {@link InvokeConnectionRequest} is captured as it is handed to + * {@link HttpUtility#sendRequest}. + * + *

Tests suffixed {@code _knownGap} pin confirmed defects and assert the CURRENT behaviour. + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({HttpUtility.class}) +public class ConnectionRequestFidelityTests { + + private static final String API_KEY = "sky-ab123-abcd1234cdef1234abcd4321cdef4321"; // gitleaks:allow + private static final String CONNECTION_URL = "https://conn.example.com/api/{resource}/details"; + private static final String NON_ASCII_NAME = "日本語 テスト"; + + private static ConnectionConfig connectionConfig; + private static Credentials credentials; + private ConnectionController controller; + + @BeforeClass + public static void setupClass() { + credentials = new Credentials(); + credentials.setApiKey(API_KEY); + + connectionConfig = new ConnectionConfig(); + connectionConfig.setConnectionId("conn123"); + connectionConfig.setConnectionUrl(CONNECTION_URL); + connectionConfig.setCredentials(credentials); + } + + @Before + public void setup() throws Exception { + controller = new ConnectionController(connectionConfig, credentials); + PowerMockito.mockStatic(HttpUtility.class); + when(HttpUtility.sendRequest(anyString(), any(URL.class), any(), any())).thenReturn("{}"); + when(HttpUtility.getRequestID()).thenReturn("req-fidelity-1"); + } + + private static final class Captured { + private String method; + private URL url; + private JsonObject body; + private Map headers; + } + + @SuppressWarnings("unchecked") + private static Captured capture() throws Exception { + ArgumentCaptor methodCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor urlCaptor = ArgumentCaptor.forClass(URL.class); + ArgumentCaptor bodyCaptor = ArgumentCaptor.forClass(JsonObject.class); + ArgumentCaptor headersCaptor = ArgumentCaptor.forClass(Map.class); + + PowerMockito.verifyStatic(HttpUtility.class); + HttpUtility.sendRequest(methodCaptor.capture(), urlCaptor.capture(), + bodyCaptor.capture(), headersCaptor.capture()); + + Captured captured = new Captured(); + captured.method = methodCaptor.getValue(); + captured.url = urlCaptor.getValue(); + captured.body = bodyCaptor.getValue(); + captured.headers = headersCaptor.getValue(); + return captured; + } + + @Test + public void testInvoke_methodPathParamsQueryParamsHeadersAndBodyAllReachTransport() throws Exception { + Map pathParams = new LinkedHashMap<>(); + pathParams.put("resource", "cards"); + + Map queryParams = new LinkedHashMap<>(); + queryParams.put("limit", "10"); + queryParams.put("offset", "20"); + + Map requestHeaders = new LinkedHashMap<>(); + requestHeaders.put("X-Custom-Header", NON_ASCII_NAME); + requestHeaders.put("content-type", "application/json"); + + Map nested = new LinkedHashMap<>(); + nested.put("city", "北京市 朝阳区"); + nested.put("zip", "100000"); + + Map requestBody = new LinkedHashMap<>(); + requestBody.put("name", NON_ASCII_NAME); + requestBody.put("age", 42); + requestBody.put("active", true); + requestBody.put("address", nested); + + controller.invoke(InvokeConnectionRequest.builder() + .method(RequestMethod.PUT) + .pathParams(pathParams) + .queryParams(queryParams) + .requestHeaders(requestHeaders) + .requestBody(requestBody) + .build()); + + Captured captured = capture(); + + Assert.assertEquals("PUT", captured.method); + Assert.assertEquals("https://conn.example.com/api/cards/details?limit=10&offset=20", + captured.url.toString()); + + Assert.assertEquals(NON_ASCII_NAME, captured.headers.get("x-custom-header")); + Assert.assertEquals("application/json", captured.headers.get("content-type")); + Assert.assertEquals("auth header carries the api key", + API_KEY, captured.headers.get(Constants.SDK_AUTH_HEADER_KEY)); + + Assert.assertEquals(NON_ASCII_NAME, captured.body.get("name").getAsString()); + Assert.assertEquals(42, captured.body.get("age").getAsInt()); + Assert.assertTrue(captured.body.get("active").getAsBoolean()); + Assert.assertEquals("北京市 朝阳区", + captured.body.getAsJsonObject("address").get("city").getAsString()); + Assert.assertEquals("100000", + captured.body.getAsJsonObject("address").get("zip").getAsString()); + } + + @Test + public void testInvoke_defaultMethodIsPost() throws Exception { + controller.invoke(InvokeConnectionRequest.builder().build()); + Assert.assertEquals("POST", capture().method); + } + + @Test + public void testInvoke_eachRequestMethodIsForwardedByName() throws Exception { + for (RequestMethod method : new RequestMethod[]{ + RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT, RequestMethod.DELETE}) { + ConnectionController freshController = new ConnectionController(connectionConfig, credentials); + PowerMockito.mockStatic(HttpUtility.class); + when(HttpUtility.sendRequest(anyString(), any(URL.class), any(), any())).thenReturn("{}"); + when(HttpUtility.getRequestID()).thenReturn("req-fidelity-1"); + + freshController.invoke(InvokeConnectionRequest.builder().method(method).build()); + Assert.assertEquals(method.name(), capture().method); + } + } + + /** + * NEW FINDING (not one of the 11 audited gaps): a non-object {@code requestBody} — a String, + * a number, a List — never reaches the transport. {@code Validations} calls + * {@code gson.toJsonTree(body).getAsJsonObject()} and blows up with a raw + * {@link IllegalStateException} (not a {@code SkyflowException}), which also makes + * {@code ConnectionController.convertObjectToJson}'s "wrap scalars under a value key" branch + * unreachable for scalars. + */ + @Test + public void testInvoke_scalarRequestBodyThrowsRawIllegalStateException_knownGap() throws Exception { + for (Object body : new Object[]{"a plain string", 42, java.util.Arrays.asList("a", "b")}) { + try { + controller.invoke(InvokeConnectionRequest.builder() + .method(RequestMethod.POST) + .requestBody(body) + .build()); + Assert.fail("expected an exception for a non-object request body: " + body); + } catch (IllegalStateException expected) { + Assert.assertTrue(expected.getMessage().contains("Not a JSON Object")); + } + } + PowerMockito.verifyStatic(HttpUtility.class, org.mockito.Mockito.never()); + HttpUtility.sendRequest(anyString(), any(URL.class), any(), any()); + } + + /** + * KNOWN GAP: a user-supplied {@code sky-metadata} header is unconditionally overwritten by the + * SDK's own metrics blob. + */ + @Test + public void testInvoke_userSuppliedSkyMetadataHeaderIsOverwritten_knownGap() throws Exception { + Map requestHeaders = new HashMap<>(); + requestHeaders.put(Constants.SDK_METRICS_HEADER_KEY, "user-supplied-value"); + + controller.invoke(InvokeConnectionRequest.builder() + .method(RequestMethod.GET) + .requestHeaders(requestHeaders) + .build()); + + String sent = capture().headers.get(Constants.SDK_METRICS_HEADER_KEY); + Assert.assertNotEquals("user value is discarded", "user-supplied-value", sent); + Assert.assertTrue("replaced by the SDK metrics blob", + sent.contains(Constants.SDK_METRIC_NAME_VERSION)); + } + + /** + * KNOWN GAP: two request headers differing only in case collapse into one entry before they + * reach the transport (last writer wins), so one of the user's headers is lost. + */ + @Test + public void testInvoke_headersDifferingOnlyInCaseCollapse_knownGap() throws Exception { + Map requestHeaders = new LinkedHashMap<>(); + requestHeaders.put("X-Trace", "first-value"); + requestHeaders.put("x-trace", "second-value"); + + controller.invoke(InvokeConnectionRequest.builder() + .method(RequestMethod.GET) + .requestHeaders(requestHeaders) + .build()); + + Map headers = capture().headers; + Assert.assertEquals("second-value", headers.get("x-trace")); + Assert.assertFalse("the original-cased key is gone", headers.containsKey("X-Trace")); + } + + /** + * KNOWN GAP: query-param values are not percent-encoded, so a value containing {@code &} and + * {@code =} re-partitions the query string on the wire. + */ + @Test + public void testInvoke_queryParamValuesReachTransportUnencoded_knownGap() throws Exception { + Map pathParams = new LinkedHashMap<>(); + pathParams.put("resource", "cards"); + Map queryParams = new LinkedHashMap<>(); + queryParams.put("q", "a&b=c"); + + controller.invoke(InvokeConnectionRequest.builder() + .method(RequestMethod.GET) + .pathParams(pathParams) + .queryParams(queryParams) + .build()); + + URL url = capture().url; + Assert.assertEquals("q=a&b=c", url.getQuery()); + Assert.assertEquals("one user param became two wire params", 2, url.getQuery().split("&").length); + } + + /** + * KNOWN GAP: path-param values are not percent-encoded, so a {@code /} in the value changes the + * request path on the wire. + */ + @Test + public void testInvoke_pathParamValuesReachTransportUnencoded_knownGap() throws Exception { + Map pathParams = new LinkedHashMap<>(); + pathParams.put("resource", "cards/123"); + + controller.invoke(InvokeConnectionRequest.builder() + .method(RequestMethod.GET) + .pathParams(pathParams) + .build()); + + Assert.assertEquals("/api/cards/123/details", capture().url.getPath()); + } +} diff --git a/src/test/java/com/skyflow/vault/controller/DetectControllerFileTests.java b/skyvault/src/test/java/com/skyflow/vault/controller/DetectControllerFileTests.java similarity index 99% rename from src/test/java/com/skyflow/vault/controller/DetectControllerFileTests.java rename to skyvault/src/test/java/com/skyflow/vault/controller/DetectControllerFileTests.java index 2b8379bc..f0347eeb 100644 --- a/src/test/java/com/skyflow/vault/controller/DetectControllerFileTests.java +++ b/skyvault/src/test/java/com/skyflow/vault/controller/DetectControllerFileTests.java @@ -1,5 +1,6 @@ package com.skyflow.vault.controller; +import com.skyflow.config.BaseVaultConfig; import com.skyflow.config.Credentials; import com.skyflow.config.VaultConfig; import com.skyflow.errors.ErrorCode; diff --git a/src/test/java/com/skyflow/vault/controller/DetectControllerTests.java b/skyvault/src/test/java/com/skyflow/vault/controller/DetectControllerTests.java similarity index 100% rename from src/test/java/com/skyflow/vault/controller/DetectControllerTests.java rename to skyvault/src/test/java/com/skyflow/vault/controller/DetectControllerTests.java diff --git a/src/test/java/com/skyflow/vault/controller/VaultControllerTests.java b/skyvault/src/test/java/com/skyflow/vault/controller/VaultControllerTests.java similarity index 100% rename from src/test/java/com/skyflow/vault/controller/VaultControllerTests.java rename to skyvault/src/test/java/com/skyflow/vault/controller/VaultControllerTests.java diff --git a/src/test/java/com/skyflow/vault/data/DeleteTests.java b/skyvault/src/test/java/com/skyflow/vault/data/DeleteTests.java similarity index 98% rename from src/test/java/com/skyflow/vault/data/DeleteTests.java rename to skyvault/src/test/java/com/skyflow/vault/data/DeleteTests.java index befc3d26..ae294f24 100644 --- a/src/test/java/com/skyflow/vault/data/DeleteTests.java +++ b/skyvault/src/test/java/com/skyflow/vault/data/DeleteTests.java @@ -1,8 +1,8 @@ package com.skyflow.vault.data; import com.skyflow.Skyflow; +import com.skyflow.config.BaseVaultConfig; import com.skyflow.config.Credentials; -import com.skyflow.config.VaultConfig; import com.skyflow.enums.Env; import com.skyflow.errors.ErrorCode; import com.skyflow.errors.ErrorMessage; @@ -35,7 +35,7 @@ public static void setup() { Credentials credentials = new Credentials(); credentials.setToken("valid-token"); - VaultConfig vaultConfig = new VaultConfig(); + BaseVaultConfig vaultConfig = new BaseVaultConfig(); vaultConfig.setVaultId(vaultID); vaultConfig.setClusterId(clusterID); vaultConfig.setEnv(Env.DEV); diff --git a/src/test/java/com/skyflow/vault/data/FileUploadTests.java b/skyvault/src/test/java/com/skyflow/vault/data/FileUploadTests.java similarity index 100% rename from src/test/java/com/skyflow/vault/data/FileUploadTests.java rename to skyvault/src/test/java/com/skyflow/vault/data/FileUploadTests.java diff --git a/src/test/java/com/skyflow/vault/data/GetTests.java b/skyvault/src/test/java/com/skyflow/vault/data/GetTests.java similarity index 100% rename from src/test/java/com/skyflow/vault/data/GetTests.java rename to skyvault/src/test/java/com/skyflow/vault/data/GetTests.java index e62c605f..bc6f24ef 100644 --- a/src/test/java/com/skyflow/vault/data/GetTests.java +++ b/skyvault/src/test/java/com/skyflow/vault/data/GetTests.java @@ -1,8 +1,8 @@ package com.skyflow.vault.data; import com.skyflow.Skyflow; -import com.skyflow.config.Credentials; import com.skyflow.config.VaultConfig; +import com.skyflow.config.Credentials; import com.skyflow.enums.Env; import com.skyflow.enums.RedactionType; import com.skyflow.errors.ErrorCode; diff --git a/src/test/java/com/skyflow/vault/data/InsertTests.java b/skyvault/src/test/java/com/skyflow/vault/data/InsertTests.java similarity index 100% rename from src/test/java/com/skyflow/vault/data/InsertTests.java rename to skyvault/src/test/java/com/skyflow/vault/data/InsertTests.java index 030bc5ea..0ba6c7f8 100644 --- a/src/test/java/com/skyflow/vault/data/InsertTests.java +++ b/skyvault/src/test/java/com/skyflow/vault/data/InsertTests.java @@ -1,8 +1,8 @@ package com.skyflow.vault.data; import com.skyflow.Skyflow; -import com.skyflow.config.Credentials; import com.skyflow.config.VaultConfig; +import com.skyflow.config.Credentials; import com.skyflow.enums.Env; import com.skyflow.enums.TokenMode; import com.skyflow.errors.ErrorCode; diff --git a/src/test/java/com/skyflow/vault/data/QueryResponseTest.java b/skyvault/src/test/java/com/skyflow/vault/data/QueryResponseTest.java similarity index 100% rename from src/test/java/com/skyflow/vault/data/QueryResponseTest.java rename to skyvault/src/test/java/com/skyflow/vault/data/QueryResponseTest.java diff --git a/src/test/java/com/skyflow/vault/data/QueryTests.java b/skyvault/src/test/java/com/skyflow/vault/data/QueryTests.java similarity index 100% rename from src/test/java/com/skyflow/vault/data/QueryTests.java rename to skyvault/src/test/java/com/skyflow/vault/data/QueryTests.java diff --git a/src/test/java/com/skyflow/vault/data/UpdateTests.java b/skyvault/src/test/java/com/skyflow/vault/data/UpdateTests.java similarity index 100% rename from src/test/java/com/skyflow/vault/data/UpdateTests.java rename to skyvault/src/test/java/com/skyflow/vault/data/UpdateTests.java index 8ae1db8d..ca334976 100644 --- a/src/test/java/com/skyflow/vault/data/UpdateTests.java +++ b/skyvault/src/test/java/com/skyflow/vault/data/UpdateTests.java @@ -1,8 +1,8 @@ package com.skyflow.vault.data; import com.skyflow.Skyflow; -import com.skyflow.config.Credentials; import com.skyflow.config.VaultConfig; +import com.skyflow.config.Credentials; import com.skyflow.enums.Env; import com.skyflow.enums.TokenMode; import com.skyflow.errors.ErrorCode; diff --git a/src/test/java/com/skyflow/vault/detect/DeidentifyFileRequestTest.java b/skyvault/src/test/java/com/skyflow/vault/detect/DeidentifyFileRequestTest.java similarity index 100% rename from src/test/java/com/skyflow/vault/detect/DeidentifyFileRequestTest.java rename to skyvault/src/test/java/com/skyflow/vault/detect/DeidentifyFileRequestTest.java diff --git a/src/test/java/com/skyflow/vault/detect/DeidentifyFileResponseTest.java b/skyvault/src/test/java/com/skyflow/vault/detect/DeidentifyFileResponseTest.java similarity index 100% rename from src/test/java/com/skyflow/vault/detect/DeidentifyFileResponseTest.java rename to skyvault/src/test/java/com/skyflow/vault/detect/DeidentifyFileResponseTest.java diff --git a/src/test/java/com/skyflow/vault/detect/DeidentifyTextTests.java b/skyvault/src/test/java/com/skyflow/vault/detect/DeidentifyTextTests.java similarity index 100% rename from src/test/java/com/skyflow/vault/detect/DeidentifyTextTests.java rename to skyvault/src/test/java/com/skyflow/vault/detect/DeidentifyTextTests.java diff --git a/src/test/java/com/skyflow/vault/detect/FileEntityInfoTest.java b/skyvault/src/test/java/com/skyflow/vault/detect/FileEntityInfoTest.java similarity index 100% rename from src/test/java/com/skyflow/vault/detect/FileEntityInfoTest.java rename to skyvault/src/test/java/com/skyflow/vault/detect/FileEntityInfoTest.java diff --git a/src/test/java/com/skyflow/vault/detect/FileInfoTest.java b/skyvault/src/test/java/com/skyflow/vault/detect/FileInfoTest.java similarity index 100% rename from src/test/java/com/skyflow/vault/detect/FileInfoTest.java rename to skyvault/src/test/java/com/skyflow/vault/detect/FileInfoTest.java diff --git a/src/test/java/com/skyflow/vault/detect/ReidentifyTextTests.java b/skyvault/src/test/java/com/skyflow/vault/detect/ReidentifyTextTests.java similarity index 100% rename from src/test/java/com/skyflow/vault/detect/ReidentifyTextTests.java rename to skyvault/src/test/java/com/skyflow/vault/detect/ReidentifyTextTests.java diff --git a/src/test/java/com/skyflow/vault/tokens/DetokenizeTests.java b/skyvault/src/test/java/com/skyflow/vault/tokens/DetokenizeTests.java similarity index 100% rename from src/test/java/com/skyflow/vault/tokens/DetokenizeTests.java rename to skyvault/src/test/java/com/skyflow/vault/tokens/DetokenizeTests.java diff --git a/src/test/java/com/skyflow/vault/tokens/TokenizeTests.java b/skyvault/src/test/java/com/skyflow/vault/tokens/TokenizeTests.java similarity index 100% rename from src/test/java/com/skyflow/vault/tokens/TokenizeTests.java rename to skyvault/src/test/java/com/skyflow/vault/tokens/TokenizeTests.java index 4c279fde..27563a57 100644 --- a/src/test/java/com/skyflow/vault/tokens/TokenizeTests.java +++ b/skyvault/src/test/java/com/skyflow/vault/tokens/TokenizeTests.java @@ -1,8 +1,8 @@ package com.skyflow.vault.tokens; import com.skyflow.Skyflow; -import com.skyflow.config.Credentials; import com.skyflow.config.VaultConfig; +import com.skyflow.config.Credentials; import com.skyflow.enums.Env; import com.skyflow.errors.ErrorCode; import com.skyflow.errors.ErrorMessage; diff --git a/skyvault/src/test/resources/notJson.txt b/skyvault/src/test/resources/notJson.txt new file mode 100644 index 00000000..bdf08de0 --- /dev/null +++ b/skyvault/src/test/resources/notJson.txt @@ -0,0 +1 @@ +test file \ No newline at end of file diff --git a/src/main/java/com/skyflow/Skyflow.java b/src/main/java/com/skyflow/Skyflow.java deleted file mode 100644 index eba8d6fd..00000000 --- a/src/main/java/com/skyflow/Skyflow.java +++ /dev/null @@ -1,314 +0,0 @@ -package com.skyflow; - -import com.skyflow.config.ConnectionConfig; -import com.skyflow.config.Credentials; -import com.skyflow.config.VaultConfig; -import com.skyflow.enums.Env; -import com.skyflow.enums.LogLevel; -import com.skyflow.errors.ErrorCode; -import com.skyflow.errors.ErrorMessage; -import com.skyflow.errors.SkyflowException; -import com.skyflow.logs.ErrorLogs; -import com.skyflow.logs.InfoLogs; -import com.skyflow.utils.Utils; -import com.skyflow.utils.logger.LogUtil; -import com.skyflow.utils.validations.Validations; -import com.skyflow.vault.controller.ConnectionController; -import com.skyflow.vault.controller.DetectController; -import com.skyflow.vault.controller.VaultController; - -import java.util.LinkedHashMap; - -public final class Skyflow { - private final SkyflowClientBuilder builder; - - private Skyflow(SkyflowClientBuilder builder) { - this.builder = builder; - LogUtil.printInfoLog(InfoLogs.CLIENT_INITIALIZED.getLog()); - } - - public static SkyflowClientBuilder builder() { - return new SkyflowClientBuilder(); - } - - public Skyflow addVaultConfig(VaultConfig vaultConfig) throws SkyflowException { - this.builder.addVaultConfig(vaultConfig); - return this; - } - - public VaultConfig getVaultConfig(String vaultId) { - return this.builder.vaultConfigMap.get(vaultId); - } - - public Skyflow updateVaultConfig(VaultConfig vaultConfig) throws SkyflowException { - this.builder.updateVaultConfig(vaultConfig); - return this; - } - - public Skyflow removeVaultConfig(String vaultId) throws SkyflowException { - this.builder.removeVaultConfig(vaultId); - return this; - } - - public Skyflow addConnectionConfig(ConnectionConfig connectionConfig) throws SkyflowException { - this.builder.addConnectionConfig(connectionConfig); - return this; - } - - public ConnectionConfig getConnectionConfig(String connectionId) { - return this.builder.connectionConfigMap.get(connectionId); - } - - public Skyflow updateConnectionConfig(ConnectionConfig connectionConfig) throws SkyflowException { - this.builder.updateConnectionConfig(connectionConfig); - return this; - } - - public Skyflow removeConnectionConfig(String connectionId) throws SkyflowException { - this.builder.removeConnectionConfig(connectionId); - return this; - } - - public Skyflow updateSkyflowCredentials(Credentials credentials) throws SkyflowException { - this.builder.addSkyflowCredentials(credentials); - return this; - } - - public Skyflow setLogLevel(LogLevel logLevel) { - this.builder.setLogLevel(logLevel); - return this; - } - - /** @deprecated Use {@link #setLogLevel(LogLevel)} instead. */ - @Deprecated(since = "2.1", forRemoval = true) - public Skyflow updateLogLevel(LogLevel logLevel) { - LogUtil.printWarningLog(InfoLogs.DEPRECATED_UPDATE_LOG_LEVEL.getLog()); - return setLogLevel(logLevel); - } - - public LogLevel getLogLevel() { - return this.builder.logLevel; - } - - public VaultController vault() throws SkyflowException { - Object[] array = this.builder.vaultClientsMap.keySet().toArray(); - if (array.length < 1) { - LogUtil.printErrorLog(ErrorLogs.VAULT_CONFIG_DOES_NOT_EXIST.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.VaultIdNotInConfigList.getMessage()); - } - String vaultId = (String) array[0]; - return this.vault(vaultId); - } - - public VaultController vault(String vaultId) throws SkyflowException { - VaultController controller = this.builder.vaultClientsMap.get(vaultId); - if (controller == null) { - LogUtil.printErrorLog(ErrorLogs.VAULT_CONFIG_DOES_NOT_EXIST.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.VaultIdNotInConfigList.getMessage()); - } - return controller; - } - - - public ConnectionController connection() throws SkyflowException { - Object[] array = this.builder.connectionsMap.keySet().toArray(); - if (array.length < 1) { - LogUtil.printErrorLog(ErrorLogs.CONNECTION_CONFIG_DOES_NOT_EXIST.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.ConnectionIdNotInConfigList.getMessage()); - } - String connectionId = (String) array[0]; - return this.connection(connectionId); - } - - public ConnectionController connection(String connectionId) throws SkyflowException { - ConnectionController controller = this.builder.connectionsMap.get(connectionId); - if (controller == null) { - LogUtil.printErrorLog(ErrorLogs.CONNECTION_CONFIG_DOES_NOT_EXIST.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.ConnectionIdNotInConfigList.getMessage()); - } - return controller; - } - - public DetectController detect() throws SkyflowException { - Object[] array = this.builder.detectClientsMap.keySet().toArray(); - if (array.length < 1) { - LogUtil.printErrorLog(ErrorLogs.VAULT_CONFIG_DOES_NOT_EXIST.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.VaultIdNotInConfigList.getMessage()); - } - String detectId = (String) array[0]; - return this.detect(detectId); - } - - public DetectController detect(String vaultId) throws SkyflowException { - DetectController controller = this.builder.detectClientsMap.get(vaultId); - if (controller == null) { - LogUtil.printErrorLog(ErrorLogs.VAULT_CONFIG_DOES_NOT_EXIST.getLog()); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.VaultIdNotInConfigList.getMessage()); - } - return controller; - } - - public static final class SkyflowClientBuilder { - private final LinkedHashMap connectionsMap; - private final LinkedHashMap vaultClientsMap; - private final LinkedHashMap detectClientsMap; - private final LinkedHashMap vaultConfigMap; - private final LinkedHashMap connectionConfigMap; - private Credentials skyflowCredentials; - private LogLevel logLevel; - - public SkyflowClientBuilder() { - this.vaultClientsMap = new LinkedHashMap<>(); - this.detectClientsMap = new LinkedHashMap<>(); - this.vaultConfigMap = new LinkedHashMap<>(); - this.connectionsMap = new LinkedHashMap<>(); - this.connectionConfigMap = new LinkedHashMap<>(); - this.skyflowCredentials = null; - this.logLevel = LogLevel.ERROR; - } - - public SkyflowClientBuilder addVaultConfig(VaultConfig vaultConfig) throws SkyflowException { - LogUtil.printInfoLog(InfoLogs.VALIDATING_VAULT_CONFIG.getLog()); - Validations.validateVaultConfig(vaultConfig); - if (this.vaultClientsMap.containsKey(vaultConfig.getVaultId())) { - LogUtil.printErrorLog(Utils.parameterizedString( - ErrorLogs.VAULT_CONFIG_EXISTS.getLog(), vaultConfig.getVaultId() - )); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), - ErrorMessage.VaultIdAlreadyInConfigList.getMessage()); - } else { - this.vaultConfigMap.put(vaultConfig.getVaultId(), vaultConfig); - this.vaultClientsMap.put(vaultConfig.getVaultId(), new VaultController(vaultConfig, this.skyflowCredentials)); - this.detectClientsMap.put(vaultConfig.getVaultId(), new DetectController(vaultConfig, this.skyflowCredentials)); - LogUtil.printInfoLog(Utils.parameterizedString( - InfoLogs.VAULT_CONTROLLER_INITIALIZED.getLog(), vaultConfig.getVaultId())); - LogUtil.printInfoLog(Utils.parameterizedString( - InfoLogs.DETECT_CONTROLLER_INITIALIZED.getLog(), vaultConfig.getVaultId())); - } - return this; - } - - public SkyflowClientBuilder updateVaultConfig(VaultConfig vaultConfig) throws SkyflowException { - LogUtil.printInfoLog(InfoLogs.VALIDATING_VAULT_CONFIG.getLog()); - Validations.validateVaultConfig(vaultConfig); - if (this.vaultClientsMap.containsKey(vaultConfig.getVaultId())) { - VaultConfig updatedConfig = findAndUpdateVaultConfig(vaultConfig); - this.vaultClientsMap.get(updatedConfig.getVaultId()).updateVaultConfig(); - } else { - LogUtil.printErrorLog(Utils.parameterizedString( - ErrorLogs.VAULT_CONFIG_DOES_NOT_EXIST.getLog(), vaultConfig.getVaultId() - )); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.VaultIdNotInConfigList.getMessage()); - } - return this; - } - - public SkyflowClientBuilder removeVaultConfig(String vaultId) throws SkyflowException { - if (this.vaultClientsMap.containsKey(vaultId)) { - this.vaultClientsMap.remove(vaultId); - this.vaultConfigMap.remove(vaultId); - } else { - LogUtil.printErrorLog(Utils.parameterizedString(ErrorLogs.VAULT_CONFIG_DOES_NOT_EXIST.getLog(), vaultId)); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), ErrorMessage.VaultIdNotInConfigList.getMessage()); - } - return this; - } - - public SkyflowClientBuilder addConnectionConfig(ConnectionConfig connectionConfig) throws SkyflowException { - LogUtil.printInfoLog(InfoLogs.VALIDATING_CONNECTION_CONFIG.getLog()); - Validations.validateConnectionConfig(connectionConfig); - if (this.connectionsMap.containsKey(connectionConfig.getConnectionId())) { - LogUtil.printErrorLog(Utils.parameterizedString( - ErrorLogs.CONNECTION_CONFIG_EXISTS.getLog(), connectionConfig.getConnectionId() - )); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), - ErrorMessage.ConnectionIdAlreadyInConfigList.getMessage()); - } else { - this.connectionConfigMap.put(connectionConfig.getConnectionId(), connectionConfig); - ConnectionController controller = new ConnectionController(connectionConfig, this.skyflowCredentials); - this.connectionsMap.put(connectionConfig.getConnectionId(), controller); - LogUtil.printInfoLog(Utils.parameterizedString( - InfoLogs.CONNECTION_CONTROLLER_INITIALIZED.getLog(), connectionConfig.getConnectionId())); - } - return this; - } - - public SkyflowClientBuilder updateConnectionConfig(ConnectionConfig connectionConfig) throws SkyflowException { - LogUtil.printInfoLog(InfoLogs.VALIDATING_CONNECTION_CONFIG.getLog()); - Validations.validateConnectionConfig(connectionConfig); - if (this.connectionsMap.containsKey(connectionConfig.getConnectionId())) { - ConnectionConfig updatedConfig = findAndUpdateConnectionConfig(connectionConfig); - this.connectionsMap.get(updatedConfig.getConnectionId()).updateConnectionConfig(connectionConfig); - } else { - LogUtil.printErrorLog(Utils.parameterizedString( - ErrorLogs.CONNECTION_CONFIG_DOES_NOT_EXIST.getLog(), connectionConfig.getConnectionId() - )); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), - ErrorMessage.ConnectionIdNotInConfigList.getMessage()); - } - return this; - } - - public SkyflowClientBuilder removeConnectionConfig(String connectionId) throws SkyflowException { - if (this.connectionsMap.containsKey(connectionId)) { - this.connectionsMap.remove(connectionId); - this.connectionConfigMap.remove(connectionId); - } else { - LogUtil.printErrorLog(Utils.parameterizedString( - ErrorLogs.CONNECTION_CONFIG_DOES_NOT_EXIST.getLog(), connectionId - )); - throw new SkyflowException(ErrorCode.INVALID_INPUT.getCode(), - ErrorMessage.ConnectionIdNotInConfigList.getMessage()); - } - return this; - } - - public SkyflowClientBuilder addSkyflowCredentials(Credentials credentials) throws SkyflowException { - Validations.validateCredentials(credentials); - this.skyflowCredentials = credentials; - for (VaultController vault : this.vaultClientsMap.values()) { - vault.setCommonCredentials(this.skyflowCredentials); - } - for (DetectController detect : this.detectClientsMap.values()) { - detect.setCommonCredentials(this.skyflowCredentials); - } - for (ConnectionController connection : this.connectionsMap.values()) { - connection.setCommonCredentials(this.skyflowCredentials); - } - return this; - } - - public SkyflowClientBuilder setLogLevel(LogLevel logLevel) { - this.logLevel = logLevel == null ? LogLevel.ERROR : logLevel; - LogUtil.setupLogger(this.logLevel); - LogUtil.printInfoLog(Utils.parameterizedString( - InfoLogs.CURRENT_LOG_LEVEL.getLog(), String.valueOf(logLevel) - )); - return this; - } - - public Skyflow build() { - return new Skyflow(this); - } - - private VaultConfig findAndUpdateVaultConfig(VaultConfig vaultConfig) { - VaultConfig previousConfig = this.vaultConfigMap.get(vaultConfig.getVaultId()); - Env env = vaultConfig.getEnv() != null ? vaultConfig.getEnv() : previousConfig.getEnv(); - String clusterId = vaultConfig.getClusterId() != null ? vaultConfig.getClusterId() : previousConfig.getClusterId(); - Credentials credentials = vaultConfig.getCredentials() != null ? vaultConfig.getCredentials() : previousConfig.getCredentials(); - previousConfig.setEnv(env); - previousConfig.setClusterId(clusterId); - previousConfig.setCredentials(credentials); - return previousConfig; - } - - private ConnectionConfig findAndUpdateConnectionConfig(ConnectionConfig connectionConfig) { - ConnectionConfig previousConfig = this.connectionConfigMap.get(connectionConfig.getConnectionId()); - String connectionURL = connectionConfig.getConnectionUrl() != null ? connectionConfig.getConnectionUrl() : previousConfig.getConnectionUrl(); - Credentials credentials = connectionConfig.getCredentials() != null ? connectionConfig.getCredentials() : previousConfig.getCredentials(); - previousConfig.setConnectionUrl(connectionURL); - previousConfig.setCredentials(credentials); - return previousConfig; - } - } -} diff --git a/src/main/java/com/skyflow/utils/Constants.java b/src/main/java/com/skyflow/utils/Constants.java deleted file mode 100644 index aa3a3f14..00000000 --- a/src/main/java/com/skyflow/utils/Constants.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.skyflow.utils; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Properties; - -public final class Constants { - public static final String SECURE_PROTOCOL = "https://"; - public static final String DEV_DOMAIN = ".vault.skyflowapis.dev"; - public static final String STAGE_DOMAIN = ".vault.skyflowapis.tech"; - public static final String SANDBOX_DOMAIN = ".vault.skyflowapis-preview.com"; - public static final String PROD_DOMAIN = ".vault.skyflowapis.com"; - public static final String PKCS8_PRIVATE_HEADER = "-----BEGIN PRIVATE KEY-----"; - public static final String PKCS8_PRIVATE_FOOTER = "-----END PRIVATE KEY-----"; - public static final String GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer"; - public static final String SIGNED_DATA_TOKEN_PREFIX = "signed_token_"; - public static final String ORDER_ASCENDING = "ASCENDING"; - public static final String API_KEY_REGEX = "^sky-[a-zA-Z0-9]{5}-[a-fA-F0-9]{32}$"; - public static final String CONTEXT_KEY_REGEX = "^[a-zA-Z0-9_]+$"; - public static final String ENV_CREDENTIALS_KEY_NAME = "SKYFLOW_CREDENTIALS"; - public static final String SDK_NAME = "Skyflow Java SDK"; - public static final String DEFAULT_SDK_VERSION = "v2"; - public static final String SDK_VERSION; - public static final String SDK_PREFIX; - public static final String SDK_METRIC_NAME_VERSION = "sdk_name_version"; - public static final String SDK_METRIC_NAME_VERSION_PREFIX = "skyflow-java@"; - public static final String SDK_METRIC_CLIENT_DEVICE_MODEL = "sdk_client_device_model"; - public static final String SDK_METRIC_CLIENT_OS_DETAILS = "sdk_client_os_details"; - public static final String SDK_METRIC_RUNTIME_DETAILS = "sdk_runtime_details"; - public static final String SDK_METRIC_RUNTIME_DETAILS_PREFIX = "Java@"; - public static final String SDK_AUTH_HEADER_KEY = "x-skyflow-authorization"; - public static final String SDK_METRICS_HEADER_KEY = "sky-metadata"; - public static final String REQUEST_ID_HEADER_KEY = "x-request-id"; - public static final String PROCESSED_FILE_NAME_PREFIX = "processed-"; - public static final String ERROR_FROM_CLIENT_HEADER_KEY = "error-from-client"; - public static final String DEIDENTIFIED_FILE_PREFIX = "deidentified"; - public static final String HTTPS_PROTOCOL = "https"; - public static final String CURLY_PLACEHOLDER = "{%s}"; - public static final String EMPTY_STRING = ""; - public static final String QUOTE = "\""; - - public static final class HttpUtilityExtra { - public static final String SDK_GENERATED_PREFIX = "SDK-Generated-"; - private HttpUtilityExtra() {} - } - - static { - String sdkVersion; - // Use a static initializer block to read the properties file - Properties properties = new Properties(); - try (InputStream input = Constants.class.getClassLoader().getResourceAsStream("sdk.properties")) { - if (input == null) { - sdkVersion = DEFAULT_SDK_VERSION; - } else { - properties.load(input); - sdkVersion = properties.getProperty("sdk.version", DEFAULT_SDK_VERSION); - } - } catch (IOException ex) { - sdkVersion = DEFAULT_SDK_VERSION; - } - SDK_VERSION = sdkVersion; - SDK_PREFIX = SDK_NAME + " " + SDK_VERSION; - } -} diff --git a/src/test/java/com/skyflow/VaultClientDotenvTests.java b/src/test/java/com/skyflow/VaultClientDotenvTests.java deleted file mode 100644 index 1a54b13d..00000000 --- a/src/test/java/com/skyflow/VaultClientDotenvTests.java +++ /dev/null @@ -1,103 +0,0 @@ -package com.skyflow; - -import com.skyflow.config.Credentials; -import com.skyflow.config.VaultConfig; -import com.skyflow.enums.Env; -import com.skyflow.errors.ErrorMessage; -import com.skyflow.errors.SkyflowException; -import com.skyflow.utils.Constants; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths; - -/** - * Tests for VaultClient's prioritiseCredentials dotenv path. - * - * These tests write a temporary .env file to exercise the code path where - * no VaultConfig credentials and no common credentials are set, so the code - * falls through to read from a .env file. - */ -public class VaultClientDotenvTests { - - private static final String ENV_FILE = ".env"; - private byte[] originalEnvContent; - - @Before - public void saveEnvFileState() throws IOException { - File f = new File(ENV_FILE); - originalEnvContent = f.exists() ? Files.readAllBytes(Paths.get(ENV_FILE)) : null; - } - - @After - public void restoreEnvFile() throws IOException { - if (originalEnvContent != null) { - Files.write(Paths.get(ENV_FILE), originalEnvContent); - } else { - Files.deleteIfExists(Paths.get(ENV_FILE)); - } - } - - private VaultClient buildClientWithNoCreds(String vaultId, String clusterId) { - VaultConfig config = new VaultConfig(); - config.setVaultId(vaultId); - config.setClusterId(clusterId); - config.setEnv(Env.DEV); - // No credentials set - return new VaultClient(config, null); - } - - /** - * Covers the dotenv success path: Dotenv.load() succeeds and returns a - * non-null credentials string, so finalCredentials is set via - * credentialsString. Lines ~862-870 of VaultClient.java. - */ - @Test - public void testPrioritiseCredentials_dotenvReturnsCredentials_setsCredentials() throws Exception { - // Write a .env file with a valid credentials string value - try (FileWriter fw = new FileWriter(ENV_FILE)) { - fw.write(Constants.ENV_CREDENTIALS_KEY_NAME + "={\"token\":\"env-token-value\"}\n"); - } - - VaultClient client = buildClientWithNoCreds("dotenv-vault-1", "cluster1"); - // updateVaultConfig() calls prioritiseCredentials() which reads from .env - // Should not throw since sysCredentials is non-null - client.updateVaultConfig(); - - // finalCredentials should be set with credentials string - java.lang.reflect.Field field = VaultClient.class.getDeclaredField("finalCredentials"); - field.setAccessible(true); - Credentials finalCreds = (Credentials) field.get(client); - Assert.assertNotNull(finalCreds); - Assert.assertEquals("{\"token\":\"env-token-value\"}", finalCreds.getCredentialsString()); - } - - /** - * Covers the path where dotenv loads but the key is absent (returns null), - * causing SkyflowException(EmptyCredentials) to be thrown directly. - * Lines ~864-876 of VaultClient.java. - */ - @Test - public void testPrioritiseCredentials_dotenvKeyMissing_throwsSkyflowException() throws Exception { - // Write a .env file WITHOUT the SKYFLOW_CREDENTIALS key - try (FileWriter fw = new FileWriter(ENV_FILE)) { - fw.write("SOME_OTHER_KEY=some_value\n"); - } - - VaultClient client = buildClientWithNoCreds("dotenv-vault-2", "cluster2"); - try { - client.updateVaultConfig(); - Assert.fail("Should have thrown SkyflowException"); - } catch (SkyflowException e) { - Assert.assertTrue(e.getMessage().contains(ErrorMessage.EmptyCredentials.getMessage())); - } catch (RuntimeException e) { - Assert.fail("Expected direct SkyflowException, not RuntimeException wrapping it"); - } - } -} diff --git a/src/test/java/com/skyflow/utils/UtilsTests.java b/src/test/java/com/skyflow/utils/UtilsTests.java deleted file mode 100644 index 4e910e15..00000000 --- a/src/test/java/com/skyflow/utils/UtilsTests.java +++ /dev/null @@ -1,219 +0,0 @@ -package com.skyflow.utils; - -import com.google.gson.JsonObject; -import com.skyflow.config.ConnectionConfig; -import com.skyflow.config.Credentials; -import com.skyflow.enums.Env; -import com.skyflow.enums.RequestMethod; -import com.skyflow.errors.ErrorCode; -import com.skyflow.errors.ErrorMessage; -import com.skyflow.errors.SkyflowException; -import com.skyflow.vault.connection.InvokeConnectionRequest; -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Test; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - -public class UtilsTests { - private static final String INVALID_EXCEPTION_THROWN = "Should not have thrown any exception"; - private static final String EXCEPTION_NOT_THROWN = "Should have thrown an exception"; - private static String clusterId = null; - private static String url = null; - private static String filePath = null; - private static String credentialsString = null; - private static String token = null; - private static String context = null; - private static ArrayList roles = null; - private static String connectionId = null; - private static String connectionUrl = null; - private static Map queryParams; - private static Map pathParams; - private static Map requestHeaders; - - @BeforeClass - public static void setup() { - clusterId = "test_cluster_id"; - url = "https://test-url.com/java/unit/tests"; - filePath = "invalid/file/path/credentials.json"; - credentialsString = "invalid credentials string"; - token = "invalid-token"; - context = "test_context"; - roles = new ArrayList<>(); - String role = "test_role"; - roles.add(role); - connectionId = "test_connection_id"; - connectionUrl = "https://test.connection.url"; - pathParams = new HashMap<>(); - queryParams = new HashMap<>(); - requestHeaders = new HashMap<>(); - } - - @Test - public void testGetVaultURLForDev() { - try { - String vaultURL = Utils.getVaultURL(clusterId, Env.DEV); - String devUrl = "https://test_cluster_id.vault.skyflowapis.dev"; - Assert.assertEquals(devUrl, vaultURL); - } catch (Exception e) { - Assert.fail(INVALID_EXCEPTION_THROWN); - } - } - - @Test - public void testGetVaultURLForStage() { - try { - String vaultURL = Utils.getVaultURL(clusterId, Env.STAGE); - String stageUrl = "https://test_cluster_id.vault.skyflowapis.tech"; - Assert.assertEquals(stageUrl, vaultURL); - } catch (Exception e) { - Assert.fail(INVALID_EXCEPTION_THROWN); - } - } - - @Test - public void testGetVaultURLForSandbox() { - try { - String vaultURL = Utils.getVaultURL(clusterId, Env.SANDBOX); - String sandboxUrl = "https://test_cluster_id.vault.skyflowapis-preview.com"; - Assert.assertEquals(sandboxUrl, vaultURL); - } catch (Exception e) { - Assert.fail(INVALID_EXCEPTION_THROWN); - } - } - - @Test - public void testGetVaultURLForProd() { - try { - String vaultURL = Utils.getVaultURL(clusterId, Env.PROD); - String prodUrl = "https://test_cluster_id.vault.skyflowapis.com"; - Assert.assertEquals(prodUrl, vaultURL); - } catch (Exception e) { - Assert.fail(INVALID_EXCEPTION_THROWN); - } - } - - @Test - public void testGetBaseURL() { - try { - String baseURL = Utils.getBaseURL(url); - String url = "https://test-url.com"; - Assert.assertEquals(url, baseURL); - } catch (Exception e) { - Assert.fail(INVALID_EXCEPTION_THROWN); - } - } - - @Test - public void testGenerateBearerTokenWithCredentialsFile() { - try { - Credentials credentials = new Credentials(); - credentials.setPath(filePath); - credentials.setContext(context); - credentials.setRoles(roles); - Utils.generateBearerToken(credentials); - Assert.fail(EXCEPTION_NOT_THROWN); - } catch (SkyflowException e) { - Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); - Assert.assertEquals( - Utils.parameterizedString(ErrorMessage.FileNotFound.getMessage(), filePath), - e.getMessage() - ); - } - } - - @Test - public void testGenerateBearerTokenWithCredentialsString() { - try { - Credentials credentials = new Credentials(); - credentials.setCredentialsString(credentialsString); - credentials.setContext(context); - credentials.setRoles(roles); - Utils.generateBearerToken(credentials); - Assert.fail(EXCEPTION_NOT_THROWN); - } catch (SkyflowException e) { - Assert.assertEquals(ErrorCode.INVALID_INPUT.getCode(), e.getHttpCode()); - Assert.assertEquals(ErrorMessage.CredentialsStringInvalidJson.getMessage(), e.getMessage()); - } - } - - @Test - public void testGenerateBearerTokenWithToken() { - try { - Credentials credentials = new Credentials(); - credentials.setToken(token); - credentials.setContext(context); - credentials.setRoles(roles); - String bearerToken = Utils.generateBearerToken(credentials); - Assert.assertEquals(token, bearerToken); - } catch (SkyflowException e) { - Assert.fail(INVALID_EXCEPTION_THROWN); - } - } - - @Test - public void testConstructConnectionURL() { - try { - queryParams.put("query_param", "value"); - pathParams.put("path_param", "value"); - - ConnectionConfig connectionConfig = new ConnectionConfig(); - connectionConfig.setConnectionId(connectionId); - connectionConfig.setConnectionUrl(connectionUrl); - - InvokeConnectionRequest request = InvokeConnectionRequest.builder() - .method(RequestMethod.POST).pathParams(pathParams).queryParams(queryParams).build(); - String filledUrl = Utils.constructConnectionURL(connectionConfig, request); - Assert.assertEquals(connectionUrl + "?" + "query_param=value", filledUrl); - } catch (Exception e) { - Assert.fail(INVALID_EXCEPTION_THROWN); - } - } - - @Test - public void testConstructConnectionHeaderMap() { - try { - requestHeaders.put("HEADER", "value"); - Map headers = Utils.constructConnectionHeadersMap(requestHeaders); - Assert.assertEquals(1, headers.size()); - Assert.assertTrue(headers.containsKey("header")); - Assert.assertFalse(headers.containsKey("HEADER")); - Assert.assertEquals("value", headers.get("header")); - } catch (Exception e) { - Assert.fail(INVALID_EXCEPTION_THROWN); - } - } - - @Test - public void testGetMetrics() { - try { - JsonObject metrics = Utils.getMetrics(); - Assert.assertNotNull(metrics.get(Constants.SDK_METRIC_NAME_VERSION)); - Assert.assertNotNull(metrics.get(Constants.SDK_METRIC_CLIENT_DEVICE_MODEL)); - Assert.assertNotNull(metrics.get(Constants.SDK_METRIC_CLIENT_OS_DETAILS)); - Assert.assertNotNull(metrics.get(Constants.SDK_METRIC_RUNTIME_DETAILS)); - } catch (Exception e) { - Assert.fail(INVALID_EXCEPTION_THROWN); - } - } - - @Test - public void testGetMetricsWithException() { - try { - // Clearing System Properties explicitly to throw exception - System.clearProperty("os.name"); - System.clearProperty("os.version"); - System.clearProperty("java.version"); - - JsonObject metrics = Utils.getMetrics(); - Assert.assertEquals("skyflow-java@" + Constants.SDK_VERSION, metrics.get(Constants.SDK_METRIC_NAME_VERSION).getAsString()); - Assert.assertEquals("Java@", metrics.get(Constants.SDK_METRIC_RUNTIME_DETAILS).getAsString()); - Assert.assertTrue(metrics.get(Constants.SDK_METRIC_CLIENT_DEVICE_MODEL).getAsString().isEmpty()); - Assert.assertTrue(metrics.get(Constants.SDK_METRIC_CLIENT_OS_DETAILS).getAsString().isEmpty()); - } catch (Exception e) { - Assert.fail(INVALID_EXCEPTION_THROWN); - } - } -}