Repository navigation
Generated client libraries #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,132 @@ | ||
| --- | ||
| name: update-openapi-templates | ||
| description: Reconcile the custom OpenAPI Generator Mustache templates in sdk/java-restclient and sdk/java-resttemplate against upstream after a generator version bump, then regenerate and verify. Use when the openapi-generator version changes, when generation fails with a Mustache parse error, or when checking whether our templates have drifted from upstream. | ||
| --- | ||
|
|
||
| # Update OpenAPI Generator templates | ||
|
|
||
| Four SDK modules override generator templates. When the generator version is bumped, upstream | ||
| changes must be reconciled into those overrides. | ||
|
|
||
| Our deliberate divergences are recorded in `sdk/custom-template-changes.md`. **Read it first** — it is | ||
| the list of things a reconciliation must preserve, and each entry says what breaks if it is lost. | ||
|
|
||
| ## Overridden templates | ||
|
|
||
| ``` | ||
| sdk/java-restclient/templates/ (java, library restclient) | ||
| sdk/java-resttemplate/templates/ (java, library resttemplate) | ||
| sdk/swift/templates/ (swift5) | ||
| sdk/python/templates/ (python) | ||
| ``` | ||
|
|
||
| Reconcile each against the upstream directory for **its own** generator. | ||
|
|
||
| ## Steps | ||
|
|
||
| ### 1 — Identify the versions | ||
|
|
||
| ```bash | ||
| git log --oneline -10 -- gradle/libs.versions.toml | ||
| grep -n 'openapi' gradle/libs.versions.toml | ||
| ``` | ||
|
|
||
| ### 2 — Extract the upstream templates for the version we run | ||
|
|
||
| Take them from the generator jar, not GitHub, so they match the running version exactly: | ||
|
|
||
| ```bash | ||
| V=7.22.0 | ||
| JAR=$(find ~/.gradle/caches/modules-2/files-2.1/org.openapitools/openapi-generator/$V \ | ||
| -name "openapi-generator-$V.jar" ! -name '*sources*' | head -1) | ||
| unzip -o "$JAR" 'Java/*' -d /tmp/upstream-$V | ||
| ``` | ||
|
|
||
| ### 3 — Diff against the *library-specific* baseline | ||
|
|
||
| The `java` generator resolves `pojo.mustache` per library. Diffing against the generic | ||
| `Java/pojo.mustache` gives plausible but wrong results — it lacks the JSpecify type-position wiring | ||
| these libraries use, and reconciling against it silently drops nullability annotations. | ||
|
|
||
| | Module | Baseline | | ||
| | --- | --- | | ||
| | `sdk/java-restclient` | `Java/libraries/restclient/pojo.mustache` | | ||
| | `sdk/java-resttemplate` | `Java/libraries/resttemplate/pojo.mustache` | | ||
|
|
||
| The two differ from each other — reconcile each module separately. | ||
|
|
||
| Our copies follow upstream formatting. If a diff is unreadable because of indentation noise, compare | ||
| whitespace-insensitively (`diff -uwB`) to find the semantic changes. | ||
|
|
||
| ### 4 — Reconcile | ||
|
|
||
| Prefer re-forking from the upstream baseline and re-applying the divergences in | ||
| `sdk/custom-template-changes.md` over hand-patching our copy. Re-forking picks up upstream fixes that | ||
| are dormant for our spec today (they render nothing now, but will if the spec grows into them); | ||
| hand-patching only ever finds what you already went looking for. | ||
|
|
||
| Classify each upstream change: | ||
|
|
||
| - **Cosmetic / structural** — take it as-is. | ||
| - **Touches `required` / `isNullable` / `x-is-jackson-optional-nullable`** — re-apply through the | ||
| polarity swap in change (1) of `custom-template-changes.md`. | ||
| - **New upstream behaviour we do not customise** — take it, and do not document it. | ||
| - **Superseded by one of our divergences** — skip, and check the divergence still reads correctly | ||
| against the new upstream code. | ||
|
|
||
| ### 5 — Check tag balance before generating | ||
|
|
||
| A mismatched section tag fails generation with `Section close tag with mismatched open tag`, naming a | ||
| line in the *rendered* partial rather than in the file at fault. Verify balance directly: | ||
|
|
||
| ```bash | ||
| python3 - sdk/java-restclient/templates/pojo.mustache <<'PY' | ||
| import re, sys | ||
| stack = [] | ||
| for m in re.finditer(r'\{\{([#^/])([^}]+)\}\}', open(sys.argv[1]).read()): | ||
| kind, name = m.group(1), m.group(2).strip() | ||
| if kind in '#^': | ||
| stack.append(name) | ||
| else: | ||
| assert stack and stack[-1] == name, f"close {name} vs open {stack[-1] if stack else None}" | ||
| stack.pop() | ||
| assert not stack, f"unclosed: {stack}" | ||
| print("balanced") | ||
| PY | ||
| ``` | ||
|
|
||
| ### 6 — Regenerate and build | ||
|
|
||
| Compilation depends on generation, so a build regenerates first: | ||
|
|
||
| ```bash | ||
| source "$HOME/.sdkman/bin/sdkman-init.sh" && sdk env && \ | ||
| ./gradlew :java-restclient:build :java-resttemplate:build | ||
| ``` | ||
|
|
||
| ### 7 — Verify against the previous output | ||
|
|
||
| Copy the generated models aside *before* regenerating, then compare. Whitespace will move when | ||
| upstream reformats; what matters is that these hold: | ||
|
|
||
| - `toString` line count unchanged — the `x-log` privacy allow-list survived | ||
| - no `_JsonNullable` occurrences — the public API shape survived | ||
| - `JsonNullable<@Nullable` present — JSpecify annotates the value, not the wrapper | ||
| - `@Nullable` in type position (`private @Nullable String x;`) | ||
| - no `@jakarta.annotation.Nullable` / `Nonnull` left in models | ||
|
|
||
| The `toString` check is the important one: losing it is invisible in a build and starts writing | ||
| personal data to logs. | ||
|
|
||
| ### 8 — Update the documentation | ||
|
|
||
| Revise `sdk/custom-template-changes.md` so it describes only what still differs from upstream. Drop | ||
| entries that upstream has absorbed. | ||
|
|
||
| ### 9 — Commit | ||
|
|
||
| Commit the templates, the regenerated sources and the documentation together: | ||
|
|
||
| ``` | ||
| Update OpenAPI generator templates to v<NEW_VERSION> | ||
| ``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| ../../.agents/skills/update-openapi-templates |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,146 @@ | ||
| name: SDK | ||
|
|
||
| on: | ||
| push: | ||
| branches: [main] | ||
| paths: ['spec/**', 'sdk/**', 'buildSrc/**', 'gradle/**', 'build.gradle', 'settings.gradle', '.github/workflows/sdk.yml'] | ||
| pull_request: | ||
| paths: ['spec/**', 'sdk/**', 'buildSrc/**', 'gradle/**', 'build.gradle', 'settings.gradle', '.github/workflows/sdk.yml'] | ||
| workflow_dispatch: | ||
|
|
||
| permissions: | ||
| contents: read | ||
| checks: write | ||
|
|
||
| concurrency: | ||
| group: sdk-${{ github.event.pull_request.head.ref || github.ref_name }} | ||
| cancel-in-progress: true | ||
|
|
||
| jobs: | ||
| generated-sources: | ||
| name: Generated sources | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v6 | ||
|
|
||
| - uses: troii-actions/setup-jvm@v1 | ||
| with: | ||
| java-version-file: .sdkmanrc | ||
|
|
||
| - name: Generate SDKs | ||
| run: ./gradlew openApiGenerate --scan | ||
|
|
||
| - name: Verify generated sources are committed | ||
| run: | | ||
| # `git diff` alone ignores untracked files, so a newly emitted generated | ||
| # file would pass while missing from the repository. | ||
| git add --all --intent-to-add . | ||
| git diff --exit-code || ( | ||
| echo "Generated sources are out of date. Run ./gradlew openApiGenerate and commit the results." | ||
| exit 1 | ||
| ) | ||
|
|
||
| jvm: | ||
| name: ${{ matrix.name }} | ||
| runs-on: ubuntu-latest | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| include: | ||
| - name: Java RestClient | ||
| module: java-restclient | ||
| path: sdk/java-restclient | ||
| - name: Java RestTemplate | ||
| module: java-resttemplate | ||
| path: sdk/java-resttemplate | ||
| - name: Kotlin OkHttp | ||
| module: kotlin-okhttp | ||
| path: sdk/kotlin-okhttp | ||
| steps: | ||
| - uses: actions/checkout@v6 | ||
|
|
||
| - uses: troii-actions/setup-jvm@v1 | ||
| with: | ||
| java-version-file: .sdkmanrc | ||
|
|
||
| - name: Assemble and test | ||
| run: ./gradlew :${{ matrix.module }}:assemble :${{ matrix.module }}:test --scan | ||
|
|
||
| - name: Publish test report | ||
| if: failure() | ||
| uses: mikepenz/action-junit-report@v6 | ||
| with: | ||
| report_paths: '${{ matrix.path }}/build/test-results/test/TEST-*.xml' | ||
| check_name: Tests ${{ matrix.name }} | ||
| detailed_summary: true | ||
|
|
||
| dotnet: | ||
| name: .NET | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v6 | ||
|
|
||
| - uses: troii-actions/setup-jvm@v1 | ||
| with: | ||
| java-version-file: .sdkmanrc | ||
|
|
||
| - uses: actions/setup-dotnet@v5 | ||
| with: | ||
| dotnet-version: | | ||
| 8.x | ||
| 10.x | ||
|
|
||
| - name: Build .NET SDK | ||
| run: ./gradlew :dotnet:dotnetBuild --scan | ||
|
|
||
| swift: | ||
| name: Swift | ||
| runs-on: macos-latest | ||
| steps: | ||
| - uses: actions/checkout@v6 | ||
|
|
||
| - uses: troii-actions/setup-jvm@v1 | ||
| with: | ||
| java-version-file: .sdkmanrc | ||
|
|
||
| - name: Assemble Swift SDK | ||
| run: ./gradlew :swift:assemble --scan | ||
|
|
||
| python: | ||
| name: Python | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v6 | ||
|
|
||
| - uses: actions/setup-python@v6 | ||
| with: | ||
| python-version: '3.9' | ||
|
|
||
| - name: Install SDK | ||
| working-directory: sdk/python | ||
| run: | | ||
| python -m pip install --upgrade pip | ||
| pip install . | ||
|
|
||
| # Importing the package exercises every module, so a generator change that | ||
| # emits invalid Python fails here. mypy is not run yet: the generator emits a | ||
| # duplicate "OAuth2" AuthSettings key for our two-flow OAuth2 scheme. | ||
| - name: Verify the package imports | ||
| working-directory: sdk/python | ||
| run: python -c "import timr_api; print(timr_api.__name__)" | ||
|
|
||
| typescript: | ||
| name: TypeScript | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v6 | ||
|
|
||
| - uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: '22' | ||
|
|
||
| - name: Build TypeScript SDK | ||
| working-directory: sdk/typescript-fetch | ||
| run: | | ||
| npm install | ||
| npm run build | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| # Enable auto-env through the sdkman_auto_env config | ||
| # Add key=value pairs of SDKs to use below | ||
| java=25-tem |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Plain
git diff --exit-codeignores untracked files, so when a specification change generates a new API or model file without modifying an existing tracked file, this verification step succeeds even though that generated source was never committed. Check the porcelain status or otherwise include untracked output so CI enforces the documented no-drift invariant.AGENTS.md reference: AGENTS.md:L56-L58
Useful? React with 👍 / 👎.